diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..6a362884 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,19 @@ +name: Gradle build + +on: [ push ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index adc74adf..00000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Gradle build - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Build with Gradle - run: ./gradlew build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..0fbf9c1e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,40 @@ +name: Bintray Publish + +on: + release: + types: + - created + +jobs: + build-on-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Gradle clean + run: ./gradlew clean + - name: Gradle build + run: ./gradlew build + - name: Run release task + run: ./gradlew release -PbintrayUser=${{ secrets.BINTRAY_USER }} -PbintrayApiKey=${{ secrets.BINTRAY_KEY }} + build-on-macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Gradle clean + run: ./gradlew clean + - name: Gradle build + run: ./gradlew build + - name: Run release task + run: ./gradlew release -PbintrayUser=${{ secrets.BINTRAY_USER }} -PbintrayApiKey=${{ secrets.BINTRAY_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 5018fb97..ccbd72a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,37 @@ ### Fixed ### Security + +## [0.3.0] +### Added +- Yaml meta format based on yaml.kt +- `Path` builders +- Special ValueType for lists +- `copy` method to descriptors +- Multiplatform yaml meta + +### Changed +- `ListValue` and `DoubleArrayValue` implement `Iterable`. +- Changed the logic of `Value::isList` to check for type instead of size +- `Meta{}` builder made inline +- Moved `Envelope` builder to a top level function. Companion invoke is deprecated. +- Context logging moved to the extension +- `number` and `string` methods on `Value` moved to extensions (breaking change) +- \[Major breaking change\] Schemes and configurables us `MutableItemProvider` instead of `Config` +- \[Major breaking change\] `MetaItem` renamed to `TypedMetaItem` and `MetaItem` is now an alias for `TypedMetaItem<*>` +- \[Major breaking change\] Moved `NodeItem` and `ValueItem` to a top level +- Plugins are removed from Context constructor and added lazily in ContextBuilder +- \[Major breaking change\] Full refactor of DataTree/DataSource +- \[Major Breaking change\] Replace KClass with KType in data. Remove direct access to constructors with types. + +### Deprecated + +### Removed + +### Fixed + +### Security + ## [0.2.0] ### Added diff --git a/README.md b/README.md index 4bd6df9f..10b32c35 100644 --- a/README.md +++ b/README.md @@ -5,98 +5,53 @@ [ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/dataforge-meta/images/download.svg) ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion) +
+* ### [dataforge-context](dataforge-context) +> +> +> **Maturity**: DEVELOPMENT +
-# Questions and Answers # +* ### [dataforge-data](dataforge-data) +> +> +> **Maturity**: EXPERIMENTAL +
-In this section we will try to cover DataForge main ideas in the form of questions and answers. +* ### [dataforge-io](dataforge-io) +> +> +> **Maturity**: PROTOTYPE +
-## General ## +* ### [dataforge-meta](dataforge-meta) +> +> +> **Maturity**: DEVELOPMENT +
-**Q:** I have a lot of data to analyze. The analysis process is complicated, requires a lot of stages and data flow is not always obvious. To top it the data size is huge, so I don't want to perform operation I don't need (calculate something I won't need or calculate something twice). And yes, I need it to be performed in parallel and probably on remote computer. By the way, I am sick and tired of scripts that modify other scripts that control scripts. Could you help me? +* ### [dataforge-scripting](dataforge-scripting) +> +> +> **Maturity**: PROTOTYPE +
-**A:** Yes, that is the precisely the problem DataForge was made to solve. It allows to perform some automated data manipulations with automatic optimization and parallelization. The important thing that data processing recipes are made in the declarative way, so it is quite easy to perform computations on a remote station. Also DataForge guarantees reproducibility of analysis results. -
+* ### [dataforge-tables](dataforge-tables) +> +> +> **Maturity**: PROTOTYPE +
-**Q:** How does it work? +* ### [dataforge-workspace](dataforge-workspace) +> +> +> **Maturity**: EXPERIMENTAL +
-**A:** At the core of DataForge lies the idea of **metadata processor**. It utilizes the statement that in order to analyze something you need data itself and some additional information about what does that data represent and what does user want as a result. This additional information is called metadata and could be organized in a regular structure (a tree of values not unlike XML or JSON). The important thing is that this distinction leaves no place for user instructions (or scripts). Indeed, the idea of DataForge logic is that one do not need imperative commands. The framework configures itself according to input meta-data and decides what operations should be performed in the most efficient way. -
+* ### [dataforge-io-yaml](dataforge-io-yaml) +> YAML meta converters and Front Matter envelope format +> +> **Maturity**: PROTOTYPE +
-**Q:** But where does it take algorithms to use? - -**A:** Of course algorithms must be written somewhere. No magic here. The logic is written in specialized modules. Some modules are provided out of the box at the system core, some need to be developed for specific problem. -
- -**Q:** So I still need to write the code? What is the difference then? - -**A:** Yes, someone still need to write the code. But not necessary you. Simple operations could be performed using provided core logic. Also your group can have one programmer writing the logic and all other using it without any real programming expertise. Also the framework organized in a such way that one writes some additional logic, he do not need to thing about complicated thing like parallel computing, resource handling, logging, caching etc. Most of the things are done by the DataForge. -
- -## Platform ## - -**Q:** Which platform does DataForge use? Which operation system is it working on? - -**A:** The DataForge is mostly written in Java and utilizes JVM as a platform. It works on any system that supports JVM (meaning almost any modern system excluding some mobile platforms). -
- - **Q:** But Java... it is slow! - - **A:** [It is not](https://stackoverflow.com/questions/2163411/is-java-really-slow/2163570#2163570). It lacks some hardware specific optimizations and requires some additional time to start (due to JIT nature), but otherwise it is at least as fast as other languages traditionally used in science. More importantly, the memory safety, tooling support and vast ecosystem makes it №1 candidate for data analysis framework. - -
- - **Q:** Can I use my C++/Fortran/Python code in DataForge? - - **A:** Yes, as long as the code could be called from Java. Most of common languages have a bridge for Java access. There are completely no problems with compiled C/Fortran libraries. Python code could be called via one of existing python-java interfaces. It is also planned to implement remote method invocation for common languages, so your Python, or, say, Julia, code could run in its native environment. The metadata processor paradigm makes it much easier to do so. - -
- -## Features ## - -**Q:** What other features does DataForge provide? - -**A:** Alongside metadata processing (and a lot of tools for metadata manipulation and layering), DataForge has two additional important concepts: - -* **Modularisation**. Contrary to lot other frameworks, DataForge is intrinsically modular. The mandatory part is a rather tiny core module. Everything else could be customized. - -* **Context encapsulation**. Every DataForge task is executed in some context. The context isolates environment for the task and also works as dependency injection base and specifies interaction of the task with the external world. - - - - -
- - - -
- -## Misc ## - -**Q:** So everything looks great, can I replace my ROOT / other data analysis framework with DataForge? - -**A:** One must note, that DataForge is made for analysis, not for visualisation. The visualisation and user interaction capabilities of DataForge are rather limited compared to frameworks like ROOT, JAS3 or DataMelt. The idea is to provide reliable API and core functionality. In fact JAS3 and DataMelt could be used as a frontend for DataForge mechanics. It is planned to add an interface to ROOT via JFreeHep AIDA. - -
- -**Q:** How does DataForge compare to cluster computation frameworks like Hadoop or Spark? - -**A:** Again, it is not the purpose of DataForge to replace cluster software. DataForge has some internal parallelism mechanics and implementations, but they are most certainly worse then specially developed programs. Still, DataForge is not fixed on one single implementation. Your favourite parallel processing tool could be still used as a back-end for the DataForge. With full benefit of configuration tools, integrations and no performance overhead. - - -
- -**Q:** Can I use DataForge on a mobile platform? - -**A:** DataForge is modular. Core and the most of api are pretty compact, so it could be used in Android applications. Some modules are designed for PC and could not be used on other platforms. IPhone does not support Java and therefore could use only client-side DataForge applications. diff --git a/build.gradle.kts b/build.gradle.kts index 7d138083..21633df0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,23 +2,27 @@ plugins { id("ru.mipt.npm.project") } -val dataforgeVersion by extra("0.2.0") +val dataforgeVersion by extra("0.3.0") + -val bintrayRepo by extra("dataforge") -val githubProject by extra("dataforge-core") -val spaceRepo by extra("https://maven.jetbrains.space/mipt-npm/p/df/maven") allprojects { group = "hep.dataforge" version = dataforgeVersion apply() - - repositories { - mavenLocal() - } } subprojects { apply(plugin = "ru.mipt.npm.publish") +} + +readme { + readmeTemplate = file("docs/templates/README-TEMPLATE.md") +} + +ksciencePublish { + bintrayRepo = "dataforge" + githubProject = "dataforge-core" + spaceRepo = "https://maven.jetbrains.space/mipt-npm/p/df/maven" } \ No newline at end of file diff --git a/dataforge-context/api/dataforge-context.api b/dataforge-context/api/dataforge-context.api index 39fa72d8..d2d736d9 100644 --- a/dataforge-context/api/dataforge-context.api +++ b/dataforge-context/api/dataforge-context.api @@ -4,16 +4,15 @@ public abstract class hep/dataforge/context/AbstractPlugin : hep/dataforge/conte public synthetic fun (Lhep/dataforge/meta/Meta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun attach (Lhep/dataforge/context/Context;)V public fun content (Ljava/lang/String;)Ljava/util/Map; - public synthetic fun dependsOn ()Ljava/util/Collection; - public final fun dependsOn ()Ljava/util/List; + public final fun dependsOn ()Ljava/util/Map; public fun detach ()V public fun getContext ()Lhep/dataforge/context/Context; public fun getDefaultChainTarget ()Ljava/lang/String; public fun getDefaultTarget ()Ljava/lang/String; - public fun getLogger ()Lmu/KLogger; public fun getMeta ()Lhep/dataforge/meta/Meta; public fun getName ()Lhep/dataforge/names/Name; - protected final fun require (Lhep/dataforge/context/PluginFactory;)Lkotlin/properties/ReadOnlyProperty; + protected final fun require (Lhep/dataforge/context/PluginFactory;Lhep/dataforge/meta/Meta;)Lkotlin/properties/ReadOnlyProperty; + public static synthetic fun require$default (Lhep/dataforge/context/AbstractPlugin;Lhep/dataforge/context/PluginFactory;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty; public fun toMeta ()Lhep/dataforge/meta/Meta; } @@ -36,18 +35,15 @@ public final class hep/dataforge/context/ClassLoaderPluginKt { public static final fun getClassLoaderPlugin (Lhep/dataforge/context/Context;)Lhep/dataforge/context/ClassLoaderPlugin; } -public class hep/dataforge/context/Context : hep/dataforge/context/Named, hep/dataforge/meta/MetaRepr, hep/dataforge/provider/Provider, kotlinx/coroutines/CoroutineScope { +public class hep/dataforge/context/Context : hep/dataforge/meta/MetaRepr, hep/dataforge/misc/Named, hep/dataforge/provider/Provider, kotlinx/coroutines/CoroutineScope { public static final field Companion Lhep/dataforge/context/Context$Companion; public static final field PROPERTY_TARGET Ljava/lang/String; - public fun (Lhep/dataforge/names/Name;Lhep/dataforge/context/Context;Lhep/dataforge/meta/Meta;Ljava/util/Set;)V - public synthetic fun (Lhep/dataforge/names/Name;Lhep/dataforge/context/Context;Lhep/dataforge/meta/Meta;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close ()V public fun content (Ljava/lang/String;)Ljava/util/Map; public final fun content (Ljava/lang/String;Z)Ljava/util/Map; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getDefaultChainTarget ()Ljava/lang/String; public fun getDefaultTarget ()Ljava/lang/String; - public final fun getLogger ()Lmu/KLogger; public final fun getName ()Lhep/dataforge/names/Name; public final fun getParent ()Lhep/dataforge/context/Context; public final fun getPlugins ()Lhep/dataforge/context/PluginManager; @@ -59,11 +55,6 @@ public final class hep/dataforge/context/Context$Companion { public abstract interface class hep/dataforge/context/ContextAware { public abstract fun getContext ()Lhep/dataforge/context/Context; - public abstract fun getLogger ()Lmu/KLogger; -} - -public final class hep/dataforge/context/ContextAware$DefaultImpls { - public static fun getLogger (Lhep/dataforge/context/ContextAware;)Lmu/KLogger; } public final class hep/dataforge/context/ContextBuilder { @@ -72,7 +63,6 @@ public final class hep/dataforge/context/ContextBuilder { public synthetic fun (Lhep/dataforge/context/Context;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lhep/dataforge/context/Context; public final fun getName ()Ljava/lang/String; - public final fun plugin (Lhep/dataforge/context/Plugin;)V public final fun plugin (Lhep/dataforge/context/PluginFactory;Lkotlin/jvm/functions/Function1;)V public final fun plugin (Lhep/dataforge/context/PluginTag;Lkotlin/jvm/functions/Function1;)V public final fun plugin (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V @@ -83,6 +73,11 @@ public final class hep/dataforge/context/ContextBuilder { public final fun setName (Ljava/lang/String;)V } +public final class hep/dataforge/context/ContextKt { + public static final fun Context (Ljava/lang/String;Lhep/dataforge/context/Context;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/context/Context; + public static synthetic fun Context$default (Ljava/lang/String;Lhep/dataforge/context/Context;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/context/Context; +} + public abstract interface class hep/dataforge/context/Factory { public abstract fun invoke (Lhep/dataforge/meta/Meta;Lhep/dataforge/context/Context;)Ljava/lang/Object; } @@ -100,24 +95,16 @@ public final class hep/dataforge/context/Global : hep/dataforge/context/Context public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; } -public abstract interface class hep/dataforge/context/Named { - public static final field Companion Lhep/dataforge/context/Named$Companion; - public abstract fun getName ()Lhep/dataforge/names/Name; +public final class hep/dataforge/context/LoggingKt { + public static final fun getLogger (Lhep/dataforge/context/Context;)Lmu/KLogger; + public static final fun getLogger (Lhep/dataforge/context/ContextAware;)Lmu/KLogger; } -public final class hep/dataforge/context/Named$Companion { - public final fun nameOf (Ljava/lang/Object;)Lhep/dataforge/names/Name; -} - -public final class hep/dataforge/context/NamedKt { - public static final fun isAnonymous (Lhep/dataforge/context/Named;)Z -} - -public abstract interface class hep/dataforge/context/Plugin : hep/dataforge/context/ContextAware, hep/dataforge/context/Named, hep/dataforge/meta/MetaRepr, hep/dataforge/provider/Provider { +public abstract interface class hep/dataforge/context/Plugin : hep/dataforge/context/ContextAware, hep/dataforge/meta/MetaRepr, hep/dataforge/misc/Named, hep/dataforge/provider/Provider { public static final field Companion Lhep/dataforge/context/Plugin$Companion; public static final field TARGET Ljava/lang/String; public abstract fun attach (Lhep/dataforge/context/Context;)V - public abstract fun dependsOn ()Ljava/util/Collection; + public abstract fun dependsOn ()Ljava/util/Map; public abstract fun detach ()V public abstract fun getMeta ()Lhep/dataforge/meta/Meta; public abstract fun getName ()Lhep/dataforge/names/Name; @@ -133,7 +120,6 @@ public final class hep/dataforge/context/Plugin$DefaultImpls { public static fun content (Lhep/dataforge/context/Plugin;Ljava/lang/String;)Ljava/util/Map; public static fun getDefaultChainTarget (Lhep/dataforge/context/Plugin;)Ljava/lang/String; public static fun getDefaultTarget (Lhep/dataforge/context/Plugin;)Ljava/lang/String; - public static fun getLogger (Lhep/dataforge/context/Plugin;)Lmu/KLogger; public static fun getName (Lhep/dataforge/context/Plugin;)Lhep/dataforge/names/Name; public static fun toMeta (Lhep/dataforge/context/Plugin;)Lhep/dataforge/meta/Meta; } @@ -150,10 +136,10 @@ public final class hep/dataforge/context/PluginFactory$Companion { } public final class hep/dataforge/context/PluginManager : hep/dataforge/context/ContextAware, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { - public fun (Lhep/dataforge/context/Context;Ljava/util/Set;)V - public final fun fetch (Lhep/dataforge/context/PluginFactory;ZLhep/dataforge/meta/Meta;)Lhep/dataforge/context/Plugin; + public fun (Lhep/dataforge/context/Context;)V + public final fun fetch (Lhep/dataforge/context/PluginFactory;Lhep/dataforge/meta/Meta;Z)Lhep/dataforge/context/Plugin; public final fun fetch (Lhep/dataforge/context/PluginFactory;ZLkotlin/jvm/functions/Function1;)Lhep/dataforge/context/Plugin; - public static synthetic fun fetch$default (Lhep/dataforge/context/PluginManager;Lhep/dataforge/context/PluginFactory;ZLhep/dataforge/meta/Meta;ILjava/lang/Object;)Lhep/dataforge/context/Plugin; + public static synthetic fun fetch$default (Lhep/dataforge/context/PluginManager;Lhep/dataforge/context/PluginFactory;Lhep/dataforge/meta/Meta;ZILjava/lang/Object;)Lhep/dataforge/context/Plugin; public static synthetic fun fetch$default (Lhep/dataforge/context/PluginManager;Lhep/dataforge/context/PluginFactory;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/context/Plugin; public final fun find (ZLkotlin/jvm/functions/Function1;)Lhep/dataforge/context/Plugin; public static synthetic fun find$default (Lhep/dataforge/context/PluginManager;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/context/Plugin; @@ -162,7 +148,6 @@ public final class hep/dataforge/context/PluginManager : hep/dataforge/context/C public static synthetic fun get$default (Lhep/dataforge/context/PluginManager;Lhep/dataforge/context/PluginTag;ZILjava/lang/Object;)Lhep/dataforge/context/Plugin; public static synthetic fun get$default (Lhep/dataforge/context/PluginManager;Lkotlin/reflect/KClass;Lhep/dataforge/context/PluginTag;ZILjava/lang/Object;)Ljava/lang/Object; public fun getContext ()Lhep/dataforge/context/Context; - public fun getLogger ()Lmu/KLogger; public fun iterator ()Ljava/util/Iterator; public final fun list (Z)Ljava/util/Collection; public final fun load (Lhep/dataforge/context/Plugin;)Lhep/dataforge/context/Plugin; @@ -268,9 +253,6 @@ public final class hep/dataforge/provider/Path : java/lang/Iterable, kotlin/jvm/ public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (Ljava/util/List;Ljava/lang/Object;)Z public static final fun equals-impl0 (Ljava/util/List;Ljava/util/List;)Z - public static final fun getHead-impl (Ljava/util/List;)Lhep/dataforge/provider/PathToken; - public static final fun getLength-impl (Ljava/util/List;)I - public static final fun getTail-e2ET3QM (Ljava/util/List;)Ljava/util/List; public final fun getTokens ()Ljava/util/List; public fun hashCode ()I public static fun hashCode-impl (Ljava/util/List;)I @@ -282,12 +264,19 @@ public final class hep/dataforge/provider/Path : java/lang/Iterable, kotlin/jvm/ } public final class hep/dataforge/provider/Path$Companion { - public final fun parse-IN54j3k (Ljava/lang/String;)Ljava/util/List; + public final fun parse-AnEnhig (Ljava/lang/String;)Ljava/util/List; } public final class hep/dataforge/provider/PathKt { - public static final fun plus-MQiGgVU (Ljava/util/List;Ljava/util/List;)Ljava/util/List; - public static final fun toPath (Lhep/dataforge/provider/PathToken;)Ljava/util/List; + public static final fun Path ([Lhep/dataforge/names/Name;)Ljava/util/List; + public static final fun Path ([Lkotlin/Pair;)Ljava/util/List; + public static final fun asPath (Lhep/dataforge/names/Name;Ljava/lang/String;)Ljava/util/List; + public static final fun asPath (Lhep/dataforge/provider/PathToken;)Ljava/util/List; + public static synthetic fun asPath$default (Lhep/dataforge/names/Name;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/List; + public static final fun getHead-Ipm_iiw (Ljava/util/List;)Lhep/dataforge/provider/PathToken; + public static final fun getLength-Ipm_iiw (Ljava/util/List;)I + public static final fun getTail-Ipm_iiw (Ljava/util/List;)Ljava/util/List; + public static final fun plus-BlTXZnM (Ljava/util/List;Ljava/util/List;)Ljava/util/List; } public final class hep/dataforge/provider/PathToken { @@ -323,12 +312,8 @@ public final class hep/dataforge/provider/Provider$DefaultImpls { } public final class hep/dataforge/provider/ProviderKt { - public static final fun provide-0Dbucg0 (Lhep/dataforge/provider/Provider;Ljava/util/List;Ljava/lang/String;)Ljava/lang/Object; - public static synthetic fun provide-0Dbucg0$default (Lhep/dataforge/provider/Provider;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun provide-mzxrFLw (Lhep/dataforge/provider/Provider;Ljava/util/List;Ljava/lang/String;)Ljava/lang/Object; + public static synthetic fun provide-mzxrFLw$default (Lhep/dataforge/provider/Provider;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Object; public static final fun top (Lhep/dataforge/provider/Provider;Ljava/lang/String;Lkotlin/reflect/KClass;)Ljava/util/Map; } -public abstract interface annotation class hep/dataforge/provider/Type : java/lang/annotation/Annotation { - public abstract fun id ()Ljava/lang/String; -} - diff --git a/dataforge-context/build.gradle.kts b/dataforge-context/build.gradle.kts index 01a7e3bb..84785ce8 100644 --- a/dataforge-context/build.gradle.kts +++ b/dataforge-context/build.gradle.kts @@ -28,4 +28,8 @@ kotlin { } } } +} + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT } \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/AbstractPlugin.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/AbstractPlugin.kt index f64c1042..a7a310cb 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/AbstractPlugin.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/AbstractPlugin.kt @@ -1,6 +1,7 @@ package hep.dataforge.context import hep.dataforge.meta.Meta +import hep.dataforge.misc.Named import hep.dataforge.names.Name import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass @@ -8,7 +9,7 @@ import kotlin.reflect.KProperty public abstract class AbstractPlugin(override val meta: Meta = Meta.EMPTY) : Plugin { private var _context: Context? = null - private val dependencies = ArrayList>() + private val dependencies = HashMap, Meta>() override val context: Context get() = _context ?: error("Plugin $tag is not attached") @@ -21,13 +22,13 @@ public abstract class AbstractPlugin(override val meta: Meta = Meta.EMPTY) : Plu this._context = null } - final override fun dependsOn(): List> = dependencies + final override fun dependsOn(): Map, Meta> = dependencies /** * Register plugin dependency and return a delegate which provides lazily initialized reference to dependent plugin */ - protected fun

require(factory: PluginFactory

): ReadOnlyProperty { - dependencies.add(factory) + protected fun

require(factory: PluginFactory

, meta: Meta = Meta.EMPTY): ReadOnlyProperty { + dependencies[factory] = meta return PluginDependencyDelegate(factory.type) } } diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt index 605e6f8b..b701d3d6 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt @@ -3,15 +3,13 @@ package hep.dataforge.context import hep.dataforge.meta.Laminate import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaRepr -import hep.dataforge.meta.sequence +import hep.dataforge.meta.itemSequence +import hep.dataforge.misc.Named import hep.dataforge.names.Name -import hep.dataforge.names.plus import hep.dataforge.provider.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import mu.KLogger -import mu.KotlinLogging import kotlin.coroutines.CoroutineContext /** @@ -24,11 +22,10 @@ import kotlin.coroutines.CoroutineContext * be overridden by plugin implementation. * */ -public open class Context( +public open class Context internal constructor( final override val name: Name, public val parent: Context?, meta: Meta, - plugins: Set = emptySet(), ) : Named, MetaRepr, Provider, CoroutineScope { /** @@ -40,28 +37,23 @@ public open class Context( Laminate(meta, parent.properties) } - /** - * Context logger - */ - public val logger: KLogger = KotlinLogging.logger(name.toString()) - /** * A [PluginManager] for current context */ - public val plugins: PluginManager by lazy { PluginManager(this, plugins)} + public val plugins: PluginManager by lazy { PluginManager(this) } override val defaultTarget: String get() = Plugin.TARGET public fun content(target: String, inherit: Boolean): Map { return if (inherit) { when (target) { - PROPERTY_TARGET -> properties.sequence().toMap() + PROPERTY_TARGET -> properties.itemSequence().toMap() Plugin.TARGET -> plugins.list(true).associateBy { it.name } else -> emptyMap() } } else { when (target) { - PROPERTY_TARGET -> properties.layers.firstOrNull()?.sequence()?.toMap() ?: emptyMap() + PROPERTY_TARGET -> properties.layers.firstOrNull()?.itemSequence()?.toMap() ?: emptyMap() Plugin.TARGET -> plugins.list(false).associateBy { it.name } else -> emptyMap() } @@ -95,6 +87,9 @@ public open class Context( } } +public fun Context(name: String, parent: Context = Global, block: ContextBuilder.() -> Unit = {}): Context = + Global.context(name, parent, block) + /** * The interface for something that encapsulated in context * @@ -106,12 +101,4 @@ public interface ContextAware { * @return */ public val context: Context - - public val logger: KLogger - get() = if (this is Named) { - KotlinLogging.logger((context.name + this.name).toString()) - } else { - context.logger - } - } \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt index b742474c..f124d3d2 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt @@ -1,24 +1,29 @@ package hep.dataforge.context -import hep.dataforge.meta.* +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.seal +import hep.dataforge.misc.DFBuilder +import hep.dataforge.misc.DFExperimental import hep.dataforge.names.toName +import kotlin.collections.HashMap +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.forEach +import kotlin.collections.set /** * A convenience builder for context */ @DFBuilder public class ContextBuilder(private val parent: Context = Global, public var name: String = "@anonymous") { - private val plugins = HashSet() + private val factories = HashMap, Meta>() private var meta = MetaBuilder() public fun properties(action: MetaBuilder.() -> Unit) { meta.action() } - public fun plugin(plugin: Plugin) { - plugins.add(plugin) - } - @OptIn(DFExperimental::class) private fun findPluginFactory(tag: PluginTag): PluginFactory<*> = parent.gatherInSequence>(PluginFactory.TYPE).values @@ -26,12 +31,11 @@ public class ContextBuilder(private val parent: Context = Global, public var nam public fun plugin(tag: PluginTag, metaBuilder: MetaBuilder.() -> Unit = {}) { val factory = findPluginFactory(tag) - val plugin = factory.invoke(Meta(metaBuilder), parent) - plugins.add(plugin) + factories[factory] = Meta(metaBuilder) } - public fun plugin(builder: PluginFactory<*>, action: MetaBuilder.() -> Unit = {}) { - plugins.add(builder.invoke(Meta(action))) + public fun plugin(factory: PluginFactory<*>, metaBuilder: MetaBuilder.() -> Unit = {}) { + factories[factory] = Meta(metaBuilder) } public fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) { @@ -39,6 +43,10 @@ public class ContextBuilder(private val parent: Context = Global, public var nam } public fun build(): Context { - return Context(name.toName(), parent, meta.seal(), plugins) + return Context(name.toName(), parent, meta.seal()).apply { + factories.forEach { (factory, meta) -> + plugins.load(factory, meta) + } + } } } \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt index ff0e3a0c..3d4aea3d 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt @@ -3,10 +3,11 @@ package hep.dataforge.context import hep.dataforge.context.Plugin.Companion.TARGET import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaRepr +import hep.dataforge.misc.Named +import hep.dataforge.misc.Type import hep.dataforge.names.Name import hep.dataforge.names.toName import hep.dataforge.provider.Provider -import hep.dataforge.provider.Type /** * The interface to define a Context plugin. A plugin stores all runtime features of a context. @@ -37,7 +38,7 @@ public interface Plugin : Named, ContextAware, Provider, MetaRepr { * dependencies must be initialized and enabled in the Context before this * plugin is enabled. */ - public fun dependsOn(): Collection> + public fun dependsOn(): Map, Meta> /** * Start this plugin and attach registration info to the context. This method diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginFactory.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginFactory.kt new file mode 100644 index 00000000..18e40fed --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginFactory.kt @@ -0,0 +1,14 @@ +package hep.dataforge.context + +import hep.dataforge.misc.Type +import kotlin.reflect.KClass + +@Type(PluginFactory.TYPE) +public interface PluginFactory : Factory { + public val tag: PluginTag + public val type: KClass + + public companion object { + public const val TYPE: String = "pluginFactory" + } +} diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt index 3e9def85..4854b328 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt @@ -2,34 +2,23 @@ package hep.dataforge.context import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder -import hep.dataforge.provider.Type import kotlin.reflect.KClass -@Type(PluginFactory.TYPE) -public interface PluginFactory : Factory { - public val tag: PluginTag - public val type: KClass - - public companion object { - public const val TYPE: String = "pluginFactory" - } -} - /** * The manager for plugin system. Should monitor plugin dependencies and locks. * * @property context A context for this plugin manager * @author Alexander Nozik */ -public class PluginManager(override val context: Context, plugins: Set) : ContextAware, Iterable { +public class PluginManager(override val context: Context) : ContextAware, Iterable { //TODO refactor to read-only container /** * A set of loaded plugins */ - private val plugins: HashSet = HashSet(plugins) + private val plugins: HashSet = HashSet() init { plugins.forEach { it.attach(context) } @@ -100,8 +89,8 @@ public class PluginManager(override val context: Context, plugins: Set) if (get(plugin::class, plugin.tag, recursive = false) != null) { error("Plugin with tag ${plugin.tag} already exists in ${context.name}") } else { - for (tag in plugin.dependsOn()) { - fetch(tag, true) + for ((factory, meta) in plugin.dependsOn()) { + fetch(factory, meta, true) } logger.info { "Loading plugin ${plugin.name} into ${context.name}" } @@ -134,7 +123,7 @@ public class PluginManager(override val context: Context, plugins: Set) /** * Get an existing plugin with given meta or load new one using provided factory */ - public fun fetch(factory: PluginFactory, recursive: Boolean = true, meta: Meta = Meta.EMPTY): T { + public fun fetch(factory: PluginFactory, meta: Meta = Meta.EMPTY, recursive: Boolean = true): T { val loaded = get(factory.type, factory.tag, recursive) return when { loaded == null -> load(factory(meta, context)) @@ -147,7 +136,7 @@ public class PluginManager(override val context: Context, plugins: Set) factory: PluginFactory, recursive: Boolean = true, metaBuilder: MetaBuilder.() -> Unit, - ): T = fetch(factory, recursive, Meta(metaBuilder)) + ): T = fetch(factory, Meta(metaBuilder), recursive) override fun iterator(): Iterator = plugins.iterator() diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/logging.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/logging.kt new file mode 100644 index 00000000..c43f7ea2 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/logging.kt @@ -0,0 +1,22 @@ +package hep.dataforge.context + +import hep.dataforge.misc.Named +import hep.dataforge.provider.Path +import mu.KLogger +import mu.KotlinLogging + +/** + * The logger specific to this context + */ +public val Context.logger: KLogger get() = KotlinLogging.logger(name.toString()) + +/** + * The logger + */ +public val ContextAware.logger: KLogger + get() = if (this is Named) { + KotlinLogging.logger(Path(context.name, this.name).toString()) + } else { + context.logger + } + diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt index 811220af..b41e11fd 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/resolve.kt @@ -1,6 +1,6 @@ package hep.dataforge.context -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import hep.dataforge.names.Name import hep.dataforge.names.plus import hep.dataforge.provider.Provider @@ -48,8 +48,9 @@ public fun Context.gather( putAll(top(target, type)) plugins.forEach { plugin -> plugin.top(target, type).forEach { (name, value) -> - if (containsKey(name)) error("Name conflict during gather. An item with name $name could not be gathered from $plugin because key is already present.") - put(plugin.name + name, value) + val itemName = plugin.name + name + if (containsKey(itemName)) error("Name conflict during gather. An item with name $name could not be gathered from $plugin because key is already present.") + put(itemName, value) } } if (inherit) { diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt index 329662b5..86c6bcde 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/ConfigProperty.kt @@ -1,11 +1,12 @@ package hep.dataforge.properties import hep.dataforge.meta.Config -import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.get +import hep.dataforge.meta.set import hep.dataforge.meta.transformations.MetaConverter import hep.dataforge.meta.transformations.nullableItemToObject import hep.dataforge.meta.transformations.nullableObjectToMetaItem +import hep.dataforge.misc.DFExperimental import hep.dataforge.names.Name @DFExperimental @@ -18,7 +19,7 @@ public class ConfigProperty( override var value: T? get() = converter.nullableItemToObject(config[name]) set(value) { - config.setItem(name,converter.nullableObjectToMetaItem(value)) + config[name] = converter.nullableObjectToMetaItem(value) } override fun onChange(owner: Any?, callback: (T?) -> Unit) { diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt index 987cfe4c..44e97ea9 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/properties/Property.kt @@ -1,6 +1,6 @@ package hep.dataforge.properties -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt index 6cd0d7fa..149a9ece 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt @@ -19,39 +19,39 @@ import hep.dataforge.names.Name import hep.dataforge.names.toName /** - * - * * Path interface. * - * @author Alexander Nozik - * @version $Id: $Id */ public inline class Path(public val tokens: List) : Iterable { - public val head: PathToken? get() = tokens.firstOrNull() - - public val length: Int get() = tokens.size - - /** - * Returns non-empty optional containing the chain without first segment in case of chain path. - * - * @return - */ - public val tail: Path? get() = if (tokens.isEmpty()) null else Path(tokens.drop(1)) - override fun iterator(): Iterator = tokens.iterator() + override fun toString(): String = tokens.joinToString(separator = PATH_SEGMENT_SEPARATOR) + 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).toPath() + parse(tail) + return PathToken.parse(head).asPath() + parse(tail) } } } +public val Path.length: Int get() = tokens.size + +public val Path.head: PathToken? get() = tokens.firstOrNull() + + +/** + * Returns non-empty optional containing the chain without first segment in case of chain path. + * + * @return + */ +public val Path.tail: Path? get() = if (tokens.isEmpty()) null else Path(tokens.drop(1)) + + public operator fun Path.plus(path: Path): Path = Path(this.tokens + path.tokens) public data class PathToken(val name: Name, val target: String? = null) { @@ -72,4 +72,22 @@ public data class PathToken(val name: Name, val target: String? = null) { } } -public fun PathToken.toPath(): Path = Path(listOf(this)) +/** + * Represent this path token as full path + */ +public fun PathToken.asPath(): Path = Path(listOf(this)) + +/** + * Represent a name with optional [target] as a [Path] + */ +public fun Name.asPath(target: String? = null): Path = PathToken(this, target).asPath() + +/** + * Build a path from given names using default targets + */ +public fun Path(vararg names: Name): Path = Path(names.map { PathToken(it) }) + +/** + * Use an array of [Name]-target pairs to construct segmented [Path] + */ +public fun Path(vararg tokens: Pair): Path = Path(tokens.map { PathToken(it.first, it.second) }) \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt index 3a153d86..8d3ae30d 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt @@ -84,6 +84,6 @@ public fun Provider.top(target: String, type: KClass): Map Provider.top(target: String): Map = top(target, T::class) +public inline fun Provider.top(target: String ): Map = top(target, T::class) diff --git a/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt b/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt index 58647400..3d15e8b3 100644 --- a/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt +++ b/dataforge-context/src/commonTest/kotlin/hep/dataforge/context/ContextTest.kt @@ -17,13 +17,13 @@ class ContextTest { else -> emptyMap() } } + } @Test fun testPluginManager() { - val context = Global.context("test"){ - plugin(DummyPlugin()) - } + val context = Global.context("test") + context.plugins.load(DummyPlugin()) //Global.plugins.load(DummyPlugin()) val members = context.gather("test") assertEquals(3, members.count()) diff --git a/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt b/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt index 05818cae..86296e39 100644 --- a/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt +++ b/dataforge-context/src/jsMain/kotlin/hep/dataforge/properties/bindings.kt @@ -1,10 +1,10 @@ package hep.dataforge.properties -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import org.w3c.dom.HTMLInputElement @DFExperimental -fun HTMLInputElement.bindValue(property: Property) { +public fun HTMLInputElement.bindValue(property: Property) { if (this.onchange != null) error("Input element already bound") this.onchange = { property.value = this.value @@ -18,7 +18,7 @@ fun HTMLInputElement.bindValue(property: Property) { } @DFExperimental -fun HTMLInputElement.bindChecked(property: Property) { +public fun HTMLInputElement.bindChecked(property: Property) { if (this.onchange != null) error("Input element already bound") this.onchange = { property.value = this.checked diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt index cadd4231..f88cee99 100644 --- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt @@ -16,7 +16,6 @@ package hep.dataforge.descriptors -import hep.dataforge.meta.DFExperimental import hep.dataforge.values.ValueType import kotlin.reflect.KClass diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt index 9c08d6a8..6f7855a9 100644 --- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/dfType.kt @@ -2,7 +2,8 @@ package hep.dataforge.provider import hep.dataforge.context.Context import hep.dataforge.context.gather -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental +import hep.dataforge.misc.Type import hep.dataforge.names.Name import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation diff --git a/dataforge-data/api/dataforge-data.api b/dataforge-data/api/dataforge-data.api index 06e99242..709a8038 100644 --- a/dataforge-data/api/dataforge-data.api +++ b/dataforge-data/api/dataforge-data.api @@ -26,6 +26,24 @@ public final class hep/dataforge/data/ActionKt { public static final fun then (Lhep/dataforge/data/Action;Lhep/dataforge/data/Action;)Lhep/dataforge/data/Action; } +public final class hep/dataforge/data/ComputationData : hep/dataforge/data/ComputationGoal, hep/dataforge/data/Data { + public fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getMeta ()Lhep/dataforge/meta/Meta; + public fun getType ()Lkotlin/reflect/KClass; + public fun toMeta ()Lhep/dataforge/meta/Meta; +} + +public class hep/dataforge/data/ComputationGoal : hep/dataforge/data/Goal { + public fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getBlock ()Lkotlin/jvm/functions/Function2; + public fun getDependencies ()Ljava/util/Collection; + public final fun getResult ()Lkotlinx/coroutines/Deferred; + public fun reset ()V + public fun startAsync (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Deferred; +} + public final class hep/dataforge/data/CoroutineMonitor : kotlin/coroutines/CoroutineContext$Element { public static final field Companion Lhep/dataforge/data/CoroutineMonitor$Companion; public fun ()V @@ -79,14 +97,6 @@ public final class hep/dataforge/data/Data$DefaultImpls { public static fun toMeta (Lhep/dataforge/data/Data;)Lhep/dataforge/meta/Meta; } -public final class hep/dataforge/data/DataCastKt { - public static final fun canCast (Lhep/dataforge/data/DataItem;Lkotlin/reflect/KClass;)Z - public static final fun cast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data; - public static final fun cast (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataNode; - public static final fun ensureType (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)V - public static final fun upcast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data; -} - public final class hep/dataforge/data/DataFilter : hep/dataforge/meta/Scheme { public static final field Companion Lhep/dataforge/data/DataFilter$Companion; public fun ()V @@ -129,17 +139,22 @@ public final class hep/dataforge/data/DataItem$Node : hep/dataforge/data/DataIte } public final class hep/dataforge/data/DataJVMKt { + public static final fun canCast (Lhep/dataforge/data/DataItem;Lkotlin/reflect/KClass;)Z + public static final fun cast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data; + public static final fun cast (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataNode; + public static final fun ensureType (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)V public static final fun filterIsInstance (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data; public static final fun filterIsInstance (Lhep/dataforge/data/DataItem;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataItem; public static final fun filterIsInstance (Lhep/dataforge/data/DataNode;Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataNode; public static final fun get (Lhep/dataforge/data/Data;)Ljava/lang/Object; + public static final fun upcast (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;)Lhep/dataforge/data/Data; } public final class hep/dataforge/data/DataKt { public static final fun map (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/data/Data; public static synthetic fun map$default (Lhep/dataforge/data/Data;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/data/Data; - public static final fun reduce (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/data/DynamicData; - public static synthetic fun reduce$default (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/data/DynamicData; + public static final fun reduce (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/data/ComputationData; + public static synthetic fun reduce$default (Ljava/util/Map;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/data/ComputationData; } public abstract interface class hep/dataforge/data/DataNode : hep/dataforge/meta/MetaRepr { @@ -148,50 +163,36 @@ public abstract interface class hep/dataforge/data/DataNode : hep/dataforge/meta public abstract fun getItems ()Ljava/util/Map; public abstract fun getMeta ()Lhep/dataforge/meta/Meta; public abstract fun getType ()Lkotlin/reflect/KClass; - public abstract fun startAll (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job; public abstract fun toMeta ()Lhep/dataforge/meta/Meta; } public final class hep/dataforge/data/DataNode$Companion { public static final field TYPE Ljava/lang/String; public final fun builder (Lkotlin/reflect/KClass;)Lhep/dataforge/data/DataTreeBuilder; - public final fun invoke (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/data/DataTree; } public final class hep/dataforge/data/DataNode$DefaultImpls { - public static fun startAll (Lhep/dataforge/data/DataNode;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job; public static fun toMeta (Lhep/dataforge/data/DataNode;)Lhep/dataforge/meta/Meta; } public final class hep/dataforge/data/DataNodeKt { - public static final fun asSequence (Lhep/dataforge/data/DataNode;)Lkotlin/sequences/Sequence; - public static final fun builder (Lhep/dataforge/data/DataNode;)Lhep/dataforge/data/DataTreeBuilder; public static final fun dataSequence (Lhep/dataforge/data/DataNode;)Lkotlin/sequences/Sequence; - public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/Data;)V - public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/Data;)V public static final fun filter (Lhep/dataforge/data/DataNode;Lkotlin/jvm/functions/Function2;)Lhep/dataforge/data/DataNode; public static final fun first (Lhep/dataforge/data/DataNode;)Lhep/dataforge/data/Data; public static final fun get (Lhep/dataforge/data/DataNode;Lhep/dataforge/names/Name;)Lhep/dataforge/data/DataItem; public static final fun get (Lhep/dataforge/data/DataNode;Ljava/lang/String;)Lhep/dataforge/data/DataItem; public static final fun getData (Lhep/dataforge/data/DataItem;)Lhep/dataforge/data/Data; public static final fun getNode (Lhep/dataforge/data/DataItem;)Lhep/dataforge/data/DataNode; + public static final fun itemSequence (Lhep/dataforge/data/DataNode;)Lkotlin/sequences/Sequence; public static final fun iterator (Lhep/dataforge/data/DataNode;)Ljava/util/Iterator; public static final fun join (Lhep/dataforge/data/DataNode;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/DataNode;)V - public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/DataNode;)V - public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;)V - public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V - public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V - public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)V - public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static final fun startAll (Lhep/dataforge/data/DataNode;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job; } public final class hep/dataforge/data/DataTree : hep/dataforge/data/DataNode { public fun getItems ()Ljava/util/Map; public fun getMeta ()Lhep/dataforge/meta/Meta; public fun getType ()Lkotlin/reflect/KClass; - public fun startAll (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job; public fun toMeta ()Lhep/dataforge/meta/Meta; } @@ -214,6 +215,21 @@ public final class hep/dataforge/data/DataTreeBuilder { public final fun update (Lhep/dataforge/data/DataNode;)V } +public final class hep/dataforge/data/DataTreeBuilderKt { + public static final fun DataTree (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/data/DataTree; + public static final fun builder (Lhep/dataforge/data/DataNode;)Lhep/dataforge/data/DataTreeBuilder; + public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/Data;)V + public static final fun datum (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/Data;)V + public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Lhep/dataforge/data/DataNode;)V + public static final fun node (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Lhep/dataforge/data/DataNode;)V + public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;)V + public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V + public static final fun static (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)V + public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Lhep/dataforge/names/Name;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public static synthetic fun static$default (Lhep/dataforge/data/DataTreeBuilder;Ljava/lang/String;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V +} + public final class hep/dataforge/data/Dependencies : kotlin/coroutines/CoroutineContext$Element { public static final field Companion Lhep/dataforge/data/Dependencies$Companion; public fun (Ljava/util/Collection;)V @@ -228,24 +244,6 @@ public final class hep/dataforge/data/Dependencies : kotlin/coroutines/Coroutine public final class hep/dataforge/data/Dependencies$Companion : kotlin/coroutines/CoroutineContext$Key { } -public final class hep/dataforge/data/DynamicData : hep/dataforge/data/DynamicGoal, hep/dataforge/data/Data { - public fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V - public synthetic fun (Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getType ()Lkotlin/reflect/KClass; - public fun toMeta ()Lhep/dataforge/meta/Meta; -} - -public class hep/dataforge/data/DynamicGoal : hep/dataforge/data/Goal { - public fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;)V - public synthetic fun (Lkotlin/coroutines/CoroutineContext;Ljava/util/Collection;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getBlock ()Lkotlin/jvm/functions/Function2; - public fun getDependencies ()Ljava/util/Collection; - public final fun getResult ()Lkotlinx/coroutines/Deferred; - public fun reset ()V - public fun startAsync (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Deferred; -} - public final class hep/dataforge/data/FragmentRule { public field result Lkotlin/jvm/functions/Function2; public fun (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaBuilder;)V @@ -302,9 +300,7 @@ public final class hep/dataforge/data/JoinGroup { } public final class hep/dataforge/data/MapAction : hep/dataforge/data/Action { - public fun (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V - public final fun getInputType ()Lkotlin/reflect/KClass; - public final fun getOutputType ()Lkotlin/reflect/KClass; + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V public fun invoke (Lhep/dataforge/data/DataNode;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode; public fun isTerminal ()Z } @@ -335,9 +331,7 @@ public final class hep/dataforge/data/NamedData : hep/dataforge/data/Data { } public final class hep/dataforge/data/ReduceAction : hep/dataforge/data/Action { - public fun (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V - public final fun getInputType ()Lkotlin/reflect/KClass; - public final fun getOutputType ()Lkotlin/reflect/KClass; + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V public fun invoke (Lhep/dataforge/data/DataNode;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode; public fun isTerminal ()Z } @@ -357,9 +351,7 @@ public final class hep/dataforge/data/ReduceGroupBuilder { } public final class hep/dataforge/data/SplitAction : hep/dataforge/data/Action { - public fun (Lkotlin/reflect/KClass;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V - public final fun getInputType ()Lkotlin/reflect/KClass; - public final fun getOutputType ()Lkotlin/reflect/KClass; + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)V public fun invoke (Lhep/dataforge/data/DataNode;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode; public fun isTerminal ()Z } @@ -394,7 +386,6 @@ public final class hep/dataforge/data/TypeFilteredDataNode : hep/dataforge/data/ public fun getMeta ()Lhep/dataforge/meta/Meta; public final fun getOrigin ()Lhep/dataforge/data/DataNode; public fun getType ()Lkotlin/reflect/KClass; - public fun startAll (Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job; public fun toMeta ()Lhep/dataforge/meta/Meta; } diff --git a/dataforge-data/build.gradle.kts b/dataforge-data/build.gradle.kts index 436d9428..16d98ced 100644 --- a/dataforge-data/build.gradle.kts +++ b/dataforge-data/build.gradle.kts @@ -12,12 +12,12 @@ kotlin { commonMain{ dependencies { api(project(":dataforge-meta")) - } - } - jvmMain{ - dependencies{ api(kotlin("reflect")) } } } -} \ No newline at end of file +} + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL +} diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/Action.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/Action.kt new file mode 100644 index 00000000..85ec2977 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/Action.kt @@ -0,0 +1,41 @@ +package hep.dataforge.actions + +import hep.dataforge.data.DataSet +import hep.dataforge.meta.Meta +import hep.dataforge.misc.DFExperimental +import kotlinx.coroutines.CoroutineScope + +/** + * A simple data transformation on a data node. Actions should avoid doing actual dependency evaluation in [execute]. + */ +public interface Action { + /** + * Transform the data in the node, producing a new node. By default it is assumed that all calculations are lazy + * so not actual computation is started at this moment. + * + * [scope] context used to compute the initial result, also it is used for updates propagation + */ + public suspend fun execute(dataSet: DataSet, meta: Meta = Meta.EMPTY, scope: CoroutineScope? = null): DataSet + + public companion object +} + +/** + * Action composition. The result is terminal if one of its parts is terminal + */ +public infix fun Action.then(action: Action): Action { + // TODO introduce composite action and add optimize by adding action to the list + return object : Action { + override suspend fun execute(dataSet: DataSet, meta: Meta, scope: CoroutineScope?): DataSet { + return action.execute(this@then.execute(dataSet, meta, scope), meta, scope) + } + } +} + +@DFExperimental +public suspend fun DataSet.transformWith( + action: Action, + meta: Meta = Meta.EMPTY, + scope: CoroutineScope? = null, +): DataSet = action.execute(this, meta, scope) + diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/MapAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/MapAction.kt new file mode 100644 index 00000000..23731621 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/MapAction.kt @@ -0,0 +1,104 @@ +package hep.dataforge.actions + +import hep.dataforge.data.* +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.seal +import hep.dataforge.meta.toMutableMeta +import hep.dataforge.misc.DFBuilder +import hep.dataforge.misc.DFExperimental +import hep.dataforge.misc.DFInternal +import hep.dataforge.names.Name +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +/** + * Action environment includes data name, data meta and action configuration meta + */ +public data class ActionEnv( + val name: Name, + val meta: Meta, + val actionMeta: Meta, +) + +/** + * Action environment + */ +@DFBuilder +public class MapActionBuilder(public var name: Name, public var meta: MetaBuilder, public val actionMeta: Meta) { + public lateinit var result: suspend ActionEnv.(T) -> R + + /** + * Calculate the result of goal + */ + public fun result(f: suspend ActionEnv.(T) -> R) { + result = f; + } +} + +@PublishedApi +internal class MapAction( + private val outputType: KType, + private val block: MapActionBuilder.() -> Unit, +) : Action { + + override suspend fun execute( + dataSet: DataSet, + meta: Meta, + scope: CoroutineScope?, + ): DataSet { + suspend fun mapOne(data: NamedData): NamedData { + // Creating a new environment for action using **old** name, old meta and task meta + val env = ActionEnv(data.name, data.meta, meta) + + //applying transformation from builder + val builder = MapActionBuilder( + data.name, + data.meta.toMutableMeta(), // using data meta + meta + ).apply(block) + + //getting new name + val newName = builder.name + + //getting new meta + val newMeta = builder.meta.seal() + + @OptIn(DFInternal::class) val newData = Data(outputType, newMeta, dependencies = listOf(data)) { + builder.result(env, data.await()) + } + //setting the data node + return newData.named(newName) + } + + val flow = dataSet.flow().map(::mapOne) + + return ActiveDataTree(outputType) { + populate(flow) + scope?.launch { + dataSet.updates.collect { name -> + //clear old nodes + remove(name) + //collect new items + populate(dataSet.flowChildren(name).map(::mapOne)) + } + } + } + } +} + + +/** + * A one-to-one mapping action + */ +@DFExperimental +@Suppress("FunctionName") +public inline fun Action.Companion.map( + noinline builder: MapActionBuilder.() -> Unit, +): Action = MapAction(typeOf(), builder) + + diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/ReduceAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/ReduceAction.kt new file mode 100644 index 00000000..8e2781b5 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/ReduceAction.kt @@ -0,0 +1,116 @@ +package hep.dataforge.actions + +import hep.dataforge.data.* +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.misc.DFBuilder +import hep.dataforge.misc.DFExperimental +import hep.dataforge.misc.DFInternal +import hep.dataforge.names.Name +import hep.dataforge.names.toName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.fold +import kotlin.reflect.KType +import kotlin.reflect.typeOf + + +public class JoinGroup(public var name: String, internal val set: DataSet) { + + public var meta: MetaBuilder = MetaBuilder() + + public lateinit var result: suspend ActionEnv.(Map) -> R + + public fun result(f: suspend ActionEnv.(Map) -> R) { + this.result = f; + } + +} + +@DFBuilder +public class ReduceGroupBuilder( + private val inputType: KType, + private val scope: CoroutineScope, + public val actionMeta: Meta, +) { + private val groupRules: MutableList) -> List>> = ArrayList(); + + /** + * introduce grouping by meta value + */ + public fun byValue(tag: String, defaultTag: String = "@default", action: JoinGroup.() -> Unit) { + groupRules += { node -> + GroupRule.byMetaValue(scope, tag, defaultTag).gather(node).map { + JoinGroup(it.key, it.value).apply(action) + } + } + } + + public fun group( + groupName: String, + filter: suspend (Name, Data) -> Boolean, + action: JoinGroup.() -> Unit, + ) { + groupRules += { source -> + listOf( + JoinGroup(groupName, source.filter(filter)).apply(action) + ) + } + } + + /** + * Apply transformation to the whole node + */ + public fun result(resultName: String, f: suspend ActionEnv.(Map) -> R) { + groupRules += { node -> + listOf(JoinGroup(resultName, node).apply { result(f) }) + } + } + + internal suspend fun buildGroups(input: DataSet): List> { + return groupRules.flatMap { it.invoke(input) } + } + +} + +@PublishedApi +internal class ReduceAction( + private val inputType: KType, + outputType: KType, + private val action: ReduceGroupBuilder.() -> Unit, +) : CachingAction(outputType) { + //TODO optimize reduction. Currently the whole action recalculates on push + + + override fun CoroutineScope.transform(set: DataSet, meta: Meta, key: Name): Flow> = flow { + ReduceGroupBuilder(inputType, this@transform, meta).apply(action).buildGroups(set).forEach { group -> + val dataFlow: Map> = group.set.flow().fold(HashMap()) { acc, value -> + acc.apply { + acc[value.name] = value.data + } + } + + val groupName: String = group.name + + val groupMeta = group.meta + + val env = ActionEnv(groupName.toName(), groupMeta, meta) + @OptIn(DFInternal::class) val res: Data = dataFlow.reduceToData( + outputType, + meta = groupMeta + ) { group.result.invoke(env, it) } + + emit(res.named(env.name)) + } + } +} + +/** + * A one-to-one mapping action + */ +@DFExperimental +@Suppress("FunctionName") +public inline fun Action.Companion.reduce( + noinline builder: ReduceGroupBuilder.() -> Unit, +): Action = ReduceAction(typeOf(), typeOf(), builder) diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/SplitAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/SplitAction.kt new file mode 100644 index 00000000..37b8f734 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/actions/SplitAction.kt @@ -0,0 +1,96 @@ +package hep.dataforge.actions + +import hep.dataforge.data.* +import hep.dataforge.meta.Laminate +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.toMutableMeta +import hep.dataforge.misc.DFExperimental +import hep.dataforge.misc.DFInternal +import hep.dataforge.names.Name +import hep.dataforge.names.toName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import kotlin.collections.set +import kotlin.reflect.KType +import kotlin.reflect.typeOf + + +public class SplitBuilder(public val name: Name, public val meta: Meta) { + + public class FragmentRule(public val name: Name, public var meta: MetaBuilder) { + public lateinit var result: suspend (T) -> R + + public fun result(f: suspend (T) -> R) { + result = f; + } + } + + internal val fragments: MutableMap.() -> Unit> = HashMap() + + /** + * Add new fragment building rule. If the framgent not defined, result won't be available even if it is present in the map + * @param name the name of a fragment + * @param rule the rule to transform fragment name and meta using + */ + public fun fragment(name: String, rule: FragmentRule.() -> Unit) { + fragments[name.toName()] = rule + } +} + +/** + * Action that splits each incoming element into a number of fragments defined in builder + */ +@PublishedApi +internal class SplitAction( + private val outputType: KType, + private val action: SplitBuilder.() -> Unit, +) : Action { + + @OptIn(FlowPreview::class) + override suspend fun execute( + dataSet: DataSet, + meta: Meta, + scope: CoroutineScope?, + ): DataSet { + + suspend fun splitOne(data: NamedData): Flow> { + val laminate = Laminate(data.meta, meta) + + val split = SplitBuilder(data.name, data.meta).apply(action) + + + // apply individual fragment rules to result + return split.fragments.entries.asFlow().map { (fragmentName, rule) -> + val env = SplitBuilder.FragmentRule(fragmentName, laminate.toMutableMeta()).apply(rule) + //data.map(outputType, meta = env.meta) { env.result(it) }.named(fragmentName) + @OptIn(DFInternal::class) Data(outputType, meta = env.meta, dependencies = listOf(data)) { + env.result(data.await()) + }.named(fragmentName) + } + } + + return ActiveDataTree(outputType) { + populate(dataSet.flow().flatMapConcat(transform = ::splitOne)) + scope?.launch { + dataSet.updates.collect { name -> + //clear old nodes + remove(name) + //collect new items + populate(dataSet.flowChildren(name).flatMapConcat(transform = ::splitOne)) + } + } + } + } +} + +/** + * Action that splits each incoming element into a number of fragments defined in builder + */ +@DFExperimental +@Suppress("FunctionName") +public inline fun Action.Companion.split( + noinline builder: SplitBuilder.() -> Unit, +): Action = SplitAction(typeOf(), builder) \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt deleted file mode 100644 index d747587e..00000000 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt +++ /dev/null @@ -1,35 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.Meta - -/** - * A simple data transformation on a data node - */ -public interface Action { - /** - * Transform the data in the node, producing a new node. By default it is assumed that all calculations are lazy - * so not actual computation is started at this moment - */ - public operator fun invoke(node: DataNode, meta: Meta): DataNode - - /** - * Terminal action is the one that could not be invoked lazily and requires some kind of blocking computation to invoke - */ - public val isTerminal: Boolean get() = false -} - -/** - * Action composition. The result is terminal if one of its parts is terminal - */ -public infix fun Action.then(action: Action): Action { - // TODO introduce composite action and add optimize by adding action to the list - return object : Action { - override fun invoke(node: DataNode, meta: Meta): DataNode { - return action(this@then.invoke(node, meta), meta) - } - - override val isTerminal: Boolean - get() = this@then.isTerminal || action.isTerminal - } -} - diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ActiveDataTree.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ActiveDataTree.kt new file mode 100644 index 00000000..5d197982 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ActiveDataTree.kt @@ -0,0 +1,118 @@ +package hep.dataforge.data + +import hep.dataforge.meta.* +import hep.dataforge.names.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +/** + * A mutable [DataTree.Companion.active]. It + */ +public class ActiveDataTree( + override val dataType: KType, +) : DataTree, DataSetBuilder, ActiveDataSet { + private val mutex = Mutex() + private val treeItems = HashMap>() + + override suspend fun items(): Map> = mutex.withLock { + treeItems.filter { !it.key.body.startsWith("@") } + } + + private val _updates = MutableSharedFlow() + + override val updates: Flow + get() = _updates + + private suspend fun remove(token: NameToken) { + mutex.withLock { + if (treeItems.remove(token) != null) { + _updates.emit(token.asName()) + } + } + } + + override suspend fun remove(name: Name) { + if (name.isEmpty()) error("Can't remove the root node") + (getItem(name.cutLast()).tree as? ActiveDataTree)?.remove(name.lastOrNull()!!) + } + + private suspend fun set(token: NameToken, data: Data) { + mutex.withLock { + treeItems[token] = DataTreeItem.Leaf(data) + } + } + + private suspend fun getOrCreateNode(token: NameToken): ActiveDataTree = + (treeItems[token] as? DataTreeItem.Node)?.tree as? ActiveDataTree + ?: ActiveDataTree(dataType).also { + mutex.withLock { + treeItems[token] = DataTreeItem.Node(it) + } + } + + private suspend fun getOrCreateNode(name: Name): ActiveDataTree { + return when (name.length) { + 0 -> this + 1 -> getOrCreateNode(name.firstOrNull()!!) + else -> getOrCreateNode(name.firstOrNull()!!).getOrCreateNode(name.cutFirst()) + } + } + + override suspend fun emit(name: Name, data: Data?) { + if (data == null) { + remove(name) + } else { + when (name.length) { + 0 -> error("Can't add data with empty name") + 1 -> set(name.firstOrNull()!!, data) + 2 -> getOrCreateNode(name.cutLast()).set(name.lastOrNull()!!, data) + } + } + _updates.emit(name) + } + + /** + * Copy given data set and mirror its changes to this [ActiveDataTree] in [this@setAndObserve]. Returns an update [Job] + */ + public fun CoroutineScope.setAndObserve(name: Name, dataSet: DataSet): Job = launch { + emit(name, dataSet) + dataSet.updates.collect { nameInBranch -> + emit(name + nameInBranch, dataSet.getData(nameInBranch)) + } + } +} + +/** + * Create a dynamic tree. Initial data is placed synchronously. Updates are propagated via [updatesScope] + */ +@Suppress("FunctionName") +public suspend fun ActiveDataTree( + type: KType, + block: suspend ActiveDataTree.() -> Unit, +): ActiveDataTree { + val tree = ActiveDataTree(type) + tree.block() + return tree +} + +@Suppress("FunctionName") +public suspend inline fun ActiveDataTree( + crossinline block: suspend ActiveDataTree.() -> Unit, +): ActiveDataTree = ActiveDataTree(typeOf()).apply { block() } + + +public suspend inline fun ActiveDataTree.emit( + name: Name, + noinline block: suspend ActiveDataTree.() -> Unit, +): Unit = emit(name, ActiveDataTree(typeOf(), block)) + +public suspend inline fun ActiveDataTree.emit( + name: String, + noinline block: suspend ActiveDataTree.() -> Unit, +): Unit = emit(name.toName(), ActiveDataTree(typeOf(), block)) diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CachingAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CachingAction.kt new file mode 100644 index 00000000..7911cb1f --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CachingAction.kt @@ -0,0 +1,52 @@ +package hep.dataforge.data + +import hep.dataforge.actions.Action +import hep.dataforge.meta.Meta +import hep.dataforge.names.Name +import hep.dataforge.names.startsWith +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlin.reflect.KType + +/** + * Remove all values with keys starting with [name] + */ +internal fun MutableMap.removeWhatStartsWith(name: Name) { + val toRemove = keys.filter { it.startsWith(name) } + toRemove.forEach(::remove) +} + +/** + * An action that caches results on-demand and recalculates them on source push + */ +public abstract class CachingAction( + public val outputType: KType, +) : Action { + + protected abstract fun CoroutineScope.transform( + set: DataSet, + meta: Meta, + key: Name = Name.EMPTY, + ): Flow> + + override suspend fun execute( + dataSet: DataSet, + meta: Meta, + scope: CoroutineScope?, + ): DataSet = ActiveDataTree(outputType) { + coroutineScope { + populate(transform(dataSet, meta)) + } + scope?.let { + dataSet.updates.collect { + //clear old nodes + remove(it) + //collect new items + populate(scope.transform(dataSet, meta, it)) + //FIXME if the target is data, updates are fired twice + } + } + } +} diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt index d1c0d55e..60bf5775 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/CoroutineMonitor.kt @@ -1,6 +1,6 @@ package hep.dataforge.data -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlin.coroutines.CoroutineContext diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt index c573aad8..6702de4d 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt @@ -3,28 +3,32 @@ package hep.dataforge.data import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaRepr import hep.dataforge.meta.isEmpty -import kotlinx.coroutines.CoroutineScope +import hep.dataforge.misc.DFInternal +import hep.dataforge.misc.Type +import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * A data element characterized by its meta */ -public interface Data : Goal, MetaRepr{ +@Type(Data.TYPE) +public interface Data : Goal, MetaRepr { /** * Type marker for the data. The type is known before the calculation takes place so it could be checked. */ - public val type: KClass + public val type: KType /** * Meta for the data */ public val meta: Meta - override fun toMeta(): Meta = Meta { - "type" put (type.simpleName?:"undefined") - if(!meta.isEmpty()) { + override fun toMeta(): Meta = Meta { + "type" put (type.toString()) + if (!meta.isEmpty()) { "meta" put meta } } @@ -32,131 +36,67 @@ public interface Data : Goal, MetaRepr{ public companion object { public const val TYPE: String = "data" - public operator fun invoke( - type: KClass, - meta: Meta = Meta.EMPTY, - context: CoroutineContext = EmptyCoroutineContext, - dependencies: Collection> = emptyList(), - block: suspend CoroutineScope.() -> T - ): Data = DynamicData(type, meta, context, dependencies, block) + /** + * The type that can't have any subtypes + */ + internal val TYPE_OF_NOTHING: KType = typeOf() - public inline operator fun invoke( + public inline fun static( + value: T, meta: Meta = Meta.EMPTY, - context: CoroutineContext = EmptyCoroutineContext, - dependencies: Collection> = emptyList(), - noinline block: suspend CoroutineScope.() -> T - ): Data = invoke(T::class, meta, context, dependencies, block) + ): Data = StaticData(typeOf(), value, meta) - public operator fun invoke( - name: String, - type: KClass, - meta: Meta = Meta.EMPTY, - context: CoroutineContext = EmptyCoroutineContext, - dependencies: Collection> = emptyList(), - block: suspend CoroutineScope.() -> T - ): Data = NamedData(name, invoke(type, meta, context, dependencies, block)) + /** + * An empty data containing only meta + */ + public fun empty(meta: Meta): Data = object : Data { + override val type: KType = TYPE_OF_NOTHING + override val meta: Meta = meta + override val dependencies: Collection> = emptyList() + override val deferred: Deferred + get() = GlobalScope.async(start = CoroutineStart.LAZY) { + error("The Data is empty and could not be computed") + } - public inline operator fun invoke( - name: String, - meta: Meta = Meta.EMPTY, - context: CoroutineContext = EmptyCoroutineContext, - dependencies: Collection> = emptyList(), - noinline block: suspend CoroutineScope.() -> T - ): Data = - invoke(name, T::class, meta, context, dependencies, block) - - public fun static(value: T, meta: Meta = Meta.EMPTY): Data = - StaticData(value, meta) + override fun async(coroutineScope: CoroutineScope): Deferred = deferred + override fun reset() {} + } } } - -public class DynamicData( - override val type: KClass, +/** + * A lazily computed variant of [Data] based on [LazyGoal] + * One must ensure that proper [type] is used so this method should not be used + */ +private class LazyData( + override val type: KType, override val meta: Meta = Meta.EMPTY, - context: CoroutineContext = EmptyCoroutineContext, + additionalContext: CoroutineContext = EmptyCoroutineContext, dependencies: Collection> = emptyList(), - block: suspend CoroutineScope.() -> T -) : Data, DynamicGoal(context, dependencies, block) + block: suspend () -> T, +) : Data, LazyGoal(additionalContext, dependencies, block) public class StaticData( + override val type: KType, value: T, - override val meta: Meta = Meta.EMPTY -) : Data, StaticGoal(value) { - override val type: KClass get() = value::class -} - -public class NamedData(public val name: String, data: Data) : Data by data - -public fun Data.map( - outputType: KClass, - coroutineContext: CoroutineContext = EmptyCoroutineContext, - meta: Meta = this.meta, - block: suspend CoroutineScope.(T) -> R -): Data = DynamicData(outputType, meta, coroutineContext, listOf(this)) { - block(await()) -} - - -/** - * Create a data pipe - */ -public inline fun Data.map( - coroutineContext: CoroutineContext = EmptyCoroutineContext, - meta: Meta = this.meta, - noinline block: suspend CoroutineScope.(T) -> R -): Data = DynamicData(R::class, meta, coroutineContext, listOf(this)) { - block(await()) -} - -/** - * Create a joined data. - */ -public inline fun Collection>.reduce( - coroutineContext: CoroutineContext = EmptyCoroutineContext, - meta: Meta, - noinline block: suspend CoroutineScope.(Collection) -> R -): Data = DynamicData( - R::class, - meta, - coroutineContext, - this -) { - block(map { run { it.await() } }) -} - -public fun Map>.reduce( - outputType: KClass, - coroutineContext: CoroutineContext = EmptyCoroutineContext, - meta: Meta, - block: suspend CoroutineScope.(Map) -> R -): DynamicData = DynamicData( - outputType, - meta, - coroutineContext, - this.values -) { - block(mapValues { it.value.await() }) -} - - -/** - * A joining of multiple data into a single one - * @param K type of the map key - * @param T type of the input goal - * @param R type of the result goal - */ -public inline fun Map>.reduce( - coroutineContext: CoroutineContext = EmptyCoroutineContext, - meta: Meta, - noinline block: suspend CoroutineScope.(Map) -> R -): DynamicData = DynamicData( - R::class, - meta, - coroutineContext, - this.values -) { - block(mapValues { it.value.await() }) -} + override val meta: Meta = Meta.EMPTY, +) : Data, StaticGoal(value) +@Suppress("FunctionName") +@DFInternal +public fun Data( + type: KType, + meta: Meta = Meta.EMPTY, + context: CoroutineContext = EmptyCoroutineContext, + dependencies: Collection> = emptyList(), + block: suspend () -> T, +): Data = LazyData(type, meta, context, dependencies, block) +@OptIn(DFInternal::class) +@Suppress("FunctionName") +public inline fun Data( + meta: Meta = Meta.EMPTY, + context: CoroutineContext = EmptyCoroutineContext, + dependencies: Collection> = emptyList(), + noinline block: suspend () -> T, +): Data = Data(typeOf(), meta, context, dependencies, block) diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt deleted file mode 100644 index df19acd0..00000000 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.* -import hep.dataforge.names.toName - - -public class DataFilter : Scheme() { - /** - * A source node for the filter - */ - public var from: String? by string() - /** - * A target placement for the filtered node - */ - public var to: String? by string() - /** - * A regular expression pattern for the filter - */ - public var pattern: String by string(".*") -// val prefix by string() -// val suffix by string() - - public companion object : SchemeSpec(::DataFilter) -} - -/** - * Apply meta-based filter to given data node - */ -public fun DataNode.filter(filter: DataFilter): DataNode { - val sourceNode = filter.from?.let { get(it.toName()).node } ?: this@filter - val regex = filter.pattern.toRegex() - val targetNode = DataTreeBuilder(type).apply { - sourceNode.dataSequence().forEach { (name, data) -> - if (name.toString().matches(regex)) { - this[name] = data - } - } - } - return filter.to?.let { - DataTreeBuilder(type).apply { this[it.toName()] = targetNode }.build() - } ?: targetNode.build() -} - -/** - * Filter data using [DataFilter] specification - */ -public fun DataNode.filter(filter: Meta): DataNode = filter(DataFilter.read(filter)) - -/** - * Filter data using [DataFilter] builder - */ -public fun DataNode.filter(filterBuilder: DataFilter.() -> Unit): DataNode = - filter(DataFilter(filterBuilder)) \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt deleted file mode 100644 index 23fea9ef..00000000 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt +++ /dev/null @@ -1,296 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.* -import hep.dataforge.names.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.set -import kotlin.reflect.KClass - -public sealed class DataItem : MetaRepr { - public abstract val type: KClass - - public abstract val meta: Meta - - public class Node(public val node: DataNode) : DataItem() { - override val type: KClass get() = node.type - - override fun toMeta(): Meta = node.toMeta() - - override val meta: Meta get() = node.meta - } - - public class Leaf(public val data: Data) : DataItem() { - override val type: KClass get() = data.type - - override fun toMeta(): Meta = data.toMeta() - - override val meta: Meta get() = data.meta - } -} - -/** - * A tree-like data structure grouped into the node. All data inside the node must inherit its type - */ -public interface DataNode : MetaRepr { - - /** - * The minimal common ancestor to all data in the node - */ - public val type: KClass - - public val items: Map> - - public val meta: Meta - - override fun toMeta(): Meta = Meta { - "type" put (type.simpleName ?: "undefined") - "items" put { - this@DataNode.items.forEach { - it.key.toString() put it.value.toMeta() - } - } - } - - /** - * Start computation for all goals in data node and return a job for the whole node - */ - @Suppress("DeferredResultUnused") - public fun CoroutineScope.startAll(): Job = launch { - items.values.forEach { - when (it) { - is DataItem.Node<*> -> it.node.run { startAll() } - is DataItem.Leaf<*> -> it.data.run { startAsync() } - } - } - } - - public companion object { - public const val TYPE: String = "dataNode" - - public operator fun invoke(type: KClass, block: DataTreeBuilder.() -> Unit): DataTree = - DataTreeBuilder(type).apply(block).build() - - public inline operator fun invoke(noinline block: DataTreeBuilder.() -> Unit): DataTree = - DataTreeBuilder(T::class).apply(block).build() - - public fun builder(type: KClass): DataTreeBuilder = DataTreeBuilder(type) - } -} - -public suspend fun DataNode.join(): Unit = coroutineScope { startAll().join() } - -public val DataItem?.node: DataNode? get() = (this as? DataItem.Node)?.node -public val DataItem?.data: Data? get() = (this as? DataItem.Leaf)?.data - -public operator fun DataNode.get(name: Name): DataItem? = when (name.length) { - 0 -> error("Empty name") - 1 -> items[name.firstOrNull()] - else -> get(name.firstOrNull()!!.asName()).node?.get(name.cutFirst()) -} - -public operator fun DataNode.get(name: String): DataItem? = get(name.toName()) - -/** - * Sequence of all children including nodes - */ -public fun DataNode.asSequence(): Sequence>> = sequence { - items.forEach { (head, item) -> - yield(head.asName() to item) - if (item is DataItem.Node) { - val subSequence = item.node.asSequence() - .map { (name, data) -> (head.asName() + name) to data } - yieldAll(subSequence) - } - } -} - -/** - * Sequence of data entries - */ -public fun DataNode.dataSequence(): Sequence>> = sequence { - items.forEach { (head, item) -> - when (item) { - is DataItem.Leaf -> yield(head.asName() to item.data) - is DataItem.Node -> { - val subSequence = item.node.dataSequence() - .map { (name, data) -> (head.asName() + name) to data } - yieldAll(subSequence) - } - } - } -} - -public operator fun DataNode.iterator(): Iterator>> = asSequence().iterator() - -public class DataTree internal constructor( - override val type: KClass, - override val items: Map>, - override val meta: Meta -) : DataNode - -private sealed class DataTreeBuilderItem { - class Node(val tree: DataTreeBuilder) : DataTreeBuilderItem() - class Leaf(val value: Data) : DataTreeBuilderItem() -} - -/** - * A builder for a DataTree. - */ -@DFBuilder -public class DataTreeBuilder(public val type: KClass) { - private val map = HashMap>() - - private var meta = MetaBuilder() - - public operator fun set(token: NameToken, node: DataTreeBuilder) { - if (map.containsKey(token)) error("Tree entry with name $token is not empty") - map[token] = DataTreeBuilderItem.Node(node) - } - - public operator fun set(token: NameToken, data: Data) { - if (map.containsKey(token)) error("Tree entry with name $token is not empty") - map[token] = DataTreeBuilderItem.Leaf(data) - } - - private fun buildNode(token: NameToken): DataTreeBuilder { - return if (!map.containsKey(token)) { - DataTreeBuilder(type).also { map[token] = DataTreeBuilderItem.Node(it) } - } else { - (map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree - } - } - - private fun buildNode(name: Name): DataTreeBuilder { - return when (name.length) { - 0 -> this - 1 -> buildNode(name.firstOrNull()!!) - else -> buildNode(name.firstOrNull()!!).buildNode(name.cutFirst()) - } - } - - public operator fun set(name: Name, data: Data) { - when (name.length) { - 0 -> error("Can't add data with empty name") - 1 -> set(name.firstOrNull()!!, data) - 2 -> buildNode(name.cutLast())[name.lastOrNull()!!] = data - } - } - - public operator fun set(name: Name, node: DataTreeBuilder) { - when (name.length) { - 0 -> error("Can't add data with empty name") - 1 -> set(name.firstOrNull()!!, node) - 2 -> buildNode(name.cutLast())[name.lastOrNull()!!] = node - } - } - - public operator fun set(name: Name, node: DataNode): Unit = set(name, node.builder()) - - public operator fun set(name: Name, item: DataItem): Unit = when (item) { - is DataItem.Node -> set(name, item.node.builder()) - is DataItem.Leaf -> set(name, item.data) - } - - /** - * Append data to node - */ - public infix fun String.put(data: Data): Unit = set(toName(), data) - - /** - * Append node - */ - public infix fun String.put(node: DataNode): Unit = set(toName(), node) - - public infix fun String.put(item: DataItem): Unit = set(toName(), item) - - /** - * Build and append node - */ - public infix fun String.put(block: DataTreeBuilder.() -> Unit): Unit = set(toName(), DataTreeBuilder(type).apply(block)) - - - /** - * Update data with given node data and meta with node meta. - */ - public fun update(node: DataNode) { - node.dataSequence().forEach { - //TODO check if the place is occupied - this[it.first] = it.second - } - meta.update(node.meta) - } - - public fun meta(block: MetaBuilder.() -> Unit): MetaBuilder = meta.apply(block) - - public fun meta(meta: Meta) { - this.meta = meta.builder() - } - - public fun build(): DataTree { - val resMap = map.mapValues { (_, value) -> - when (value) { - is DataTreeBuilderItem.Leaf -> DataItem.Leaf(value.value) - is DataTreeBuilderItem.Node -> DataItem.Node(value.tree.build()) - } - } - return DataTree(type, resMap, meta.seal()) - } -} - -public fun DataTreeBuilder.datum(name: Name, data: Data) { - this[name] = data -} - -public fun DataTreeBuilder.datum(name: String, data: Data) { - this[name.toName()] = data -} - -public fun DataTreeBuilder.static(name: Name, data: T, meta: Meta = Meta.EMPTY) { - this[name] = Data.static(data, meta) -} - -public fun DataTreeBuilder.static(name: Name, data: T, block: MetaBuilder.() -> Unit = {}) { - this[name] = Data.static(data, Meta(block)) -} - -public fun DataTreeBuilder.static(name: String, data: T, block: MetaBuilder.() -> Unit = {}) { - this[name.toName()] = Data.static(data, Meta(block)) -} - -public fun DataTreeBuilder.node(name: Name, node: DataNode) { - this[name] = node -} - -public fun DataTreeBuilder.node(name: String, node: DataNode) { - this[name.toName()] = node -} - -public inline fun DataTreeBuilder.node(name: Name, noinline block: DataTreeBuilder.() -> Unit) { - this[name] = DataNode(T::class, block) -} - -public inline fun DataTreeBuilder.node(name: String, noinline block: DataTreeBuilder.() -> Unit) { - this[name.toName()] = DataNode(T::class, block) -} - -/** - * Generate a mutable builder from this node. Node content is not changed - */ -public fun DataNode.builder(): DataTreeBuilder = DataTreeBuilder(type).apply { - dataSequence().forEach { (name, data) -> this[name] = data } -} - -public fun DataNode.filter(predicate: (Name, Data) -> Boolean): DataNode = DataNode.invoke(type) { - dataSequence().forEach { (name, data) -> - if (predicate(name, data)) { - this[name] = data - } - } -} - -public fun DataNode.first(): Data? = dataSequence().first().second \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataSet.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataSet.kt new file mode 100644 index 00000000..f2a4bf88 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataSet.kt @@ -0,0 +1,99 @@ +package hep.dataforge.data + +import hep.dataforge.data.Data.Companion.TYPE_OF_NOTHING +import hep.dataforge.meta.Meta +import hep.dataforge.meta.set +import hep.dataforge.names.* +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlin.reflect.KType + +public interface DataSet { + + /** + * The minimal common ancestor to all data in the node + */ + public val dataType: KType + + /** + * Traverse this provider or its child. The order is not guaranteed. + * [root] points to a root name for traversal. If it is empty, traverse this source, if it points to a [Data], + * return flow, that contains single [Data], if it points to a node with children, return children. + */ + public fun flow(): Flow> + + /** + * Get data with given name. + */ + public suspend fun getData(name: Name): Data? + + /** + * Get a snapshot of names of children of given node. Empty if node does not exist or is a leaf. + * + * By default traverses the whole tree. Could be optimized in descendants + */ + public suspend fun listChildren(prefix: Name = Name.EMPTY): List = + flow().map { it.name }.filter { it.startsWith(prefix) && (it.length == prefix.length + 1) }.toList() + + public companion object { + public val META_KEY: Name = "@meta".asName() + + /** + * An empty [DataSet] that suits all types + */ + public val EMPTY: DataSet = object : DataSet { + override val dataType: KType = TYPE_OF_NOTHING + + private val nothing: Nothing get() = error("this is nothing") + + override fun flow(): Flow> = emptyFlow() + + override suspend fun getData(name: Name): Data? = null + } + } +} + +public interface ActiveDataSet : DataSet { + /** + * A flow of updated item names. Updates are propagated in a form of [Flow] of names of updated nodes. + * Those can include new data items and replacement of existing ones. The replaced items could update existing data content + * and replace it completely, so they should be pulled again. + * + */ + public val updates: Flow +} + +public val DataSet.updates: Flow get() = if (this is ActiveDataSet) updates else emptyFlow() + +/** + * Flow all data nodes with names starting with [branchName] + */ +public fun DataSet.flowChildren(branchName: Name): Flow> = this@flowChildren.flow().filter { + it.name.startsWith(branchName) +} + +/** + * Start computation for all goals in data node and return a job for the whole node + */ +public fun DataSet.startAll(coroutineScope: CoroutineScope): Job = coroutineScope.launch { + flow().map { + it.launch(this@launch) + }.toList().joinAll() +} + +public suspend fun DataSet.join(): Unit = coroutineScope { startAll(this).join() } + +public suspend fun DataSet<*>.toMeta(): Meta = Meta { + flow().collect { + if (it.name.endsWith(DataSet.META_KEY)) { + set(it.name, it.meta) + } else { + it.name put { + "type" put it.type.toString() + "meta" put it.meta + } + } + } +} + +public val DataSet.updatesWithData: Flow> get() = updates.mapNotNull { getData(it)?.named(it) } \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataSetBuilder.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataSetBuilder.kt new file mode 100644 index 00000000..95e9fd06 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataSetBuilder.kt @@ -0,0 +1,145 @@ +package hep.dataforge.data + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.misc.DFExperimental +import hep.dataforge.names.Name +import hep.dataforge.names.plus +import hep.dataforge.names.toName +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlin.reflect.KType + +public interface DataSetBuilder { + public val dataType: KType + + /** + * Remove all data items starting with [name] + */ + public suspend fun remove(name: Name) + + public suspend fun emit(name: Name, data: Data?) + + /** + * Set a current state of given [dataSet] into a branch [name]. Does not propagate updates + */ + public suspend fun emit(name: Name, dataSet: DataSet) { + //remove previous items + if (name != Name.EMPTY) { + remove(name) + } + + //Set new items + dataSet.flow().collect { + emit(name + it.name, it.data) + } + } + + /** + * Append data to node + */ + public suspend infix fun String.put(data: Data): Unit = emit(toName(), data) + + /** + * Append node + */ + public suspend infix fun String.put(dataSet: DataSet): Unit = emit(toName(), dataSet) + + /** + * Build and append node + */ + public suspend infix fun String.put(block: suspend DataSetBuilder.() -> Unit): Unit = emit(toName(), block) +} + +private class SubSetBuilder( + private val parent: DataSetBuilder, + private val branch: Name, +) : DataSetBuilder { + override val dataType: KType get() = parent.dataType + + override suspend fun remove(name: Name) { + parent.remove(branch + name) + } + + override suspend fun emit(name: Name, data: Data?) { + parent.emit(branch + name, data) + } + + override suspend fun emit(name: Name, dataSet: DataSet) { + parent.emit(branch + name, dataSet) + } +} + +public suspend fun DataSetBuilder.emit(name: Name, block: suspend DataSetBuilder.() -> Unit) { + SubSetBuilder(this, name).apply { block() } +} + + +public suspend fun DataSetBuilder.emit(name: String, data: Data) { + emit(name.toName(), data) +} + +public suspend fun DataSetBuilder.emit(name: String, set: DataSet) { + this.emit(name.toName(), set) +} + +public suspend fun DataSetBuilder.emit(name: String, block: suspend DataSetBuilder.() -> Unit): Unit = + this@emit.emit(name.toName(), block) + +public suspend fun DataSetBuilder.emit(data: NamedData) { + emit(data.name, data.data) +} + +/** + * Produce lazy [Data] and emit it into the [DataSetBuilder] + */ +public suspend inline fun DataSetBuilder.produce( + name: String, + meta: Meta = Meta.EMPTY, + noinline producer: suspend () -> T, +) { + val data = Data(meta, block = producer) + emit(name, data) +} + +public suspend inline fun DataSetBuilder.produce( + name: Name, + meta: Meta = Meta.EMPTY, + noinline producer: suspend () -> T, +) { + val data = Data(meta, block = producer) + emit(name, data) +} + +/** + * Emit a static data with the fixed value + */ +public suspend inline fun DataSetBuilder.static(name: String, data: T, meta: Meta = Meta.EMPTY): Unit = + emit(name, Data.static(data, meta)) + +public suspend inline fun DataSetBuilder.static(name: Name, data: T, meta: Meta = Meta.EMPTY): Unit = + emit(name, Data.static(data, meta)) + +public suspend inline fun DataSetBuilder.static( + name: String, + data: T, + metaBuilder: MetaBuilder.() -> Unit, +): Unit = emit(name.toName(), Data.static(data, Meta(metaBuilder))) + +/** + * Update data with given node data and meta with node meta. + */ +@DFExperimental +public suspend fun DataSetBuilder.populate(tree: DataSet): Unit = coroutineScope { + tree.flow().collect { + //TODO check if the place is occupied + emit(it.name, it.data) + } +} + +public suspend fun DataSetBuilder.populate(flow: Flow>) { + flow.collect { + emit(it.name, it.data) + } +} diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataTree.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataTree.kt new file mode 100644 index 00000000..5676eeed --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataTree.kt @@ -0,0 +1,96 @@ +package hep.dataforge.data + +import hep.dataforge.misc.Type +import hep.dataforge.names.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.reflect.KType + +public sealed class DataTreeItem { + public class Node(public val tree: DataTree) : DataTreeItem() + public class Leaf(public val data: Data) : DataTreeItem() +} + +public val DataTreeItem.type: KType + get() = when (this) { + is DataTreeItem.Node -> tree.dataType + is DataTreeItem.Leaf -> data.type + } + +/** + * A tree-like [DataSet] grouped into the node. All data inside the node must inherit its type + */ +@Type(DataTree.TYPE) +public interface DataTree : DataSet { + + /** + * Children items of this [DataTree] provided asynchronously + */ + public suspend fun items(): Map> + + override fun flow(): Flow> = flow { + items().forEach { (token, childItem: DataTreeItem) -> + if(!token.body.startsWith("@")) { + when (childItem) { + is DataTreeItem.Leaf -> emit(childItem.data.named(token.asName())) + is DataTreeItem.Node -> emitAll(childItem.tree.flow().map { it.named(token + it.name) }) + } + } + } + } + + override suspend fun listChildren(prefix: Name): List = + getItem(prefix).tree?.items()?.keys?.map { prefix + it } ?: emptyList() + + override suspend fun getData(name: Name): Data? = when (name.length) { + 0 -> null + 1 -> items()[name.firstOrNull()!!].data + else -> items()[name.firstOrNull()!!].tree?.getData(name.cutFirst()) + } + + public companion object { + public const val TYPE: String = "dataTree" + } +} + +public suspend fun DataSet.getData(name: String): Data? = getData(name.toName()) + +/** + * Get a [DataTreeItem] with given [name] or null if the item does not exist + */ +public tailrec suspend fun DataTree.getItem(name: Name): DataTreeItem? = when (name.length) { + 0 -> DataTreeItem.Node(this) + 1 -> items()[name.firstOrNull()] + else -> items()[name.firstOrNull()!!].tree?.getItem(name.cutFirst()) +} + +public val DataTreeItem?.tree: DataTree? get() = (this as? DataTreeItem.Node)?.tree +public val DataTreeItem?.data: Data? get() = (this as? DataTreeItem.Leaf)?.data + +/** + * Flow of all children including nodes + */ +public fun DataTree.itemFlow(): Flow>> = flow { + items().forEach { (head, item) -> + emit(head.asName() to item) + if (item is DataTreeItem.Node) { + val subSequence = item.tree.itemFlow() + .map { (name, data) -> (head.asName() + name) to data } + emitAll(subSequence) + } + } +} + +/** + * Get a branch of this [DataTree] with a given [branchName]. + * The difference from similar method for [DataSet] is that internal logic is more simple and the return value is a [DataTree] + */ +public fun DataTree.branch(branchName: Name): DataTree = object : DataTree { + override val dataType: KType get() = this@branch.dataType + + override suspend fun items(): Map> = getItem(branchName).tree?.items() ?: emptyMap() +} diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt index e5050d22..7763c3ce 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt @@ -1,22 +1,28 @@ package hep.dataforge.data -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +/** + * Lazy computation result with its dependencies to allowing to stat computing dependencies ahead of time + */ public interface Goal { public val dependencies: Collection> + /** - * Returns current running coroutine if the goal is started + * Returns current running coroutine if the goal is started. Null if the computation is not started. */ - public val result: Deferred? + public val deferred: Deferred? /** * Get ongoing computation or start a new one. * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations. + * + * If the computation is already running, the scope is not used. */ - public fun CoroutineScope.startAsync(): Deferred + public fun async(coroutineScope: CoroutineScope): Deferred /** * Reset the computation @@ -26,89 +32,81 @@ public interface Goal { public companion object } -public suspend fun Goal.await(): T = coroutineScope { startAsync().await() } +public fun Goal<*>.launch(coroutineScope: CoroutineScope): Job = async(coroutineScope) -public val Goal<*>.isComplete: Boolean get() = result?.isCompleted ?: false +public suspend fun Goal.await(): T = coroutineScope { async(this).await() } + +public val Goal<*>.isComplete: Boolean get() = deferred?.isCompleted ?: false public open class StaticGoal(public val value: T) : Goal { override val dependencies: Collection> get() = emptyList() - override val result: Deferred = CompletableDeferred(value) + override val deferred: Deferred = CompletableDeferred(value) - override fun CoroutineScope.startAsync(): Deferred = result + override fun async(coroutineScope: CoroutineScope): Deferred = deferred override fun reset() { //doNothing } } -public open class DynamicGoal( +/** + * @param coroutineContext additional context information + */ +public open class LazyGoal( private val coroutineContext: CoroutineContext = EmptyCoroutineContext, override val dependencies: Collection> = emptyList(), - public val block: suspend CoroutineScope.() -> T + public val block: suspend () -> T, ) : Goal { - final override var result: Deferred? = null + final override var deferred: Deferred? = null private set /** * Get ongoing computation or start a new one. * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations. + * If [GoalExecutionRestriction] is present in the [coroutineScope] context, the call could produce a error a warning + * depending on the settings. */ @DFExperimental - override fun CoroutineScope.startAsync(): Deferred { - val startedDependencies = this@DynamicGoal.dependencies.map { goal -> - goal.run { startAsync() } - } - return result - ?: async(this@DynamicGoal.coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) { - startedDependencies.forEach { deferred -> - deferred.invokeOnCompletion { error -> - if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}")) - } + override fun async(coroutineScope: CoroutineScope): Deferred { + val log = coroutineScope.coroutineContext[GoalLogger] + // Check if context restricts goal computation + coroutineScope.coroutineContext[GoalExecutionRestriction]?.let { restriction -> + when (restriction.policy) { + GoalExecutionRestrictionPolicy.WARNING -> log?.emit(GoalLogger.WARNING_TAG) { "Goal eager execution is prohibited by the coroutine scope policy" } + GoalExecutionRestrictionPolicy.ERROR -> error("Goal eager execution is prohibited by the coroutine scope policy") + else -> { + /*do nothing*/ } - block() - }.also { result = it } + } + } + + log?.emit { "Starting dependencies computation for ${this@LazyGoal}" } + val startedDependencies = this.dependencies.map { goal -> + goal.run { async(coroutineScope) } + } + return deferred ?: coroutineScope.async( + coroutineContext + + CoroutineMonitor() + + Dependencies(startedDependencies) + + GoalExecutionRestriction(GoalExecutionRestrictionPolicy.NONE) // Remove restrictions on goal execution + ) { + //cancel execution if error encountered in one of dependencies + startedDependencies.forEach { deferred -> + deferred.invokeOnCompletion { error -> + if (error != null) this.cancel(CancellationException("Dependency $deferred failed with error: ${error.message}")) + } + } + coroutineContext[GoalLogger]?.emit { "Starting computation of ${this@LazyGoal}" } + block() + }.also { deferred = it } } /** * Reset the computation */ override fun reset() { - result?.cancel() - result = null + deferred?.cancel() + deferred = null } -} - -/** - * Create a one-to-one goal based on existing goal - */ -public fun Goal.map( - coroutineContext: CoroutineContext = EmptyCoroutineContext, - block: suspend CoroutineScope.(T) -> R -): Goal = DynamicGoal(coroutineContext, listOf(this)) { - block(await()) -} - -/** - * Create a joining goal. - */ -public fun Collection>.reduce( - coroutineContext: CoroutineContext = EmptyCoroutineContext, - block: suspend CoroutineScope.(Collection) -> R -): Goal = DynamicGoal(coroutineContext, this) { - block(map { run { it.await() } }) -} - -/** - * A joining goal for a map - * @param K type of the map key - * @param T type of the input goal - * @param R type of the result goal - */ -public fun Map>.reduce( - coroutineContext: CoroutineContext = EmptyCoroutineContext, - block: suspend CoroutineScope.(Map) -> R -): Goal = DynamicGoal(coroutineContext, this.values) { - block(mapValues { it.value.await() }) -} - +} \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GoalExecutionRestriction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GoalExecutionRestriction.kt new file mode 100644 index 00000000..38e439d5 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GoalExecutionRestriction.kt @@ -0,0 +1,17 @@ +package hep.dataforge.data + +import kotlin.coroutines.CoroutineContext + +public enum class GoalExecutionRestrictionPolicy { + NONE, + WARNING, + ERROR +} + +public class GoalExecutionRestriction( + public val policy: GoalExecutionRestrictionPolicy = GoalExecutionRestrictionPolicy.ERROR, +) : CoroutineContext.Element { + override val key: CoroutineContext.Key<*> get() = Companion + + public companion object : CoroutineContext.Key +} \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GoalLogger.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GoalLogger.kt new file mode 100644 index 00000000..f0520578 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GoalLogger.kt @@ -0,0 +1,13 @@ +package hep.dataforge.data + +import kotlin.coroutines.CoroutineContext + +public interface GoalLogger : CoroutineContext.Element { + override val key: CoroutineContext.Key<*> get() = GoalLogger + + public fun emit(vararg tags: String, message: suspend () -> String) + + public companion object : CoroutineContext.Key{ + public const val WARNING_TAG: String = "WARNING" + } +} \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GroupRule.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GroupRule.kt index 0a5de778..fcaa0658 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GroupRule.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/GroupRule.kt @@ -15,14 +15,16 @@ */ package hep.dataforge.data -import hep.dataforge.meta.Meta import hep.dataforge.meta.get import hep.dataforge.meta.string +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch public interface GroupRule { - public operator fun invoke(node: DataNode): Map> + public suspend fun gather(set: DataSet): Map> - public companion object{ + public companion object { /** * Create grouping rule that creates groups for different values of value * field with name [key] @@ -31,38 +33,32 @@ public interface GroupRule { * @param defaultTagValue * @return */ - public fun byValue(key: String, defaultTagValue: String): GroupRule = object : - GroupRule { - override fun invoke(node: DataNode): Map> { - val map = HashMap>() + public fun byMetaValue( + scope: CoroutineScope, + key: String, + defaultTagValue: String, + ): GroupRule = object : GroupRule { - node.dataSequence().forEach { (name, data) -> + override suspend fun gather( + set: DataSet, + ): Map> { + val map = HashMap>() + + set.flow().collect { data -> val tagValue = data.meta[key]?.string ?: defaultTagValue - map.getOrPut(tagValue) { DataNode.builder(node.type) }[name] = data + map.getOrPut(tagValue) { ActiveDataTree(set.dataType) }.emit(data.name, data.data) } - return map.mapValues { it.value.build() } - } - } - - - // @ValueDef(key = "byValue", required = true, info = "The name of annotation value by which grouping should be made") -// @ValueDef( -// key = "defaultValue", -// def = "default", -// info = "Default value which should be used for content in which the grouping value is not presented" -// ) - public fun byMeta(config: Meta): GroupRule { - //TODO expand grouping options - return config["byValue"]?.string?.let { - byValue( - it, - config["defaultValue"]?.string ?: "default" - ) - } - ?: object : GroupRule { - override fun invoke(node: DataNode): Map> = mapOf("" to node) + scope.launch { + set.updates.collect { name -> + val data = set.getData(name) + val tagValue = data?.meta[key]?.string ?: defaultTagValue + map.getOrPut(tagValue) { ActiveDataTree(set.dataType) }.emit(name, data) + } } + + return map + } } } -} +} \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt deleted file mode 100644 index e5504530..00000000 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt +++ /dev/null @@ -1,75 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.* -import hep.dataforge.names.Name -import kotlin.reflect.KClass - -/** - * Action environment includes data name, data meta and action configuration meta - */ -public data class ActionEnv( - val name: Name, - val meta: Meta, - val actionMeta: Meta -) - -/** - * Action environment - */ -@DFBuilder -public class MapActionBuilder(public var name: Name, public var meta: MetaBuilder, public val actionMeta: Meta) { - public lateinit var result: suspend ActionEnv.(T) -> R - - /** - * Calculate the result of goal - */ - public fun result(f: suspend ActionEnv.(T) -> R) { - result = f; - } -} - - -public class MapAction( - public val inputType: KClass, - public val outputType: KClass, - private val block: MapActionBuilder.() -> Unit -) : Action { - - override fun invoke(node: DataNode, meta: Meta): DataNode { - node.ensureType(inputType) - - return DataNode.invoke(outputType) { - node.dataSequence().forEach { (name, data) -> - /* - * Creating a new environment for action using **old** name, old meta and task meta - */ - val env = ActionEnv(name, data.meta, meta) - - //applying transformation from builder - val builder = MapActionBuilder( - name, - data.meta.builder(), // using data meta - meta - ).apply(block) - - //getting new name - val newName = builder.name - - //getting new meta - val newMeta = builder.meta.seal() - - val newData = data.map(outputType, meta = newMeta) { builder.result(env, it) } - //setting the data node - this[newName] = newData - } - } - } -} - -public inline fun DataNode.map( - meta: Meta, - noinline action: MapActionBuilder.() -> Unit -): DataNode = MapAction(T::class, R::class, action).invoke(this, meta) - - - diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/NamedData.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/NamedData.kt new file mode 100644 index 00000000..aa5afcdc --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/NamedData.kt @@ -0,0 +1,32 @@ +package hep.dataforge.data + +import hep.dataforge.meta.isEmpty +import hep.dataforge.misc.Named +import hep.dataforge.names.Name + +public interface NamedData : Named, Data { + override val name: Name + public val data: Data +} + +private class NamedDataImpl( + override val name: Name, + override val data: Data, +) : Data by data, NamedData { + override fun toString(): String = buildString { + append("NamedData(name=\"$name\"") + if (data is StaticData) { + append(", value=${data.value}") + } + if (!data.meta.isEmpty()) { + append(", meta=${data.meta}") + } + append(")") + } +} + +public fun Data.named(name: Name): NamedData = if (this is NamedData) { + NamedDataImpl(name, this.data) +} else { + NamedDataImpl(name, this) +} \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ReduceAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ReduceAction.kt deleted file mode 100644 index a0227af9..00000000 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/ReduceAction.kt +++ /dev/null @@ -1,107 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder -import hep.dataforge.names.Name -import hep.dataforge.names.toName -import kotlin.reflect.KClass - - -public class JoinGroup(public var name: String, internal val node: DataNode) { - - public var meta: MetaBuilder = MetaBuilder() - - public lateinit var result: suspend ActionEnv.(Map) -> R - - public fun result(f: suspend ActionEnv.(Map) -> R) { - this.result = f; - } - -} - -public class ReduceGroupBuilder(public val actionMeta: Meta) { - private val groupRules: MutableList<(DataNode) -> List>> = ArrayList(); - - /** - * introduce grouping by value name - */ - public fun byValue(tag: String, defaultTag: String = "@default", action: JoinGroup.() -> Unit) { - groupRules += { node -> - GroupRule.byValue(tag, defaultTag).invoke(node).map { - JoinGroup(it.key, it.value).apply(action) - } - } - } - - /** - * Add a single fixed group to grouping rules - */ - public fun group(groupName: String, filter: DataFilter, action: JoinGroup.() -> Unit) { - groupRules += { node -> - listOf( - JoinGroup(groupName, node.filter(filter)).apply(action) - ) - } - } - - public fun group(groupName: String, filter: (Name, Data) -> Boolean, action: JoinGroup.() -> Unit) { - groupRules += { node -> - listOf( - JoinGroup(groupName, node.filter(filter)).apply(action) - ) - } - } - - /** - * Apply transformation to the whole node - */ - public fun result(resultName: String, f: suspend ActionEnv.(Map) -> R) { - groupRules += { node -> - listOf(JoinGroup(resultName, node).apply { result(f) }) - } - } - - internal fun buildGroups(input: DataNode): List> { - return groupRules.flatMap { it.invoke(input) } - } - -} - - -/** - * The same rules as for KPipe - */ -public class ReduceAction( - public val inputType: KClass, - public val outputType: KClass, - private val action: ReduceGroupBuilder.() -> Unit -) : Action { - - override fun invoke(node: DataNode, meta: Meta): DataNode { - node.ensureType(inputType) - return DataNode.invoke(outputType) { - ReduceGroupBuilder(meta).apply(action).buildGroups(node).forEach { group -> - - //val laminate = Laminate(group.meta, meta) - - val dataMap = group.node.dataSequence().associate { it } - - val groupName: String = group.name - - val groupMeta = group.meta - - val env = ActionEnv(groupName.toName(), groupMeta, meta) - - val res: DynamicData = dataMap.reduce( - outputType, - meta = groupMeta - ) { group.result.invoke(env, it) } - - set(env.name, res) - } - - } - } -} - -public operator fun Map.get(name: String): T? = get(name.toName()) diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/SplitAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/SplitAction.kt deleted file mode 100644 index 731a9403..00000000 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/SplitAction.kt +++ /dev/null @@ -1,64 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.Laminate -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder -import hep.dataforge.meta.builder -import hep.dataforge.names.Name -import hep.dataforge.names.toName -import kotlin.collections.set -import kotlin.reflect.KClass - - -public class FragmentRule(public val name: Name, public var meta: MetaBuilder) { - public lateinit var result: suspend (T) -> R - - public fun result(f: suspend (T) -> R) { - result = f; - } -} - - -public class SplitBuilder(public val name: Name, public val meta: Meta) { - internal val fragments: MutableMap.() -> Unit> = HashMap() - - /** - * Add new fragment building rule. If the framgent not defined, result won't be available even if it is present in the map - * @param name the name of a fragment - * @param rule the rule to transform fragment name and meta using - */ - public fun fragment(name: String, rule: FragmentRule.() -> Unit) { - fragments[name.toName()] = rule - } -} - -public class SplitAction( - public val inputType: KClass, - public val outputType: KClass, - private val action: SplitBuilder.() -> Unit -) : Action { - - override fun invoke(node: DataNode, meta: Meta): DataNode { - node.ensureType(inputType) - - return DataNode.invoke(outputType) { - node.dataSequence().forEach { (name, data) -> - - val laminate = Laminate(data.meta, meta) - - val split = SplitBuilder(name, data.meta).apply(action) - - - // apply individual fragment rules to result - split.fragments.forEach { (fragmentName, rule) -> - val env = FragmentRule(fragmentName, laminate.builder()) - - rule(env) - - val res = data.map(outputType, meta = env.meta) { env.result(it) } - set(env.name, res) - } - } - } - } -} \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/StaticDataTree.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/StaticDataTree.kt new file mode 100644 index 00000000..950cb1f1 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/StaticDataTree.kt @@ -0,0 +1,76 @@ +package hep.dataforge.data + +import hep.dataforge.names.* +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.collect +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +@PublishedApi +internal class StaticDataTree( + override val dataType: KType, +) : DataSetBuilder, DataTree { + + private val items: MutableMap> = HashMap() + + override suspend fun items(): Map> = items.filter { !it.key.body.startsWith("@") } + + override suspend fun remove(name: Name) { + when (name.length) { + 0 -> error("Can't remove root tree node") + 1 -> items.remove(name.firstOrNull()!!) + else -> (items[name.firstOrNull()!!].tree as? StaticDataTree)?.remove(name.cutFirst()) + } + } + + private fun getOrCreateNode(name: Name): StaticDataTree = when (name.length) { + 0 -> this + 1 -> { + val itemName = name.firstOrNull()!! + (items[itemName].tree as? StaticDataTree) ?: StaticDataTree(dataType).also { + items[itemName] = DataTreeItem.Node(it) + } + } + else -> getOrCreateNode(name.cutLast()).getOrCreateNode(name.lastOrNull()!!.asName()) + } + + private suspend fun set(name: Name, item: DataTreeItem?) { + if (name.isEmpty()) error("Can't set top level tree node") + if (item == null) { + remove(name) + } else { + getOrCreateNode(name.cutLast()).items[name.lastOrNull()!!] = item + } + } + + override suspend fun emit(name: Name, data: Data?) { + set(name, data?.let { DataTreeItem.Leaf(it) }) + } + + override suspend fun emit(name: Name, dataSet: DataSet) { + if (dataSet is StaticDataTree) { + set(name, DataTreeItem.Node(dataSet)) + } else { + coroutineScope { + dataSet.flow().collect { + emit(name + it.name, it.data) + } + } + } + } +} + +@Suppress("FunctionName") +public suspend fun DataTree( + dataType: KType, + block: suspend DataSetBuilder.() -> Unit, +): DataTree = StaticDataTree(dataType).apply { block() } + +@Suppress("FunctionName") +public suspend inline fun DataTree( + noinline block: suspend DataSetBuilder.() -> Unit, +): DataTree = DataTree(typeOf(), block) + +public suspend fun DataSet.seal(): DataTree = DataTree(dataType){ + populate(this@seal) +} \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt deleted file mode 100644 index 26563bc8..00000000 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt +++ /dev/null @@ -1,73 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.Meta -import hep.dataforge.names.NameToken -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlin.reflect.KClass - -public fun Data.upcast(type: KClass): Data { - return object : Data by this { - override val type: KClass = type - } -} - -/** - * Safe upcast a [Data] to a supertype - */ -public inline fun Data.upcast(): Data = upcast(R::class) - -/** - * Check if node could be safely cast to given class - */ -internal expect fun DataNode<*>.canCast(type: KClass): Boolean - -/** - * Check if data could be safely cast to given class - */ -internal expect fun Data<*>.canCast(type: KClass): Boolean - -public fun DataItem<*>.canCast(type: KClass): Boolean = when (this) { - is DataItem.Node -> node.canCast(type) - is DataItem.Leaf -> data.canCast(type) -} - -/** - * Unsafe cast of data node - */ -@Suppress("UNCHECKED_CAST") -public fun Data<*>.cast(type: KClass): Data { - return object : Data { - override val meta: Meta get() = this@cast.meta - override val dependencies: Collection> get() = this@cast.dependencies - override val result: Deferred? get() = this@cast.result as Deferred - override fun CoroutineScope.startAsync(): Deferred = this@cast.run { startAsync() as Deferred } - override fun reset() = this@cast.reset() - override val type: KClass = type - } -} - -public inline fun Data<*>.cast(): Data = cast(R::class) - -@Suppress("UNCHECKED_CAST") -public fun DataNode<*>.cast(type: KClass): DataNode { - return object : DataNode { - override val meta: Meta get() = this@cast.meta - override val type: KClass = type - override val items: Map> get() = this@cast.items as Map> - } -} - -public inline fun DataNode<*>.cast(): DataNode = cast(R::class) - -/** - * Check that node is compatible with given type meaning that each element could be cast to the type - */ -public fun DataNode<*>.ensureType(type: KClass) { - if (!canCast(type)) { - error("$type expected, but $type received") - } -} - - -//expect fun DataNode.cast(type: KClass): DataNode \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataFilter.kt new file mode 100644 index 00000000..8f38d100 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataFilter.kt @@ -0,0 +1,71 @@ +package hep.dataforge.data + +import hep.dataforge.misc.DFExperimental +import hep.dataforge.names.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlin.reflect.KType + + +/** + * A stateless filtered [DataSet] + */ +public fun DataSet.filter( + predicate: suspend (Name, Data) -> Boolean, +): ActiveDataSet = object : ActiveDataSet { + override val dataType: KType get() = this@filter.dataType + + override fun flow(): Flow> = + this@filter.flow().filter { predicate(it.name, it.data) } + + override suspend fun getData(name: Name): Data? = this@filter.getData(name)?.takeIf { + predicate(name, it) + } + + override val updates: Flow = this@filter.updates.filter flowFilter@{ name -> + val theData = this@filter.getData(name) ?: return@flowFilter false + predicate(name, theData) + } +} + +/** + * Generate a wrapper data set with a given name prefix appended to all names + */ +public fun DataSet.withNamePrefix(prefix: Name): DataSet = if (prefix.isEmpty()) this +else object : ActiveDataSet { + override val dataType: KType get() = this@withNamePrefix.dataType + + override fun flow(): Flow> = this@withNamePrefix.flow().map { it.data.named(prefix + it.name) } + + override suspend fun getData(name: Name): Data? = + name.removeHeadOrNull(name)?.let { this@withNamePrefix.getData(it) } + + override val updates: Flow get() = this@withNamePrefix.updates.map { prefix + it } +} + +/** + * Get a subset of data starting with a given [branchName] + */ +public fun DataSet.branch(branchName: Name): DataSet = if (branchName.isEmpty()) { + this +} else object : ActiveDataSet { + override val dataType: KType get() = this@branch.dataType + + override fun flow(): Flow> = this@branch.flow().mapNotNull { + it.name.removeHeadOrNull(branchName)?.let { name -> + it.data.named(name) + } + } + + override suspend fun getData(name: Name): Data? = this@branch.getData(branchName + name) + + override val updates: Flow get() = this@branch.updates.mapNotNull { it.removeHeadOrNull(branchName) } +} + +public fun DataSet.branch(branchName: String): DataSet = this@branch.branch(branchName.toName()) + +@DFExperimental +public suspend fun DataSet.rootData(): Data? = getData(Name.EMPTY) + diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataSetMeta.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataSetMeta.kt new file mode 100644 index 00000000..5ca07aa5 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataSetMeta.kt @@ -0,0 +1,20 @@ +package hep.dataforge.data + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder + + +/** + * Get a metadata node for this set if it is present + */ +public suspend fun DataSet<*>.getMeta(): Meta? = getData(DataSet.META_KEY)?.meta + +/** + * Add meta-data node to a [DataSet] + */ +public suspend fun DataSetBuilder<*>.meta(meta: Meta): Unit = emit(DataSet.META_KEY, Data.empty(meta)) + +/** + * Add meta-data node to a [DataSet] + */ +public suspend fun DataSetBuilder<*>.meta(metaBuilder: MetaBuilder.() -> Unit): Unit = meta(Meta(metaBuilder)) \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataTransform.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataTransform.kt new file mode 100644 index 00000000..8b0c7787 --- /dev/null +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataTransform.kt @@ -0,0 +1,181 @@ +package hep.dataforge.data + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.seal +import hep.dataforge.meta.toMutableMeta +import hep.dataforge.misc.DFInternal +import kotlinx.coroutines.flow.* +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +/** + * Lazily transform this data to another data. By convention [block] should not use external data (be pure). + * @param coroutineContext additional [CoroutineContext] elements used for data computation. + * @param meta for the resulting data. By default equals input data. + * @param block the transformation itself + */ +public inline fun Data.map( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = this.meta, + crossinline block: suspend (T) -> R, +): Data = Data(meta, coroutineContext, listOf(this)) { + block(await()) +} + +/** + * Combine this data with the other data using [block]. See [map] for other details + */ +public inline fun Data.combine( + other: Data, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = this.meta, + crossinline block: suspend (left: T1, right: T2) -> R, +): Data = Data(meta, coroutineContext, listOf(this, other)) { + block(await(), other.await()) +} + + +//data collection operations + +/** + * Lazily reduce a collection of [Data] to a single data. + */ +public inline fun Collection>.reduceToData( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + crossinline block: suspend (Collection) -> R, +): Data = Data( + meta, + coroutineContext, + this +) { + block(map { it.await() }) +} + +@DFInternal +public fun Map>.reduceToData( + outputType: KType, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + block: suspend (Map) -> R, +): Data = Data( + outputType, + meta, + coroutineContext, + this.values +) { + block(mapValues { it.value.await() }) +} + + +/** + * Lazily reduce a [Map] of [Data] with any static key. + * @param K type of the map key + * @param T type of the input goal + * @param R type of the result goal + */ +public inline fun Map>.reduceToData( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + noinline block: suspend (Map) -> R, +): Data = Data( + meta, + coroutineContext, + this.values +) { + block(mapValues { it.value.await() }) +} + +//flow operations + +/** + * Transform a [Flow] of [NamedData] to a single [Data]. + */ +@DFInternal +public suspend fun Flow>.reduceToData( + outputType: KType, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + transformation: suspend (Flow>) -> R, +): Data = Data( + outputType, + meta, + coroutineContext, + toList() +) { + transformation(this) +} + +@OptIn(DFInternal::class) +public suspend inline fun Flow>.reduceToData( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + noinline transformation: suspend (Flow>) -> R, +): Data = reduceToData(typeOf(), coroutineContext, meta) { + transformation(it) +} + +/** + * Fold a flow of named data into a single [Data] + */ +public suspend inline fun Flow>.foldToData( + initial: R, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + noinline block: suspend (result: R, data: NamedData) -> R, +): Data = reduceToData( + coroutineContext, meta +) { + it.fold(initial, block) +} + +//DataSet operations + +@DFInternal +public suspend fun DataSet.map( + outputType: KType, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + metaTransform: MetaBuilder.() -> Unit = {}, + block: suspend (T) -> R, +): DataTree = DataTree(outputType) { + populate( + flow().map { + val newMeta = it.meta.toMutableMeta().apply(metaTransform).seal() + Data(outputType, newMeta, coroutineContext, listOf(it)) { + block(it.await()) + }.named(it.name) + } + ) +} + +@OptIn(DFInternal::class) +public suspend inline fun DataSet.map( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline metaTransform: MetaBuilder.() -> Unit = {}, + noinline block: suspend (T) -> R, +): DataTree = map(typeOf(), coroutineContext, metaTransform, block) + +public suspend fun DataSet.forEach(block: suspend (NamedData) -> Unit) { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + flow().collect { + block(it) + } +} + +public suspend inline fun DataSet.reduceToData( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + noinline transformation: suspend (Flow>) -> R, +): Data = flow().reduceToData(coroutineContext, meta, transformation) + +public suspend inline fun DataSet.foldToData( + initial: R, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + meta: Meta = Meta.EMPTY, + noinline block: suspend (result: R, data: NamedData) -> R, +): Data = flow().foldToData(initial, coroutineContext, meta, block) \ No newline at end of file diff --git a/dataforge-data/src/commonTest/kotlin/hep/dataforge/data/DataTreeBuilderTest.kt b/dataforge-data/src/commonTest/kotlin/hep/dataforge/data/DataTreeBuilderTest.kt deleted file mode 100644 index 32bf1760..00000000 --- a/dataforge-data/src/commonTest/kotlin/hep/dataforge/data/DataTreeBuilderTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package hep.dataforge.data - -import kotlin.test.Test -import kotlin.test.assertTrue - - -internal class DataTreeBuilderTest{ - @Test - fun testDataUpdate(){ - val updateData = DataNode{ - "update" put { - "a" put Data.static("a") - "b" put Data.static("b") - } - } - - val node = DataNode{ - node("primary"){ - static("a","a") - static("b","b") - } - static("root","root") - update(updateData) - } - - println(node.toMeta()) - - assertTrue { node["update.a"] != null } - assertTrue { node["primary.a"] != null } - - } -} \ No newline at end of file diff --git a/dataforge-data/src/jsMain/kotlin/hep/dataforge/data/dataJS.kt b/dataforge-data/src/jsMain/kotlin/hep/dataforge/data/dataJS.kt deleted file mode 100644 index 0257b85a..00000000 --- a/dataforge-data/src/jsMain/kotlin/hep/dataforge/data/dataJS.kt +++ /dev/null @@ -1,14 +0,0 @@ -package hep.dataforge.data - -import kotlin.reflect.KClass - -/** - * Check that node is compatible with given type meaning that each element could be cast to the type - */ -internal actual fun DataNode<*>.canCast(type: KClass): Boolean { - return this.type == type -} - -internal actual fun Data<*>.canCast(type: KClass): Boolean { - return this.type == type -} diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt deleted file mode 100644 index b67becff..00000000 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt +++ /dev/null @@ -1,27 +0,0 @@ -package hep.dataforge.data - -import hep.dataforge.meta.Meta -import hep.dataforge.names.NameToken -import kotlin.reflect.KClass - - -/** - * A zero-copy data node wrapper that returns only children with appropriate type. - */ -public class TypeFilteredDataNode(public val origin: DataNode<*>, override val type: KClass) : DataNode { - override val meta: Meta get() = origin.meta - override val items: Map> by lazy { - origin.items.mapNotNull { (key, item) -> - when (item) { - is DataItem.Leaf -> { - (item.data.filterIsInstance(type))?.let { - key to DataItem.Leaf(it) - } - } - is DataItem.Node -> { - key to DataItem.Node(item.node.filterIsInstance(type)) - } - } - }.associate { it } - } -} \ No newline at end of file diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt deleted file mode 100644 index 6f758dae..00000000 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt +++ /dev/null @@ -1,48 +0,0 @@ -package hep.dataforge.data - -import kotlinx.coroutines.runBlocking -import kotlin.reflect.KClass -import kotlin.reflect.full.isSubclassOf - -/** - * Block the thread and get data content - */ -public fun Data.get(): T = runBlocking { await() } - -/** - * Check that node is compatible with given type meaning that each element could be cast to the type - */ -internal actual fun DataNode<*>.canCast(type: KClass): Boolean = - type.isSubclassOf(this.type) - -internal actual fun Data<*>.canCast(type: KClass): Boolean = - this.type.isSubclassOf(type) - -/** - * Cast the node to given type if the cast is possible or return null - */ -public fun Data<*>.filterIsInstance(type: KClass): Data? = - if (canCast(type)) cast(type) else null - -/** - * Filter a node by data and node type. Resulting node and its subnodes is guaranteed to have border type [type], - * but could contain empty nodes - */ -public fun DataNode<*>.filterIsInstance(type: KClass): DataNode { - return when { - canCast(type) -> cast(type) - this is TypeFilteredDataNode -> origin.filterIsInstance(type) - else -> TypeFilteredDataNode(this, type) - } -} - -/** - * Filter all elements of given data item that could be cast to given type. If no elements are available, return null. - */ -public fun DataItem<*>?.filterIsInstance(type: KClass): DataItem? = when (this) { - null -> null - is DataItem.Node -> DataItem.Node(this.node.filterIsInstance(type)) - is DataItem.Leaf -> this.data.filterIsInstance(type)?.let { DataItem.Leaf(it) } -} - -public inline fun DataItem<*>?.filterIsInstance(): DataItem? = this@filterIsInstance.filterIsInstance(R::class) \ No newline at end of file diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/select.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/select.kt new file mode 100644 index 00000000..02672c37 --- /dev/null +++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/select.kt @@ -0,0 +1,64 @@ +package hep.dataforge.data + +import hep.dataforge.misc.DFExperimental +import hep.dataforge.names.Name +import hep.dataforge.names.matches +import hep.dataforge.names.toName +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlin.reflect.KType +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.typeOf + + +/** + * Cast the node to given type if the cast is possible or return null + */ +@Suppress("UNCHECKED_CAST") +private fun Data<*>.castOrNull(type: KType): Data? = + if (!this.type.isSubtypeOf(type)) null else object : Data by (this as Data) { + override val type: KType = type + } + +/** + * Select all data matching given type and filters. Does not modify paths + */ +@OptIn(DFExperimental::class) +@PublishedApi +internal fun DataSet<*>.select( + type: KType, + namePattern: Name? = null, +): ActiveDataSet = object : ActiveDataSet { + override val dataType = type + + + override fun flow(): Flow> = this@select.flow().filter { datum -> + datum.type.isSubtypeOf(type) && (namePattern == null || datum.name.matches(namePattern)) + }.map { + @Suppress("UNCHECKED_CAST") + it as NamedData + } + + override suspend fun getData(name: Name): Data? = this@select.getData(name)?.castOrNull(type) + + override val updates: Flow = this@select.updates.filter { + val datum = this@select.getData(it) + datum?.type?.isSubtypeOf(type) ?: false + } + +} + +/** + * Select a single datum of the appropriate type + */ +public inline fun DataSet<*>.select(namePattern: Name? = null): DataSet = + select(typeOf(), namePattern) + +public suspend fun DataSet<*>.selectOne(type: KType, name: Name): NamedData? = + getData(name)?.castOrNull(type)?.named(name) + +public suspend inline fun DataSet<*>.selectOne(name: Name): NamedData? = selectOne(typeOf(), name) + +public suspend inline fun DataSet<*>.selectOne(name: String): NamedData? = + selectOne(typeOf(), name.toName()) \ No newline at end of file diff --git a/dataforge-data/src/jvmTest/kotlin/hep/dataforge/data/ActionsTest.kt b/dataforge-data/src/jvmTest/kotlin/hep/dataforge/data/ActionsTest.kt new file mode 100644 index 00000000..0da9c2c2 --- /dev/null +++ b/dataforge-data/src/jvmTest/kotlin/hep/dataforge/data/ActionsTest.kt @@ -0,0 +1,42 @@ +package hep.dataforge.data + +import hep.dataforge.actions.Action +import hep.dataforge.actions.map +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +@Suppress("EXPERIMENTAL_API_USAGE") +class ActionsTest { + val data: DataTree = runBlocking { + DataTree { + repeat(10) { + static(it.toString(), it) + } + } + } + + @Test + fun testStaticMapAction() { + val plusOne = Action.map { + result { it + 1 } + } + runBlocking { + val result = plusOne.execute(data) + assertEquals(2, result.getData("1")?.await()) + } + } + + @Test + fun testDynamicMapAction() { + val plusOne = Action.map { + result { it + 1 } + } + val datum = runBlocking { + val result = plusOne.execute(data, scope = this) + result.getData("1")?.await() + } + assertEquals(2, datum) + } + +} \ No newline at end of file diff --git a/dataforge-data/src/jvmTest/kotlin/hep/dataforge/data/DataTreeBuilderTest.kt b/dataforge-data/src/jvmTest/kotlin/hep/dataforge/data/DataTreeBuilderTest.kt new file mode 100644 index 00000000..0225e9cf --- /dev/null +++ b/dataforge-data/src/jvmTest/kotlin/hep/dataforge/data/DataTreeBuilderTest.kt @@ -0,0 +1,90 @@ +package hep.dataforge.data + +import hep.dataforge.names.toName +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.collect +import kotlin.test.Test +import kotlin.test.assertEquals + + +internal class DataTreeBuilderTest { + @Test + fun testTreeBuild() = runBlocking { + val node = DataTree { + "primary" put { + static("a", "a") + static("b", "b") + } + static("c.d", "c.d") + static("c.f", "c.f") + } + runBlocking { + assertEquals("a", node.getData("primary.a")?.await()) + assertEquals("b", node.getData("primary.b")?.await()) + assertEquals("c.d", node.getData("c.d")?.await()) + assertEquals("c.f", node.getData("c.f")?.await()) + } + } + + @Test + fun testDataUpdate() = runBlocking { + val updateData: DataTree = DataTree { + "update" put { + "a" put Data.static("a") + "b" put Data.static("b") + } + } + + val node = DataTree { + "primary" put { + static("a", "a") + static("b", "b") + } + static("root", "root") + populate(updateData) + } + + runBlocking { + assertEquals("a", node.getData("update.a")?.await()) + assertEquals("a", node.getData("primary.a")?.await()) + } + } + + @Test + fun testDynamicUpdates() = runBlocking { + try { + lateinit var updateJob: Job + supervisorScope { + val subNode = ActiveDataTree { + updateJob = launch { + repeat(10) { + delay(10) + static("value", it) + } + delay(10) + } + } + launch { + subNode.updatesWithData.collect { + println(it) + } + } + val rootNode = ActiveDataTree { + setAndObserve("sub".toName(), subNode) + } + + launch { + rootNode.updatesWithData.collect { + println(it) + } + } + updateJob.join() + assertEquals(9, rootNode.getData("sub.value")?.await()) + cancel() + } + } catch (t: Throwable) { + if (t !is CancellationException) throw t + } + + } +} \ No newline at end of file diff --git a/dataforge-data/src/nativeMain/kotlin/hep/dataforge/data/dataNative.kt b/dataforge-data/src/nativeMain/kotlin/hep/dataforge/data/dataNative.kt deleted file mode 100644 index fe770f88..00000000 --- a/dataforge-data/src/nativeMain/kotlin/hep/dataforge/data/dataNative.kt +++ /dev/null @@ -1,14 +0,0 @@ -package hep.dataforge.data - -import kotlin.reflect.KClass - -/** - * Check that node is compatible with given type meaning that each element could be cast to the type - */ -internal actual fun DataNode<*>.canCast(type: KClass): Boolean { - return this.type == type -} - -internal actual fun Data<*>.canCast(type: KClass): Boolean { - return this.type == type -} \ No newline at end of file diff --git a/dataforge-io/api/dataforge-io.api b/dataforge-io/api/dataforge-io.api index 1b8f876b..2ef69061 100644 --- a/dataforge-io/api/dataforge-io.api +++ b/dataforge-io/api/dataforge-io.api @@ -7,7 +7,7 @@ public final class hep/dataforge/io/BinaryMetaFormat : hep/dataforge/io/MetaForm public fun invoke (Lhep/dataforge/meta/Meta;Lhep/dataforge/context/Context;)Lhep/dataforge/io/MetaFormat; public synthetic fun invoke (Lhep/dataforge/meta/Meta;Lhep/dataforge/context/Context;)Ljava/lang/Object; public fun readMeta (Lkotlinx/io/Input;Lhep/dataforge/meta/descriptors/NodeDescriptor;)Lhep/dataforge/meta/Meta; - public final fun readMetaItem (Lkotlinx/io/Input;)Lhep/dataforge/meta/MetaItem; + public final fun readMetaItem (Lkotlinx/io/Input;)Lhep/dataforge/meta/TypedMetaItem; public fun readObject (Lkotlinx/io/Input;)Lhep/dataforge/meta/Meta; public synthetic fun readObject (Lkotlinx/io/Input;)Ljava/lang/Object; public fun toMeta ()Lhep/dataforge/meta/Meta; @@ -77,6 +77,10 @@ public final class hep/dataforge/io/EnvelopeBuilder : hep/dataforge/io/Envelope public final fun setType (Ljava/lang/String;)V } +public final class hep/dataforge/io/EnvelopeBuilderKt { + public static final fun Envelope (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/io/Envelope; +} + public abstract interface class hep/dataforge/io/EnvelopeFormat : hep/dataforge/io/IOFormat { public abstract fun getDefaultMetaFormat ()Lhep/dataforge/io/MetaFormatFactory; public abstract fun readObject (Lkotlinx/io/Input;)Lhep/dataforge/io/Envelope; @@ -215,8 +219,8 @@ public final class hep/dataforge/io/IOPlugin : hep/dataforge/context/AbstractPlu public final fun getIoFormatFactories ()Ljava/util/Collection; public final fun getMetaFormatFactories ()Ljava/util/Collection; public fun getTag ()Lhep/dataforge/context/PluginTag; - public final fun resolveEnvelopeFormat (Lhep/dataforge/meta/MetaItem;)Lhep/dataforge/io/EnvelopeFormat; - public final fun resolveIOFormat (Lhep/dataforge/meta/MetaItem;Lkotlin/reflect/KClass;)Lhep/dataforge/io/IOFormat; + public final fun resolveEnvelopeFormat (Lhep/dataforge/meta/TypedMetaItem;)Lhep/dataforge/io/EnvelopeFormat; + public final fun resolveIOFormat (Lhep/dataforge/meta/TypedMetaItem;Lkotlin/reflect/KClass;)Lhep/dataforge/io/IOFormat; public final fun resolveMetaFormat (Ljava/lang/String;Lhep/dataforge/meta/Meta;)Lhep/dataforge/io/MetaFormat; public final fun resolveMetaFormat (SLhep/dataforge/meta/Meta;)Lhep/dataforge/io/MetaFormat; public static synthetic fun resolveMetaFormat$default (Lhep/dataforge/io/IOPlugin;Ljava/lang/String;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)Lhep/dataforge/io/MetaFormat; diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index d2e3a41a..01f749cb 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -22,4 +22,8 @@ kotlin { } } } +} + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE } \ No newline at end of file diff --git a/dataforge-io/dataforge-io-yaml/api/dataforge-io-yaml.api b/dataforge-io/dataforge-io-yaml/api/dataforge-io-yaml.api index de7b3ab2..61c5dea9 100644 --- a/dataforge-io/dataforge-io-yaml/api/dataforge-io-yaml.api +++ b/dataforge-io/dataforge-io-yaml/api/dataforge-io-yaml.api @@ -57,3 +57,10 @@ public final class hep/dataforge/io/yaml/YamlMetaFormat$Companion : hep/dataforg public synthetic fun writeObject (Lkotlinx/io/Output;Ljava/lang/Object;)V } +public final class hep/dataforge/io/yaml/YamlMetaFormatKt { + public static final fun toMeta (Lnet/mamoe/yamlkt/YamlMap;)Lhep/dataforge/meta/Meta; + public static final fun toMetaItem (Lnet/mamoe/yamlkt/YamlElement;Lhep/dataforge/meta/descriptors/ItemDescriptor;)Lhep/dataforge/meta/TypedMetaItem; + public static synthetic fun toMetaItem$default (Lnet/mamoe/yamlkt/YamlElement;Lhep/dataforge/meta/descriptors/ItemDescriptor;ILjava/lang/Object;)Lhep/dataforge/meta/TypedMetaItem; + public static final fun toYaml (Lhep/dataforge/meta/Meta;)Lnet/mamoe/yamlkt/YamlMap; +} + diff --git a/dataforge-io/dataforge-io-yaml/build.gradle.kts b/dataforge-io/dataforge-io-yaml/build.gradle.kts index 720e1fba..4ee1029a 100644 --- a/dataforge-io/dataforge-io-yaml/build.gradle.kts +++ b/dataforge-io/dataforge-io-yaml/build.gradle.kts @@ -1,16 +1,33 @@ plugins { - id("ru.mipt.npm.jvm") + id("ru.mipt.npm.mpp") +// id("ru.mipt.npm.native") } description = "YAML meta IO" kscience { - useSerialization { - yaml() + useSerialization{ + yamlKt("0.9.0-dev-1") } } -dependencies { - api(project(":dataforge-io")) - api("org.yaml:snakeyaml:1.26") +repositories{ + maven("https://dl.bintray.com/mamoe/yamlkt") +} + +kotlin { + sourceSets { + commonMain{ + dependencies { + api(project(":dataforge-io")) + } + } + } +} + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE + description =""" + YAML meta converters and Front Matter envelope format + """.trimIndent() } diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt similarity index 93% rename from dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt rename to dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt index aa2537be..9a6d38f8 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt @@ -4,8 +4,8 @@ import hep.dataforge.context.Context import hep.dataforge.io.* import hep.dataforge.io.IOFormat.Companion.META_KEY import hep.dataforge.io.IOFormat.Companion.NAME_KEY -import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta +import hep.dataforge.misc.DFExperimental import kotlinx.io.* import kotlinx.io.text.readUtf8Line import kotlinx.io.text.writeUtf8String @@ -17,11 +17,11 @@ public class FrontMatterEnvelopeFormat( ) : EnvelopeFormat { override fun readPartial(input: Input): PartialEnvelope { - var line = "" + var line: String var offset = 0u do { line = input.readUtf8Line() //?: error("Input does not contain front matter separator") - offset += line.toByteArray().size.toUInt() + offset += line.encodeToByteArray().size.toUInt() } while (!line.startsWith(SEPARATOR)) val readMetaFormat = @@ -33,7 +33,7 @@ public class FrontMatterEnvelopeFormat( do { line = input.readUtf8Line() writeUtf8String(line + "\r\n") - offset += line.toByteArray().size.toUInt() + offset += line.encodeToByteArray().size.toUInt() } while (!line.startsWith(SEPARATOR)) }.read { readMetaFormat.readMeta(input) @@ -43,7 +43,7 @@ public class FrontMatterEnvelopeFormat( } override fun readObject(input: Input): Envelope { - var line = "" + var line: String do { line = input.readUtf8Line() //?: error("Input does not contain front matter separator") } while (!line.startsWith(SEPARATOR)) @@ -86,7 +86,7 @@ public class FrontMatterEnvelopeFormat( } public companion object : EnvelopeFormatFactory { - public const val SEPARATOR = "---" + public const val SEPARATOR: String = "---" private val metaTypeRegex = "---(\\w*)\\s*".toRegex() diff --git a/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt new file mode 100644 index 00000000..9f5cdda3 --- /dev/null +++ b/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt @@ -0,0 +1,122 @@ +package hep.dataforge.io.yaml + +import hep.dataforge.context.Context +import hep.dataforge.io.IOFormat.Companion.META_KEY +import hep.dataforge.io.IOFormat.Companion.NAME_KEY +import hep.dataforge.io.MetaFormat +import hep.dataforge.io.MetaFormatFactory +import hep.dataforge.meta.* +import hep.dataforge.meta.descriptors.ItemDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.misc.DFExperimental +import hep.dataforge.names.NameToken +import hep.dataforge.names.withIndex +import hep.dataforge.values.ListValue +import hep.dataforge.values.Null +import hep.dataforge.values.parseValue +import kotlinx.io.Input +import kotlinx.io.Output +import kotlinx.io.text.readUtf8String +import kotlinx.io.text.writeUtf8String +import net.mamoe.yamlkt.* + +public fun Meta.toYaml(): YamlMap { + val map: Map = items.entries.associate { (key, item) -> + key.toString() to when (item) { + is MetaItemValue -> { + item.value.value + } + is MetaItemNode -> { + item.node.toYaml() + } + } + } + return YamlMap(map) +} + +private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : MetaBase() { + + private fun buildItems(): Map { + val map = LinkedHashMap() + + yamlMap.content.entries.forEach { (key, value) -> + val stringKey = key.toString() + val itemDescriptor = descriptor?.items?.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() + is YamlList -> if (value.all { it is YamlLiteral }) { + val listValue = ListValue( + value.map { + //We already checked that all values are primitives + (it as YamlLiteral).content.parseValue() + } + ) + map[token] = MetaItemValue(listValue) + } else value.forEachIndexed { index, yamlElement -> + val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: ItemDescriptor.DEFAULT_INDEX_KEY + 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) + } + } + } + return map + } + + override val items: Map get() = buildItems() +} + +public fun YamlElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem = when (this) { + YamlNull -> Null.asMetaItem() + is YamlLiteral -> content.parseValue().asMetaItem() + is YamlMap -> toMeta().asMetaItem() + //We can't return multiple items therefore we create top level node + is YamlList -> YamlMap(mapOf("@yamlArray" to this)).toMetaItem(descriptor) +} + +public fun YamlMap.toMeta(): Meta = YamlMeta(this) + + +/** + * Represent meta as Yaml + */ +@DFExperimental +public class YamlMetaFormat(private val meta: Meta) : MetaFormat { + + override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) { + val yaml = meta.toYaml() + val string = Yaml.encodeToString(yaml) + output.writeUtf8String(string) + } + + override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { + val yaml = Yaml.decodeYamlMapFromString(input.readUtf8String()) + return yaml.toMeta() + } + + override fun toMeta(): Meta = Meta { + NAME_KEY put FrontMatterEnvelopeFormat.name.toString() + META_KEY put meta + } + + public companion object : MetaFormatFactory { + override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta) + + override val shortName: String = "yaml" + + override val key: Short = 0x594d //YM + + private val default = YamlMetaFormat() + + override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?): Unit = + default.writeMeta(output, meta, descriptor) + + override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta = + default.readMeta(input, descriptor) + } +} \ No newline at end of file diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/commonTest/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt similarity index 96% rename from dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt rename to dataforge-io/dataforge-io-yaml/src/commonTest/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt index 83f6d3a6..d63da939 100644 --- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt +++ b/dataforge-io/dataforge-io-yaml/src/commonTest/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt @@ -2,10 +2,10 @@ package hep.dataforge.io.yaml import hep.dataforge.io.parse import hep.dataforge.io.toString -import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta import hep.dataforge.meta.get import hep.dataforge.meta.seal +import hep.dataforge.misc.DFExperimental import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt deleted file mode 100644 index 18e8af06..00000000 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt +++ /dev/null @@ -1,56 +0,0 @@ -package hep.dataforge.io.yaml - -import hep.dataforge.context.Context -import hep.dataforge.io.IOFormat.Companion.META_KEY -import hep.dataforge.io.IOFormat.Companion.NAME_KEY -import hep.dataforge.io.MetaFormat -import hep.dataforge.io.MetaFormatFactory -import hep.dataforge.meta.DFExperimental -import hep.dataforge.meta.Meta -import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.toMap -import hep.dataforge.meta.toMeta -import kotlinx.io.Input -import kotlinx.io.Output -import kotlinx.io.asInputStream -import kotlinx.io.text.writeUtf8String -import org.yaml.snakeyaml.Yaml - -/** - * Represent meta as Yaml - */ -@DFExperimental -public class YamlMetaFormat(private val meta: Meta) : MetaFormat { - private val yaml = Yaml() - - override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) { - val string = yaml.dump(meta.toMap(descriptor)) - output.writeUtf8String(string) - } - - override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { - val map: Map = yaml.load(input.asInputStream()) - return map.toMeta(descriptor) - } - - override fun toMeta(): Meta = Meta{ - NAME_KEY put FrontMatterEnvelopeFormat.name.toString() - META_KEY put meta - } - - public companion object : MetaFormatFactory { - override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta) - - override val shortName: String = "yaml" - - override val key: Short = 0x594d //YM - - private val default = YamlMetaFormat() - - override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?): Unit = - default.writeMeta(output, meta, descriptor) - - override fun readMeta(input: kotlinx.io.Input, descriptor: NodeDescriptor?): Meta = - default.readMeta(input, descriptor) - } -} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt index 547d567c..43ee98b8 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt @@ -1,11 +1,8 @@ package hep.dataforge.io import hep.dataforge.context.Context -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder -import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.* import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.setItem import hep.dataforge.values.* import kotlinx.io.* import kotlinx.io.text.readUtf8String @@ -22,7 +19,7 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory { override fun invoke(meta: Meta, context: Context): MetaFormat = this override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { - return (input.readMetaItem() as MetaItem.NodeItem).node + return (input.readMetaItem() as MetaItemNode).node } private fun Output.writeChar(char: Char) = writeByte(char.toByte()) @@ -32,67 +29,66 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory { writeUtf8String(str) } - public fun Output.writeValue(value: Value) { - if (value.isList()) { + 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) } - } else when (value.type) { - ValueType.NUMBER -> when (value.value) { - is Short -> { - writeChar('s') - writeShort(value.number.toShort()) - } - is Int -> { - writeChar('i') - writeInt(value.number.toInt()) - } - is Long -> { - writeChar('l') - writeLong(value.number.toLong()) - } - is Float -> { - writeChar('f') - writeFloat(value.number.toFloat()) - } - else -> { - writeChar('d') - writeDouble(value.number.toDouble()) - } - } - ValueType.STRING -> { - writeChar('S') - writeString(value.string) - } - ValueType.BOOLEAN -> { - if (value.boolean) { - writeChar('+') - } else { - writeChar('-') - } - } - ValueType.NULL -> { - writeChar('N') - } } } override fun writeMeta( output: kotlinx.io.Output, meta: hep.dataforge.meta.Meta, - descriptor: hep.dataforge.meta.descriptors.NodeDescriptor? + descriptor: hep.dataforge.meta.descriptors.NodeDescriptor?, ) { output.writeChar('M') output.writeInt(meta.items.size) meta.items.forEach { (key, item) -> output.writeString(key.toString()) when (item) { - is MetaItem.ValueItem -> { + is MetaItemValue -> { output.writeValue(item.value) } - is MetaItem.NodeItem -> { + is MetaItemNode -> { writeObject(output, item.node) } } @@ -105,21 +101,21 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory { } @Suppress("UNCHECKED_CAST") - public fun Input.readMetaItem(): MetaItem { + public fun Input.readMetaItem(): TypedMetaItem { return when (val keyChar = readByte().toChar()) { - 'S' -> MetaItem.ValueItem(StringValue(readString())) - 'N' -> MetaItem.ValueItem(Null) - '+' -> MetaItem.ValueItem(True) - '-' -> MetaItem.ValueItem(True) - 's' -> MetaItem.ValueItem(NumberValue(readShort())) - 'i' -> MetaItem.ValueItem(NumberValue(readInt())) - 'l' -> MetaItem.ValueItem(NumberValue(readInt())) - 'f' -> MetaItem.ValueItem(NumberValue(readFloat())) - 'd' -> MetaItem.ValueItem(NumberValue(readDouble())) + '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 MetaItem.ValueItem).value } - MetaItem.ValueItem(Value.of(list)) + val list = (1..length).map { (readMetaItem() as MetaItemValue).value } + MetaItemValue(Value.of(list)) } 'M' -> { val length = readInt() @@ -127,10 +123,10 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory { (1..length).forEach { _ -> val name = readString() val item = readMetaItem() - setItem(name, item) + set(name, item) } } - MetaItem.NodeItem(meta) + MetaItemNode(meta) } else -> error("Unknown serialization key character: $keyChar") } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Consumer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Consumer.kt index 0ef5d327..51ec6250 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Consumer.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Consumer.kt @@ -1,6 +1,6 @@ package hep.dataforge.io -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental /** * A fire-and-forget consumer of messages diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index 992a080f..123da62c 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -29,6 +29,7 @@ public interface Envelope { /** * Build a static envelope using provided builder */ + @Deprecated("Use top level function instead") public inline operator fun invoke(block: EnvelopeBuilder.() -> Unit): Envelope = EnvelopeBuilder().apply(block).seal() } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt index 73775af3..20710198 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -44,6 +44,12 @@ public class EnvelopeBuilder : Envelope { } + +/** + * Build a static envelope using provided [builder] + */ +public inline fun Envelope(builder: EnvelopeBuilder.() -> Unit): Envelope = EnvelopeBuilder().apply(builder).seal() + //@ExperimentalContracts //suspend fun EnvelopeBuilder.buildData(block: suspend Output.() -> Unit): Binary{ // contract { diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt index b02052c1..085c1cde 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt @@ -3,12 +3,13 @@ package hep.dataforge.io import hep.dataforge.context.Context import hep.dataforge.io.EnvelopeFormatFactory.Companion.ENVELOPE_FORMAT_TYPE import hep.dataforge.meta.Meta +import hep.dataforge.misc.Type import hep.dataforge.names.Name import hep.dataforge.names.asName -import hep.dataforge.provider.Type import kotlinx.io.Input import kotlinx.io.Output -import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * A partially read envelope with meta, but without data @@ -16,6 +17,8 @@ import kotlin.reflect.KClass public data class PartialEnvelope(val meta: Meta, val dataOffset: UInt, val dataSize: ULong?) public interface EnvelopeFormat : IOFormat { + override val type: KType get() = typeOf() + public val defaultMetaFormat: MetaFormatFactory get() = JsonMetaFormat public fun readPartial(input: Input): PartialEnvelope @@ -37,7 +40,7 @@ public fun EnvelopeFormat.read(input: Input): Envelope = readObject(input) @Type(ENVELOPE_FORMAT_TYPE) public interface EnvelopeFormatFactory : IOFormatFactory, EnvelopeFormat { override val name: Name get() = "envelope".asName() - override val type: KClass get() = Envelope::class + override val type: KType get() = typeOf() override fun invoke(meta: Meta, context: Context): EnvelopeFormat diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index cae87229..45bfd988 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -10,13 +10,14 @@ import hep.dataforge.io.PartDescriptor.Companion.SEPARATOR_KEY import hep.dataforge.meta.* import hep.dataforge.names.asName import hep.dataforge.names.plus +import hep.dataforge.names.toName import kotlinx.io.Binary import kotlinx.io.writeBinary private class PartDescriptor : Scheme() { var offset by int(0) var size by int(0) - var meta by node() + var partMeta by node("meta".toName()) companion object : SchemeSpec(::PartDescriptor) { val MULTIPART_KEY = ENVELOPE_NODE_KEY + "multipart" @@ -48,7 +49,7 @@ public fun EnvelopeBuilder.multipart( PartDescriptor { offset = offsetCounter size = binary.size - meta = description + partMeta = description }.also { offsetCounter += binary.size } @@ -95,7 +96,7 @@ public fun Envelope.parts(): EnvelopeParts { } else { parts.map { val binary = data!!.view(it.offset, it.size) - val meta = Laminate(it.meta, meta[MULTIPART_KEY].node) + val meta = Laminate(it.partMeta, meta[MULTIPART_KEY].node) EnvelopePart(binary, meta) } } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt index 7fe4c970..6ba7d6e3 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt @@ -2,25 +2,28 @@ package hep.dataforge.io import hep.dataforge.context.Context import hep.dataforge.context.Factory -import hep.dataforge.context.Named import hep.dataforge.io.IOFormat.Companion.NAME_KEY import hep.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.MetaItemValue import hep.dataforge.meta.MetaRepr +import hep.dataforge.misc.Named +import hep.dataforge.misc.Type import hep.dataforge.names.Name import hep.dataforge.names.asName -import hep.dataforge.provider.Type import hep.dataforge.values.Value import kotlinx.io.* import kotlinx.io.buffer.Buffer import kotlinx.io.pool.ObjectPool -import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * And interface for reading and writing objects into with IO streams */ public interface IOFormat : MetaRepr { + public val type: KType + public fun writeObject(output: Output, obj: T) public fun readObject(input: Input): T @@ -42,10 +45,14 @@ public fun Binary.readWith(format: IOFormat): T = read { public fun Output.writeWith(format: IOFormat, obj: T): Unit = format.run { writeObject(this@writeWith, obj) } -public class ListIOFormat(public val format: IOFormat) : IOFormat> { +public inline fun IOFormat.Companion.listOf( + format: IOFormat, +): IOFormat> = object : IOFormat> { + override val type: KType = typeOf>() + override fun writeObject(output: Output, obj: List) { output.writeInt(obj.size) - this.format.run { + format.run { obj.forEach { writeObject(output, it) } @@ -63,9 +70,8 @@ public class ListIOFormat(public val format: IOFormat) : IOFormat

  • IOFormat.list: ListIOFormat get() = ListIOFormat(this) +} public fun ObjectPool.fill(block: Buffer.() -> Unit): Buffer { val buffer = borrow() @@ -82,7 +88,7 @@ public interface IOFormatFactory : Factory>, Named, MetaRep /** * Explicit type for dynamic type checks */ - public val type: KClass + public val type: KType override fun toMeta(): Meta = Meta { NAME_KEY put name.toString() @@ -100,7 +106,7 @@ public object DoubleIOFormat : IOFormat, IOFormatFactory { override val name: Name = "double".asName() - override val type: KClass get() = Double::class + override val type: KType get() = typeOf() override fun writeObject(output: Output, obj: kotlin.Double) { output.writeDouble(obj) @@ -114,14 +120,14 @@ public object ValueIOFormat : IOFormat, IOFormatFactory { override val name: Name = "value".asName() - override val type: KClass get() = Value::class + override val type: KType get() = typeOf() override fun writeObject(output: Output, obj: Value) { BinaryMetaFormat.run { output.writeValue(obj) } } override fun readObject(input: Input): Value { - return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItem.ValueItem)?.value + return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value ?: error("The item is not a value") } } \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt index f0c47609..ad4341e1 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt @@ -18,7 +18,7 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) { context.gather>(IO_FORMAT_TYPE).values } - public fun resolveIOFormat(item: MetaItem<*>, type: KClass): IOFormat? { + public fun resolveIOFormat(item: MetaItem, type: KClass): IOFormat? { val key = item.string ?: item.node[NAME_KEY]?.string ?: error("Format name not defined") val name = key.toName() return ioFormatFactories.find { it.name == name }?.let { @@ -46,7 +46,7 @@ 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? { + 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) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt index 2be2fe51..255a08e5 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt @@ -16,12 +16,16 @@ import kotlinx.io.text.readUtf8String import kotlinx.io.text.writeUtf8String import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * A Json format for Meta representation */ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat { + override val type: KType get() = typeOf() + override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) { val jsonObject = meta.toJson(descriptor) output.writeUtf8String(json.encodeToString(JsonObject.serializer(), jsonObject)) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt index f3e26d2a..bb59e1a0 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt @@ -4,20 +4,22 @@ import hep.dataforge.context.Context import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE import hep.dataforge.meta.Meta import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.misc.Type import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.names.plus -import hep.dataforge.provider.Type import kotlinx.io.ByteArrayInput import kotlinx.io.Input import kotlinx.io.Output import kotlinx.io.use -import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * A format for meta serialization */ public interface MetaFormat : IOFormat { + override val type: KType get() = typeOf() override fun writeObject(output: Output, obj: Meta) { writeMeta(output, obj, null) @@ -40,7 +42,7 @@ public interface MetaFormatFactory : IOFormatFactory, MetaFormat { override val name: Name get() = "meta".asName() + shortName - override val type: KClass get() = Meta::class + override val type: KType get() = typeOf() public val key: Short get() = name.hashCode().toShort() diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt index 8bcc54cd..2ce87c19 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertEquals class EnvelopeFormatTest { - val envelope = Envelope.invoke { + val envelope = Envelope { type = "test.format" meta{ "d" put 22.2 diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt index cb8ba54b..9e71a907 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt @@ -2,6 +2,8 @@ package hep.dataforge.io import hep.dataforge.meta.* import hep.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY +import hep.dataforge.values.ListValue +import hep.dataforge.values.number import kotlinx.io.asBinary import kotlinx.serialization.json.* import kotlin.test.Test @@ -78,4 +80,27 @@ class MetaFormatTest { assertEquals(listOf(1.0, 2.0, 3.0), meta["$JSON_ARRAY_KEY[2"].value?.list?.map { it.number.toDouble() }) } + @Test + fun testJsonStringToMeta(){ + val jsonString = """ + { + "comments": [ + ], + "end_time": "2018-04-13T22:01:46", + "format_description": "https://docs.google.com/document/d/12qmnZRO55y6zr08Wf-BQYAmklqgf5y3j_gD_VkNscXc/edit?usp=sharing", + "iteration_info": { + "iteration": 4, + "reverse": false + }, + "operator": "Vasiliy", + "programm_revision": "1.1.1-79-g7c0cad6", + "start_time": "2018-04-13T21:42:04", + "type": "info_file" + } + """.trimIndent() + val json = Json.parseToJsonElement(jsonString) + val meta = json.toMetaItem().node!! + assertEquals(ListValue.EMPTY, meta["comments"].value) + } + } \ No newline at end of file diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt index 23fef5d5..bf53aed9 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt @@ -1,8 +1,8 @@ package hep.dataforge.io import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MetaSerializer +import hep.dataforge.meta.TypedMetaItem import hep.dataforge.names.Name import hep.dataforge.names.toName import kotlinx.serialization.ExperimentalSerializationApi @@ -52,7 +52,7 @@ class MetaSerializerTest { @OptIn(ExperimentalSerializationApi::class) @Test fun testMetaItemDescriptor() { - val descriptor = MetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0) + val descriptor = TypedMetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0) println(descriptor) } } \ No newline at end of file diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt index 1b239edd..e511a472 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt @@ -1,9 +1,9 @@ package hep.dataforge.io import hep.dataforge.context.Global -import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.get import hep.dataforge.meta.int +import hep.dataforge.misc.DFExperimental import kotlinx.io.text.writeUtf8String import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index 0e53ed89..54cf6b2c 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -1,14 +1,15 @@ package hep.dataforge.io -import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.isEmpty +import hep.dataforge.misc.DFExperimental import kotlinx.io.* import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption -import kotlin.reflect.full.isSuperclassOf +import kotlin.reflect.full.isSupertypeOf +import kotlin.reflect.typeOf import kotlin.streams.asSequence public fun Path.read(block: Input.() -> R): R = asBinary().read(block = block) @@ -59,7 +60,7 @@ public fun Path.readEnvelope(format: EnvelopeFormat): Envelope { @Suppress("UNCHECKED_CAST") @DFExperimental public inline fun IOPlugin.resolveIOFormat(): IOFormat? { - return ioFormatFactories.find { it.type.isSuperclassOf(T::class) } as IOFormat? + return ioFormatFactories.find { it.type.isSupertypeOf(typeOf())} as IOFormat? } /** @@ -115,18 +116,18 @@ public fun IOPlugin.writeMetaFile( * Return inferred [EnvelopeFormat] if only one format could read given file. If no format accepts file, return null. If * multiple formats accepts file, throw an error. */ -public fun IOPlugin.peekBinaryFormat(path: Path): EnvelopeFormat? { +public fun IOPlugin.peekFileEnvelopeFormat(path: Path): EnvelopeFormat? { val binary = path.asBinary() val formats = envelopeFormatFactories.mapNotNull { factory -> binary.read { - factory.peekFormat(this@peekBinaryFormat, this@read) + factory.peekFormat(this@peekFileEnvelopeFormat, this@read) } } return when (formats.size) { 0 -> null 1 -> formats.first() - else -> error("Envelope format binary recognition clash") + else -> error("Envelope format binary recognition clash: $formats") } } @@ -137,10 +138,10 @@ public val IOPlugin.Companion.DATA_FILE_NAME: String get() = "@data" * Read and envelope from file if the file exists, return null if file does not exist. * * If file is directory, then expect two files inside: - * * **meta.** for meta + * * **meta.** for meta * * **data** for data * - * If the file is envelope read it using [EnvelopeFormatFactory.peekFormat] functionality to infer format. + * If the file is envelope read it using [EnvelopeFormatFactory.peekFormat] functionality to infer format (if not overridden with [formatPicker]). * * If the file is not an envelope and [readNonEnvelopes] is true, return an Envelope without meta, using file as binary. * @@ -150,9 +151,9 @@ public val IOPlugin.Companion.DATA_FILE_NAME: String get() = "@data" public fun IOPlugin.readEnvelopeFile( path: Path, readNonEnvelopes: Boolean = false, - formatPeeker: IOPlugin.(Path) -> EnvelopeFormat? = IOPlugin::peekBinaryFormat, -): Envelope? { - if (!Files.exists(path)) return null + formatPicker: IOPlugin.(Path) -> EnvelopeFormat? = IOPlugin::peekFileEnvelopeFormat, +): Envelope { + if (!Files.exists(path)) error("File with path $path does not exist") //read two-files directory if (Files.isDirectory(path)) { @@ -177,11 +178,11 @@ public fun IOPlugin.readEnvelopeFile( return SimpleEnvelope(meta, data) } - return formatPeeker(path)?.let { format -> + return formatPicker(path)?.let { format -> path.readEnvelope(format) } ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary SimpleEnvelope(Meta.EMPTY, path.asBinary()) - } else null + } else error("Can't infer format for file $path") } /** diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt index 7ccc65e7..8bfefd6d 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt @@ -1,7 +1,7 @@ package hep.dataforge.io import hep.dataforge.context.Global -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import kotlinx.io.asBinary import kotlinx.io.toByteArray import kotlinx.io.writeDouble @@ -54,7 +54,7 @@ class FileBinaryTest { val tmpPath = Files.createTempFile("dataforge_test", ".df") Global.io.writeEnvelopeFile(tmpPath, envelope) - val binary = Global.io.readEnvelopeFile(tmpPath)?.data!! + val binary = Global.io.readEnvelopeFile(tmpPath).data!! assertEquals(binary.size, binary.toByteArray().size) } } \ No newline at end of file diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt index 8db2bf62..fba3ed8e 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt @@ -1,7 +1,7 @@ package hep.dataforge.io import hep.dataforge.context.Global -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import kotlinx.io.writeDouble import java.nio.file.Files import kotlin.test.Test @@ -29,7 +29,7 @@ class FileEnvelopeTest { val tmpPath = Files.createTempFile("dataforge_test", ".df") writeEnvelopeFile(tmpPath, envelope) println(tmpPath.toUri()) - val restored: Envelope = readEnvelopeFile(tmpPath)!! + val restored: Envelope = readEnvelopeFile(tmpPath) assertTrue { envelope.contentEquals(restored) } } } @@ -40,7 +40,7 @@ class FileEnvelopeTest { val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df") writeEnvelopeFile(tmpPath, envelope, envelopeFormat = TaglessEnvelopeFormat) println(tmpPath.toUri()) - val restored: Envelope = readEnvelopeFile(tmpPath)!! + val restored: Envelope = readEnvelopeFile(tmpPath) assertTrue { envelope.contentEquals(restored) } } } diff --git a/dataforge-meta/api/dataforge-meta.api b/dataforge-meta/api/dataforge-meta.api index f4b8d3ed..06901639 100644 --- a/dataforge-meta/api/dataforge-meta.api +++ b/dataforge-meta/api/dataforge-meta.api @@ -1,20 +1,18 @@ -public abstract class hep/dataforge/meta/AbstractMetaNode : hep/dataforge/meta/MetaBase, hep/dataforge/meta/MetaNode { - public fun ()V - public fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; - public fun toMeta ()Lhep/dataforge/meta/Meta; -} - -public abstract class hep/dataforge/meta/AbstractMutableMeta : hep/dataforge/meta/AbstractMetaNode, hep/dataforge/meta/MutableMeta { +public abstract class hep/dataforge/meta/AbstractMutableMeta : hep/dataforge/meta/AbstractTypedMeta, hep/dataforge/meta/MutableMeta { public fun ()V protected final fun getChildren ()Ljava/util/Map; public fun getItems ()Ljava/util/Map; - protected fun replaceItem (Lhep/dataforge/names/NameToken;Lhep/dataforge/meta/MetaItem;Lhep/dataforge/meta/MetaItem;)V - public fun setItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)V - protected final fun wrapItem (Lhep/dataforge/meta/MetaItem;)Lhep/dataforge/meta/MetaItem; + protected fun replaceItem (Lhep/dataforge/names/NameToken;Lhep/dataforge/meta/TypedMetaItem;Lhep/dataforge/meta/TypedMetaItem;)V + public fun setItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)V protected abstract fun wrapNode (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/MutableMeta; } -public final class hep/dataforge/meta/Config : hep/dataforge/meta/AbstractMutableMeta, hep/dataforge/meta/ObservableMeta { +public abstract class hep/dataforge/meta/AbstractTypedMeta : hep/dataforge/meta/MetaBase, hep/dataforge/meta/TypedMeta { + public fun ()V + public fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; +} + +public final class hep/dataforge/meta/Config : hep/dataforge/meta/AbstractMutableMeta, hep/dataforge/meta/ObservableItemProvider { public static final field ConfigSerializer Lhep/dataforge/meta/Config$ConfigSerializer; public fun ()V public synthetic fun empty$dataforge_meta ()Lhep/dataforge/meta/MutableMeta; @@ -35,7 +33,8 @@ public final class hep/dataforge/meta/Config$ConfigSerializer : kotlinx/serializ public final class hep/dataforge/meta/ConfigKt { public static final fun asConfig (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Config; - public static final fun get (Lhep/dataforge/meta/Config;Lhep/dataforge/names/NameToken;)Lhep/dataforge/meta/MetaItem; + public static final fun get (Lhep/dataforge/meta/Config;Lhep/dataforge/names/NameToken;)Lhep/dataforge/meta/TypedMetaItem; + public static final fun toConfig (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Config; } public abstract interface class hep/dataforge/meta/Configurable { @@ -49,19 +48,6 @@ public final class hep/dataforge/meta/ConfigurableKt { public static final fun configure (Lhep/dataforge/meta/Configurable;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/Configurable; } -public abstract interface annotation class hep/dataforge/meta/DFBuilder : java/lang/annotation/Annotation { -} - -public abstract interface annotation class hep/dataforge/meta/DFExperimental : java/lang/annotation/Annotation { -} - -public final class hep/dataforge/meta/GetIndexedKt { - public static final fun getIndexed (Lhep/dataforge/meta/Meta;Lhep/dataforge/names/Name;)Ljava/util/Map; - public static final fun getIndexed (Lhep/dataforge/meta/Meta;Ljava/lang/String;)Ljava/util/Map; - public static final fun getIndexed (Lhep/dataforge/meta/MetaNode;Lhep/dataforge/names/Name;)Ljava/util/Map; - public static final fun getIndexed (Lhep/dataforge/meta/MetaNode;Ljava/lang/String;)Ljava/util/Map; -} - public final class hep/dataforge/meta/ItemDelegateKt { public static final fun boolean (Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadOnlyProperty; public static final fun boolean (Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/names/Name;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/ReadOnlyProperty; @@ -108,15 +94,41 @@ public final class hep/dataforge/meta/ItemDelegateKt { public static synthetic fun value$default (Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/names/Name;ILjava/lang/Object;)Lkotlin/properties/ReadOnlyProperty; } +public final class hep/dataforge/meta/ItemListener { + public fun (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Lkotlin/jvm/functions/Function3; + public final fun copy (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/meta/ItemListener; + public static synthetic fun copy$default (Lhep/dataforge/meta/ItemListener;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/meta/ItemListener; + public fun equals (Ljava/lang/Object;)Z + public final fun getAction ()Lkotlin/jvm/functions/Function3; + public final fun getOwner ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public abstract interface class hep/dataforge/meta/ItemProvider { public static final field Companion Lhep/dataforge/meta/ItemProvider$Companion; - public abstract fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; + public abstract fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; } public final class hep/dataforge/meta/ItemProvider$Companion { public final fun getEMPTY ()Lhep/dataforge/meta/ItemProvider; } +public final class hep/dataforge/meta/ItemProviderKt { + public static final fun get (Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; + public static final fun get (Lhep/dataforge/meta/ItemProvider;Ljava/lang/String;)Lhep/dataforge/meta/TypedMetaItem; + public static final fun getChild (Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/ItemProvider; + public static final fun getChild (Lhep/dataforge/meta/ItemProvider;Ljava/lang/String;)Lhep/dataforge/meta/ItemProvider; + public static final fun getIndexed (Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/names/Name;)Ljava/util/Map; + public static final fun getIndexed (Lhep/dataforge/meta/ItemProvider;Ljava/lang/String;)Ljava/util/Map; + public static final fun getRootItem (Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/TypedMetaItem; + public static final fun getRootNode (Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/Meta; + public static final fun withDefault (Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/ItemProvider; +} + public final class hep/dataforge/meta/JsonMeta : hep/dataforge/meta/MetaBase { public static final field Companion Lhep/dataforge/meta/JsonMeta$Companion; public static final field JSON_ARRAY_KEY Ljava/lang/String; @@ -135,8 +147,8 @@ public final class hep/dataforge/meta/JsonMetaKt { public static synthetic fun toJson$default (Lhep/dataforge/values/Value;Lhep/dataforge/meta/descriptors/ValueDescriptor;ILjava/lang/Object;)Lkotlinx/serialization/json/JsonElement; public static final fun toMeta (Lkotlinx/serialization/json/JsonObject;Lhep/dataforge/meta/descriptors/NodeDescriptor;)Lhep/dataforge/meta/JsonMeta; public static synthetic fun toMeta$default (Lkotlinx/serialization/json/JsonObject;Lhep/dataforge/meta/descriptors/NodeDescriptor;ILjava/lang/Object;)Lhep/dataforge/meta/JsonMeta; - public static final fun toMetaItem (Lkotlinx/serialization/json/JsonElement;Lhep/dataforge/meta/descriptors/ItemDescriptor;)Lhep/dataforge/meta/MetaItem; - public static synthetic fun toMetaItem$default (Lkotlinx/serialization/json/JsonElement;Lhep/dataforge/meta/descriptors/ItemDescriptor;ILjava/lang/Object;)Lhep/dataforge/meta/MetaItem; + public static final fun toMetaItem (Lkotlinx/serialization/json/JsonElement;Lhep/dataforge/meta/descriptors/ItemDescriptor;)Lhep/dataforge/meta/TypedMetaItem; + public static synthetic fun toMetaItem$default (Lkotlinx/serialization/json/JsonElement;Lhep/dataforge/meta/descriptors/ItemDescriptor;ILjava/lang/Object;)Lhep/dataforge/meta/TypedMetaItem; public static final fun toValue (Lkotlinx/serialization/json/JsonPrimitive;Lhep/dataforge/meta/descriptors/ValueDescriptor;)Lhep/dataforge/values/Value; } @@ -155,7 +167,7 @@ public final class hep/dataforge/meta/Laminate$Companion { public final class hep/dataforge/meta/LaminateKt { public static final fun Laminate ([Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Laminate; - public static final fun getFirst (Lhep/dataforge/meta/Laminate;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; + public static final fun getFirst (Lhep/dataforge/meta/Laminate;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; public static final fun withBottom (Lhep/dataforge/meta/Laminate;Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Laminate; public static final fun withTop (Lhep/dataforge/meta/Laminate;Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Laminate; } @@ -172,7 +184,7 @@ public abstract interface class hep/dataforge/meta/Meta : hep/dataforge/meta/Ite public static final field TYPE Ljava/lang/String; public static final field VALUE_KEY Ljava/lang/String; public abstract fun equals (Ljava/lang/Object;)Z - public abstract fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; + public abstract fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; public abstract fun getItems ()Ljava/util/Map; public abstract fun hashCode ()I public abstract fun toMeta ()Lhep/dataforge/meta/Meta; @@ -186,14 +198,14 @@ public final class hep/dataforge/meta/Meta$Companion { } public final class hep/dataforge/meta/Meta$DefaultImpls { - public static fun getItem (Lhep/dataforge/meta/Meta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; + public static fun getItem (Lhep/dataforge/meta/Meta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; public static fun toMeta (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Meta; } public abstract class hep/dataforge/meta/MetaBase : hep/dataforge/meta/Meta { public fun ()V public fun equals (Ljava/lang/Object;)Z - public fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; + public fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; public fun hashCode ()I public fun toMeta ()Lhep/dataforge/meta/Meta; public fun toString ()Ljava/lang/String; @@ -211,8 +223,8 @@ public final class hep/dataforge/meta/MetaBuilder : hep/dataforge/meta/AbstractM public final fun put (Lhep/dataforge/names/Name;Ljava/lang/String;)V public final fun put (Lhep/dataforge/names/Name;Lkotlin/jvm/functions/Function1;)V public final fun put (Ljava/lang/String;Lhep/dataforge/meta/Meta;)V - public final fun put (Ljava/lang/String;Lhep/dataforge/meta/MetaItem;)V public final fun put (Ljava/lang/String;Lhep/dataforge/meta/MetaRepr;)V + public final fun put (Ljava/lang/String;Lhep/dataforge/meta/TypedMetaItem;)V public final fun put (Ljava/lang/String;Lhep/dataforge/values/Value;)V public final fun put (Ljava/lang/String;Ljava/lang/Boolean;)V public final fun put (Ljava/lang/String;Ljava/lang/Enum;)V @@ -231,23 +243,27 @@ public final class hep/dataforge/meta/MetaBuilder : hep/dataforge/meta/AbstractM public final class hep/dataforge/meta/MetaBuilderKt { public static final fun Meta (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MetaBuilder; - public static final fun builder (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/MetaBuilder; - public static final fun edit (Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MetaBuilder; + public static final fun toMutableMeta (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/MetaBuilder; } -public abstract class hep/dataforge/meta/MetaItem { - public static final field Companion Lhep/dataforge/meta/MetaItem$Companion; - public abstract fun equals (Ljava/lang/Object;)Z - public abstract fun hashCode ()I +public final class hep/dataforge/meta/MetaItemKt { + public static final fun asMetaItem (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/MetaItemNode; + public static final fun asMetaItem (Lhep/dataforge/values/Value;)Lhep/dataforge/meta/MetaItemValue; + public static final fun getBoolean (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Boolean; + public static final fun getDouble (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Double; + public static final fun getFloat (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Float; + public static final fun getInt (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Integer; + public static final fun getLong (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Long; + public static final fun getNode (Lhep/dataforge/meta/TypedMetaItem;)Lhep/dataforge/meta/Meta; + public static final fun getNumber (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Number; + public static final fun getShort (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Short; + public static final fun getString (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/String; + public static final fun getStringList (Lhep/dataforge/meta/TypedMetaItem;)Ljava/util/List; + public static final fun getValue (Lhep/dataforge/meta/TypedMetaItem;)Lhep/dataforge/values/Value; } -public final class hep/dataforge/meta/MetaItem$Companion { - public final fun of (Ljava/lang/Object;)Lhep/dataforge/meta/MetaItem; - public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; -} - -public final class hep/dataforge/meta/MetaItem$NodeItem : hep/dataforge/meta/MetaItem { - public static final field Companion Lhep/dataforge/meta/MetaItem$NodeItem$Companion; +public final class hep/dataforge/meta/MetaItemNode : hep/dataforge/meta/TypedMetaItem { + public static final field Companion Lhep/dataforge/meta/MetaItemNode$Companion; public fun (Lhep/dataforge/meta/Meta;)V public fun equals (Ljava/lang/Object;)Z public final fun getNode ()Lhep/dataforge/meta/Meta; @@ -255,12 +271,21 @@ public final class hep/dataforge/meta/MetaItem$NodeItem : hep/dataforge/meta/Met public fun toString ()Ljava/lang/String; } -public final class hep/dataforge/meta/MetaItem$NodeItem$Companion { +public final class hep/dataforge/meta/MetaItemNode$Companion { public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; } -public final class hep/dataforge/meta/MetaItem$ValueItem : hep/dataforge/meta/MetaItem { - public static final field Companion Lhep/dataforge/meta/MetaItem$ValueItem$Companion; +public final class hep/dataforge/meta/MetaItemSerializer : kotlinx/serialization/KSerializer { + public static final field INSTANCE Lhep/dataforge/meta/MetaItemSerializer; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lhep/dataforge/meta/TypedMetaItem; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lhep/dataforge/meta/TypedMetaItem;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V +} + +public final class hep/dataforge/meta/MetaItemValue : hep/dataforge/meta/TypedMetaItem { + public static final field Companion Lhep/dataforge/meta/MetaItemValue$Companion; public fun (Lhep/dataforge/values/Value;)V public fun equals (Ljava/lang/Object;)Z public final fun getValue ()Lhep/dataforge/values/Value; @@ -268,68 +293,16 @@ public final class hep/dataforge/meta/MetaItem$ValueItem : hep/dataforge/meta/Me public fun toString ()Ljava/lang/String; } -public final class hep/dataforge/meta/MetaItem$ValueItem$Companion { +public final class hep/dataforge/meta/MetaItemValue$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class hep/dataforge/meta/MetaItemSerializer : kotlinx/serialization/KSerializer { - public static final field INSTANCE Lhep/dataforge/meta/MetaItemSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lhep/dataforge/meta/MetaItem; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lhep/dataforge/meta/MetaItem;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V -} - public final class hep/dataforge/meta/MetaKt { - public static final fun asMetaItem (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/MetaItem$NodeItem; - public static final fun asMetaItem (Lhep/dataforge/values/Value;)Lhep/dataforge/meta/MetaItem$ValueItem; - public static final fun get (Lhep/dataforge/meta/Meta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; - public static final fun get (Lhep/dataforge/meta/Meta;Lhep/dataforge/names/NameToken;)Lhep/dataforge/meta/MetaItem; - public static final fun get (Lhep/dataforge/meta/Meta;Ljava/lang/String;)Lhep/dataforge/meta/MetaItem; - public static final fun get (Lhep/dataforge/meta/MetaNode;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; - public static final fun get (Lhep/dataforge/meta/MetaNode;Lhep/dataforge/names/NameToken;)Lhep/dataforge/meta/MetaItem; - public static final fun get (Lhep/dataforge/meta/MetaNode;Ljava/lang/String;)Lhep/dataforge/meta/MetaItem; - public static final fun getBoolean (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Boolean; - public static final fun getDouble (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Double; - public static final fun getFloat (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Float; - public static final fun getInt (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Integer; - public static final fun getLong (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Long; - public static final fun getNode (Lhep/dataforge/meta/MetaItem;)Lhep/dataforge/meta/Meta; - public static final fun getNumber (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Number; - public static final fun getShort (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Short; - public static final fun getString (Lhep/dataforge/meta/MetaItem;)Ljava/lang/String; - public static final fun getStringList (Lhep/dataforge/meta/MetaItem;)Ljava/util/List; - public static final fun getValue (Lhep/dataforge/meta/MetaItem;)Lhep/dataforge/values/Value; + public static final fun get (Lhep/dataforge/meta/Meta;Lhep/dataforge/names/NameToken;)Lhep/dataforge/meta/TypedMetaItem; public static final fun isEmpty (Lhep/dataforge/meta/Meta;)Z + public static final fun itemSequence (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; public static final fun iterator (Lhep/dataforge/meta/Meta;)Ljava/util/Iterator; - public static final fun seal (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/SealedMeta; - public static final fun seal (Lhep/dataforge/meta/MetaItem;)Lhep/dataforge/meta/MetaItem; - public static final fun sequence (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; - public static final fun values (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; -} - -public final class hep/dataforge/meta/MetaListener { - public fun (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)V - public synthetic fun (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Object; - public final fun component2 ()Lkotlin/jvm/functions/Function3; - public final fun copy (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lhep/dataforge/meta/MetaListener; - public static synthetic fun copy$default (Lhep/dataforge/meta/MetaListener;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lhep/dataforge/meta/MetaListener; - public fun equals (Ljava/lang/Object;)Z - public final fun getAction ()Lkotlin/jvm/functions/Function3; - public final fun getOwner ()Ljava/lang/Object; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class hep/dataforge/meta/MetaNode : hep/dataforge/meta/Meta { - public abstract fun getItems ()Ljava/util/Map; -} - -public final class hep/dataforge/meta/MetaNode$DefaultImpls { - public static fun getItem (Lhep/dataforge/meta/MetaNode;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; - public static fun toMeta (Lhep/dataforge/meta/MetaNode;)Lhep/dataforge/meta/Meta; + public static final fun valueSequence (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; } public abstract interface class hep/dataforge/meta/MetaRepr { @@ -345,18 +318,6 @@ public final class hep/dataforge/meta/MetaSerializer : kotlinx/serialization/KSe public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V } -public final class hep/dataforge/meta/MetaWithDefault : hep/dataforge/meta/MetaBase { - public fun (Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/ItemProvider;)V - public final fun getDefault ()Lhep/dataforge/meta/ItemProvider; - public fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; - public fun getItems ()Ljava/util/Map; - public final fun getMeta ()Lhep/dataforge/meta/Meta; -} - -public final class hep/dataforge/meta/MetaWithDefaultKt { - public static final fun withDefault (Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MetaWithDefault; -} - public final class hep/dataforge/meta/MutableItemDelegateKt { public static final fun boolean (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; public static final fun boolean (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/ReadWriteProperty; @@ -389,6 +350,8 @@ public final class hep/dataforge/meta/MutableItemDelegateKt { public static final fun long (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; public static synthetic fun long$default (Lhep/dataforge/meta/MutableItemProvider;JLhep/dataforge/names/Name;ILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; public static synthetic fun long$default (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;ILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; + public static final fun node (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; + public static synthetic fun node$default (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;ILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; public static final fun number (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; public static final fun number (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/ReadWriteProperty; public static final fun number (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/Number;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; @@ -414,18 +377,23 @@ public final class hep/dataforge/meta/MutableItemDelegateKt { } public abstract interface class hep/dataforge/meta/MutableItemProvider : hep/dataforge/meta/ItemProvider { - public abstract fun setItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)V + public abstract fun setItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)V } public final class hep/dataforge/meta/MutableItemProviderKt { - public static final fun getItem (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;)Lhep/dataforge/meta/MetaItem; - public static final fun node (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; - public static synthetic fun node$default (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;ILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; + public static final fun editChild (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MutableItemProvider; + public static final fun getChild (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MutableItemProvider; + public static final fun getChild (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;)Lhep/dataforge/meta/MutableItemProvider; public static final fun remove (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;)V public static final fun remove (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;)V + public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lhep/dataforge/meta/Meta;)V + public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)V + public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lhep/dataforge/values/Value;)V public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Ljava/lang/Iterable;)V public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Ljava/lang/Object;)V public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/NameToken;Ljava/lang/Object;)V + public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Lhep/dataforge/meta/Meta;)V + public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Lhep/dataforge/meta/TypedMetaItem;)V public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Ljava/lang/Iterable;)V public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Ljava/lang/Object;)V public static final fun set (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V @@ -433,108 +401,126 @@ public final class hep/dataforge/meta/MutableItemProviderKt { public static synthetic fun setIndexed$default (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static final fun setIndexedItems (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)V public static synthetic fun setIndexedItems$default (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V - public static final fun setItem (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Lhep/dataforge/meta/MetaItem;)V - public static final fun setNode (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lhep/dataforge/meta/Meta;)V - public static final fun setNode (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Lhep/dataforge/meta/Meta;)V - public static final fun setValue (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Lhep/dataforge/values/Value;)V - public static final fun setValue (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Lhep/dataforge/values/Value;)V + public static final fun update (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/Meta;)V + public static final fun withDefault (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; } -public abstract interface class hep/dataforge/meta/MutableMeta : hep/dataforge/meta/MetaNode, hep/dataforge/meta/MutableItemProvider { +public abstract interface class hep/dataforge/meta/MutableMeta : hep/dataforge/meta/MutableItemProvider, hep/dataforge/meta/TypedMeta { public abstract fun getItems ()Ljava/util/Map; } public final class hep/dataforge/meta/MutableMeta$DefaultImpls { - public static fun getItem (Lhep/dataforge/meta/MutableMeta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; + public static fun getItem (Lhep/dataforge/meta/MutableMeta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; public static fun toMeta (Lhep/dataforge/meta/MutableMeta;)Lhep/dataforge/meta/Meta; } public final class hep/dataforge/meta/MutableMetaKt { - public static final fun append (Lhep/dataforge/meta/MutableMeta;Lhep/dataforge/names/Name;Ljava/lang/Object;)V - public static final fun append (Lhep/dataforge/meta/MutableMeta;Ljava/lang/String;Ljava/lang/Object;)V + public static final fun append (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/names/Name;Ljava/lang/Object;)V + public static final fun append (Lhep/dataforge/meta/MutableItemProvider;Ljava/lang/String;Ljava/lang/Object;)V public static final fun edit (Lhep/dataforge/meta/AbstractMutableMeta;Lhep/dataforge/names/Name;Lkotlin/jvm/functions/Function1;)V - public static final fun update (Lhep/dataforge/meta/MutableMeta;Lhep/dataforge/meta/Meta;)V } -public abstract interface class hep/dataforge/meta/ObservableMeta : hep/dataforge/meta/Meta { +public abstract interface class hep/dataforge/meta/ObservableItemProvider : hep/dataforge/meta/ItemProvider { public abstract fun onChange (Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)V public abstract fun removeListener (Ljava/lang/Object;)V } -public final class hep/dataforge/meta/ObservableMeta$DefaultImpls { - public static fun getItem (Lhep/dataforge/meta/ObservableMeta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; - public static fun toMeta (Lhep/dataforge/meta/ObservableMeta;)Lhep/dataforge/meta/Meta; +public abstract interface class hep/dataforge/meta/ReadOnlySpecification { + public abstract fun empty ()Lhep/dataforge/meta/ItemProvider; + public abstract fun invoke (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/ItemProvider; + public abstract fun read (Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/ItemProvider; } -public class hep/dataforge/meta/Scheme : hep/dataforge/meta/Configurable, hep/dataforge/meta/MetaRepr, hep/dataforge/meta/MutableItemProvider, hep/dataforge/meta/descriptors/Described { +public final class hep/dataforge/meta/ReadOnlySpecification$DefaultImpls { + public static fun invoke (Lhep/dataforge/meta/ReadOnlySpecification;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/ItemProvider; +} + +public class hep/dataforge/meta/Scheme : hep/dataforge/meta/MetaRepr, hep/dataforge/meta/MutableItemProvider, hep/dataforge/meta/descriptors/Described { public fun ()V - public fun (Lhep/dataforge/meta/Config;Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/meta/descriptors/NodeDescriptor;)V - public synthetic fun (Lhep/dataforge/meta/Config;Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/meta/descriptors/NodeDescriptor;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun getConfig ()Lhep/dataforge/meta/Config; - public final fun getDefaultItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; public fun getDefaultLayer ()Lhep/dataforge/meta/Meta; public synthetic fun getDescriptor ()Lhep/dataforge/meta/descriptors/ItemDescriptor; - public fun getDescriptor ()Lhep/dataforge/meta/descriptors/NodeDescriptor; - public fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/MetaItem; - public final fun isEmpty ()Z - public fun setItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)V + public final fun getDescriptor ()Lhep/dataforge/meta/descriptors/NodeDescriptor; + public fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; + public final fun setDescriptor (Lhep/dataforge/meta/descriptors/NodeDescriptor;)V + public fun setItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)V public fun toMeta ()Lhep/dataforge/meta/Laminate; public synthetic fun toMeta ()Lhep/dataforge/meta/Meta; - public final fun validateItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)Z + public fun validateItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)Z } public final class hep/dataforge/meta/SchemeKt { - public static final fun asScheme (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Scheme; public static final fun invoke (Lhep/dataforge/meta/Scheme;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/Scheme; - public static final fun toScheme (Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MutableItemProvider; - public static synthetic fun toScheme$default (Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/meta/MutableItemProvider; + public static final fun isEmpty (Lhep/dataforge/meta/Scheme;)Z + public static final fun retarget (Lhep/dataforge/meta/Scheme;Lhep/dataforge/meta/MutableItemProvider;)Lhep/dataforge/meta/Scheme; + public static final fun wrap (Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/meta/descriptors/NodeDescriptor;)Lhep/dataforge/meta/Scheme; + public static synthetic fun wrap$default (Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/ItemProvider;Lhep/dataforge/meta/descriptors/NodeDescriptor;ILjava/lang/Object;)Lhep/dataforge/meta/Scheme; } public class hep/dataforge/meta/SchemeSpec : hep/dataforge/meta/Specification, hep/dataforge/meta/descriptors/Described { public fun (Lkotlin/jvm/functions/Function0;)V - public fun (Lkotlin/jvm/functions/Function3;)V - public synthetic fun empty ()Lhep/dataforge/meta/MutableItemProvider; + public synthetic fun empty ()Lhep/dataforge/meta/ItemProvider; public fun empty ()Lhep/dataforge/meta/Scheme; public synthetic fun getDescriptor ()Lhep/dataforge/meta/descriptors/ItemDescriptor; public fun getDescriptor ()Lhep/dataforge/meta/descriptors/NodeDescriptor; - public synthetic fun invoke (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MutableItemProvider; + public synthetic fun invoke (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/ItemProvider; public final fun invoke (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/Scheme; - public synthetic fun read (Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; - public fun read (Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/Scheme; - public synthetic fun wrap (Lhep/dataforge/meta/Config;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; - public fun wrap (Lhep/dataforge/meta/Config;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/Scheme; + public synthetic fun read (Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/ItemProvider; + public fun read (Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/Scheme; + public synthetic fun write (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; + public fun write (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/Scheme; } -public final class hep/dataforge/meta/SealedMeta : hep/dataforge/meta/AbstractMetaNode { +public final class hep/dataforge/meta/SealedMeta : hep/dataforge/meta/AbstractTypedMeta { public fun getItems ()Ljava/util/Map; } -public abstract interface class hep/dataforge/meta/Specification { - public abstract fun empty ()Lhep/dataforge/meta/MutableItemProvider; - public abstract fun invoke (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MutableItemProvider; - public abstract fun read (Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; - public abstract fun wrap (Lhep/dataforge/meta/Config;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; +public final class hep/dataforge/meta/SealedMetaKt { + public static final fun seal (Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/SealedMeta; + public static final fun seal (Lhep/dataforge/meta/TypedMetaItem;)Lhep/dataforge/meta/TypedMetaItem; +} + +public abstract interface class hep/dataforge/meta/Specification : hep/dataforge/meta/ReadOnlySpecification { + public abstract fun write (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; } public final class hep/dataforge/meta/Specification$DefaultImpls { - public static fun empty (Lhep/dataforge/meta/Specification;)Lhep/dataforge/meta/MutableItemProvider; public static fun invoke (Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MutableItemProvider; - public static synthetic fun read$default (Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/Meta;Lhep/dataforge/meta/ItemProvider;ILjava/lang/Object;)Lhep/dataforge/meta/MutableItemProvider; - public static fun wrap (Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/Config;Lhep/dataforge/meta/ItemProvider;)Lhep/dataforge/meta/MutableItemProvider; - public static synthetic fun wrap$default (Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/Config;Lhep/dataforge/meta/ItemProvider;ILjava/lang/Object;)Lhep/dataforge/meta/MutableItemProvider; + public static synthetic fun write$default (Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/ItemProvider;ILjava/lang/Object;)Lhep/dataforge/meta/MutableItemProvider; } public final class hep/dataforge/meta/SpecificationKt { - public static final fun configSpec (Lhep/dataforge/meta/MetaItem;Lhep/dataforge/meta/Specification;)Lhep/dataforge/meta/MutableItemProvider; - public static final fun configure (Lhep/dataforge/meta/MetaRepr;Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MetaRepr; - public static final fun createStyle (Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/Meta; - public static final fun spec (Lhep/dataforge/meta/MetaItem;Lhep/dataforge/meta/Specification;)Lhep/dataforge/meta/MutableItemProvider; - public static final fun spec (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/Scheme;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; public static final fun spec (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/Specification;Lhep/dataforge/names/Name;)Lkotlin/properties/ReadWriteProperty; - public static synthetic fun spec$default (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/Scheme;Lhep/dataforge/names/Name;ILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; public static synthetic fun spec$default (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/Specification;Lhep/dataforge/names/Name;ILjava/lang/Object;)Lkotlin/properties/ReadWriteProperty; - public static final fun update (Lhep/dataforge/meta/Configurable;Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/Configurable; - public static final fun update (Lhep/dataforge/meta/Specification;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/MutableItemProvider; + public static final fun update (Lhep/dataforge/meta/Configurable;Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;)V + public static final fun update (Lhep/dataforge/meta/MutableItemProvider;Lhep/dataforge/meta/Specification;Lkotlin/jvm/functions/Function1;)V + public static final fun withSpec (Lhep/dataforge/meta/TypedMetaItem;Lhep/dataforge/meta/Specification;)Lhep/dataforge/meta/MutableItemProvider; +} + +public abstract interface class hep/dataforge/meta/TypedMeta : hep/dataforge/meta/Meta { + public abstract fun getItem (Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; + public abstract fun getItems ()Ljava/util/Map; +} + +public final class hep/dataforge/meta/TypedMeta$DefaultImpls { + public static fun getItem (Lhep/dataforge/meta/TypedMeta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; + public static fun toMeta (Lhep/dataforge/meta/TypedMeta;)Lhep/dataforge/meta/Meta; +} + +public abstract class hep/dataforge/meta/TypedMetaItem { + public static final field Companion Lhep/dataforge/meta/TypedMetaItem$Companion; + public abstract fun equals (Ljava/lang/Object;)Z + public abstract fun hashCode ()I +} + +public final class hep/dataforge/meta/TypedMetaItem$Companion { + public final fun of (Ljava/lang/Object;)Lhep/dataforge/meta/TypedMetaItem; + public final fun serializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; +} + +public final class hep/dataforge/meta/TypedMetaKt { + public static final fun get (Lhep/dataforge/meta/TypedMeta;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/TypedMetaItem; + public static final fun get (Lhep/dataforge/meta/TypedMeta;Lhep/dataforge/names/NameToken;)Lhep/dataforge/meta/TypedMetaItem; + public static final fun get (Lhep/dataforge/meta/TypedMeta;Ljava/lang/String;)Lhep/dataforge/meta/TypedMetaItem; } public abstract interface class hep/dataforge/meta/descriptors/Described { @@ -546,14 +532,15 @@ public final class hep/dataforge/meta/descriptors/Described$Companion { } public final class hep/dataforge/meta/descriptors/DescriptorMetaKt { - public static final fun defaultItem (Lhep/dataforge/meta/descriptors/ItemDescriptor;)Lhep/dataforge/meta/MetaItem; + public static final fun defaultItem (Lhep/dataforge/meta/descriptors/ItemDescriptor;)Lhep/dataforge/meta/TypedMetaItem; public static final fun defaultMeta (Lhep/dataforge/meta/descriptors/NodeDescriptor;)Lhep/dataforge/meta/Laminate; } -public abstract class hep/dataforge/meta/descriptors/ItemDescriptor { +public abstract class hep/dataforge/meta/descriptors/ItemDescriptor : hep/dataforge/meta/Configurable { public static final field Companion Lhep/dataforge/meta/descriptors/ItemDescriptor$Companion; public static final field DEFAULT_INDEX_KEY Ljava/lang/String; public synthetic fun (Lhep/dataforge/meta/Config;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public abstract fun copy ()Lhep/dataforge/meta/descriptors/ItemDescriptor; public final fun getAttributes ()Lhep/dataforge/meta/Config; public final fun getConfig ()Lhep/dataforge/meta/Config; public final fun getIndexKey ()Ljava/lang/String; @@ -571,11 +558,12 @@ public final class hep/dataforge/meta/descriptors/ItemDescriptor$Companion { } public final class hep/dataforge/meta/descriptors/ItemDescriptorKt { + public static final fun NodeDescriptor (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/descriptors/NodeDescriptor; public static final fun attributes (Lhep/dataforge/meta/descriptors/ItemDescriptor;Lkotlin/jvm/functions/Function1;)V public static final fun get (Lhep/dataforge/meta/descriptors/ItemDescriptor;Lhep/dataforge/names/Name;)Lhep/dataforge/meta/descriptors/ItemDescriptor; public static final fun get (Lhep/dataforge/meta/descriptors/ItemDescriptor;Ljava/lang/String;)Lhep/dataforge/meta/descriptors/ItemDescriptor; public static final fun plus (Lhep/dataforge/meta/descriptors/NodeDescriptor;Lhep/dataforge/meta/descriptors/NodeDescriptor;)Lhep/dataforge/meta/descriptors/NodeDescriptor; - public static final fun validateItem (Lhep/dataforge/meta/descriptors/ItemDescriptor;Lhep/dataforge/meta/MetaItem;)Z + public static final fun validateItem (Lhep/dataforge/meta/descriptors/ItemDescriptor;Lhep/dataforge/meta/TypedMetaItem;)Z } public final class hep/dataforge/meta/descriptors/NodeDescriptor : hep/dataforge/meta/descriptors/ItemDescriptor { @@ -583,6 +571,8 @@ public final class hep/dataforge/meta/descriptors/NodeDescriptor : hep/dataforge public fun ()V public fun (Lhep/dataforge/meta/Config;)V public synthetic fun (Lhep/dataforge/meta/Config;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun copy ()Lhep/dataforge/meta/descriptors/ItemDescriptor; + public fun copy ()Lhep/dataforge/meta/descriptors/NodeDescriptor; public final fun getDefault ()Lhep/dataforge/meta/Config; public final fun getItems ()Ljava/util/Map; public final fun getNodes ()Ljava/util/Map; @@ -599,7 +589,6 @@ public final class hep/dataforge/meta/descriptors/NodeDescriptor : hep/dataforge } public final class hep/dataforge/meta/descriptors/NodeDescriptor$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/meta/descriptors/NodeDescriptor; } public final class hep/dataforge/meta/descriptors/ValueDescriptor : hep/dataforge/meta/descriptors/ItemDescriptor { @@ -607,6 +596,8 @@ public final class hep/dataforge/meta/descriptors/ValueDescriptor : hep/dataforg public fun (Lhep/dataforge/meta/Config;)V public synthetic fun (Lhep/dataforge/meta/Config;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun allow ([Ljava/lang/Object;)V + public synthetic fun copy ()Lhep/dataforge/meta/descriptors/ItemDescriptor; + public fun copy ()Lhep/dataforge/meta/descriptors/ValueDescriptor; public final fun default (Ljava/lang/Object;)V public final fun getAllowedValues ()Ljava/util/List; public final fun getDefault ()Lhep/dataforge/values/Value; @@ -628,16 +619,16 @@ public final class hep/dataforge/meta/transformations/KeepTransformationRule : h public fun equals (Ljava/lang/Object;)Z public final fun getSelector ()Lkotlin/jvm/functions/Function1; public fun hashCode ()I - public fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)Z + public fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)Z public fun selectItems (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; public fun toString ()Ljava/lang/String; - public fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;Lhep/dataforge/meta/MutableMeta;)V + public fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;Lhep/dataforge/meta/MutableMeta;)V } public abstract interface class hep/dataforge/meta/transformations/MetaConverter { public static final field Companion Lhep/dataforge/meta/transformations/MetaConverter$Companion; - public abstract fun itemToObject (Lhep/dataforge/meta/MetaItem;)Ljava/lang/Object; - public abstract fun objectToMetaItem (Ljava/lang/Object;)Lhep/dataforge/meta/MetaItem; + public abstract fun itemToObject (Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Object; + public abstract fun objectToMetaItem (Ljava/lang/Object;)Lhep/dataforge/meta/TypedMetaItem; } public final class hep/dataforge/meta/transformations/MetaConverter$Companion { @@ -657,8 +648,8 @@ public final class hep/dataforge/meta/transformations/MetaConverter$Companion { public final class hep/dataforge/meta/transformations/MetaConverterKt { public static final fun metaToObject (Lhep/dataforge/meta/transformations/MetaConverter;Lhep/dataforge/meta/Meta;)Ljava/lang/Object; - public static final fun nullableItemToObject (Lhep/dataforge/meta/transformations/MetaConverter;Lhep/dataforge/meta/MetaItem;)Ljava/lang/Object; - public static final fun nullableObjectToMetaItem (Lhep/dataforge/meta/transformations/MetaConverter;Ljava/lang/Object;)Lhep/dataforge/meta/MetaItem; + public static final fun nullableItemToObject (Lhep/dataforge/meta/transformations/MetaConverter;Lhep/dataforge/meta/TypedMetaItem;)Ljava/lang/Object; + public static final fun nullableObjectToMetaItem (Lhep/dataforge/meta/transformations/MetaConverter;Ljava/lang/Object;)Lhep/dataforge/meta/TypedMetaItem; public static final fun valueToObject (Lhep/dataforge/meta/transformations/MetaConverter;Lhep/dataforge/values/Value;)Ljava/lang/Object; } @@ -671,7 +662,7 @@ public final class hep/dataforge/meta/transformations/MetaTransformation { public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (Ljava/util/Collection;Ljava/lang/Object;)Z public static final fun equals-impl0 (Ljava/util/Collection;Ljava/util/Collection;)Z - public static final fun generate-impl (Ljava/util/Collection;Lhep/dataforge/meta/Config;)Lhep/dataforge/meta/ObservableMeta; + public static final fun generate-impl (Ljava/util/Collection;Lhep/dataforge/meta/Config;)Lhep/dataforge/meta/ObservableItemProvider; public static final fun generate-impl (Ljava/util/Collection;Lhep/dataforge/meta/Meta;)Lhep/dataforge/meta/Meta; public final fun getTransformations ()Ljava/util/Collection; public fun hashCode ()I @@ -682,7 +673,7 @@ public final class hep/dataforge/meta/transformations/MetaTransformation { } public final class hep/dataforge/meta/transformations/MetaTransformation$Companion { - public final fun make-S5KKvQA (Lkotlin/jvm/functions/Function1;)Ljava/util/Collection; + public final fun make-edBMxVg (Lkotlin/jvm/functions/Function1;)Ljava/util/Collection; } public final class hep/dataforge/meta/transformations/MetaTransformationBuilder { @@ -705,10 +696,10 @@ public final class hep/dataforge/meta/transformations/RegexItemTransformationRul public final fun getFrom ()Lkotlin/text/Regex; public final fun getTransform ()Lkotlin/jvm/functions/Function4; public fun hashCode ()I - public fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)Z + public fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)Z public fun selectItems (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; public fun toString ()Ljava/lang/String; - public fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;Lhep/dataforge/meta/MutableMeta;)V + public fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;Lhep/dataforge/meta/MutableMeta;)V } public final class hep/dataforge/meta/transformations/SingleItemTransformationRule : hep/dataforge/meta/transformations/TransformationRule { @@ -721,22 +712,48 @@ public final class hep/dataforge/meta/transformations/SingleItemTransformationRu public final fun getFrom ()Lhep/dataforge/names/Name; public final fun getTransform ()Lkotlin/jvm/functions/Function3; public fun hashCode ()I - public fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)Z + public fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)Z public fun selectItems (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; public fun toString ()Ljava/lang/String; - public fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;Lhep/dataforge/meta/MutableMeta;)V + public fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;Lhep/dataforge/meta/MutableMeta;)V } public abstract interface class hep/dataforge/meta/transformations/TransformationRule { - public abstract fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;)Z + public abstract fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;)Z public abstract fun selectItems (Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; - public abstract fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/MetaItem;Lhep/dataforge/meta/MutableMeta;)V + public abstract fun transformItem (Lhep/dataforge/names/Name;Lhep/dataforge/meta/TypedMetaItem;Lhep/dataforge/meta/MutableMeta;)V } public final class hep/dataforge/meta/transformations/TransformationRule$DefaultImpls { public static fun selectItems (Lhep/dataforge/meta/transformations/TransformationRule;Lhep/dataforge/meta/Meta;)Lkotlin/sequences/Sequence; } +public abstract interface annotation class hep/dataforge/misc/DFBuilder : java/lang/annotation/Annotation { +} + +public abstract interface annotation class hep/dataforge/misc/DFExperimental : java/lang/annotation/Annotation { +} + +public abstract interface annotation class hep/dataforge/misc/DFInternal : java/lang/annotation/Annotation { +} + +public abstract interface class hep/dataforge/misc/Named { + public static final field Companion Lhep/dataforge/misc/Named$Companion; + public abstract fun getName ()Lhep/dataforge/names/Name; +} + +public final class hep/dataforge/misc/Named$Companion { + public final fun nameOf (Ljava/lang/Object;)Lhep/dataforge/names/Name; +} + +public final class hep/dataforge/misc/NamedKt { + public static final fun isAnonymous (Lhep/dataforge/misc/Named;)Z +} + +public abstract interface annotation class hep/dataforge/misc/Type : java/lang/annotation/Annotation { + public abstract fun id ()Ljava/lang/String; +} + public final class hep/dataforge/names/Name { public static final field Companion Lhep/dataforge/names/Name$Companion; public static final field NAME_SEPARATOR Ljava/lang/String; @@ -752,6 +769,8 @@ public final class hep/dataforge/names/Name$Companion : kotlinx/serialization/KS public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public final fun getEMPTY ()Lhep/dataforge/names/Name; + public final fun getMATCH_ALL_TOKEN ()Lhep/dataforge/names/NameToken; + public final fun getMATCH_ANY_TOKEN ()Lhep/dataforge/names/NameToken; public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lhep/dataforge/names/Name;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V public final fun serializer ()Lkotlinx/serialization/KSerializer; @@ -777,6 +796,7 @@ public final class hep/dataforge/names/NameKt { public static final fun plus (Lhep/dataforge/names/Name;Lhep/dataforge/names/NameToken;)Lhep/dataforge/names/Name; public static final fun plus (Lhep/dataforge/names/Name;Ljava/lang/String;)Lhep/dataforge/names/Name; public static final fun plus (Lhep/dataforge/names/NameToken;Lhep/dataforge/names/Name;)Lhep/dataforge/names/Name; + public static final fun removeHeadOrNull (Lhep/dataforge/names/Name;Lhep/dataforge/names/Name;)Lhep/dataforge/names/Name; public static final fun set (Ljava/util/Map;Ljava/lang/String;Ljava/lang/Object;)V public static final fun startsWith (Lhep/dataforge/names/Name;Lhep/dataforge/names/Name;)Z public static final fun startsWith (Lhep/dataforge/names/Name;Lhep/dataforge/names/NameToken;)Z @@ -784,6 +804,12 @@ public final class hep/dataforge/names/NameKt { public static final fun withIndex (Lhep/dataforge/names/Name;Ljava/lang/String;)Lhep/dataforge/names/Name; } +public final class hep/dataforge/names/NameMatcherKt { + public static final fun matches (Lhep/dataforge/names/Name;Lhep/dataforge/names/Name;)Z + public static final fun matches (Lhep/dataforge/names/Name;Ljava/lang/String;)Z + public static final fun matches (Lhep/dataforge/names/NameToken;Lhep/dataforge/names/NameToken;)Z +} + public final class hep/dataforge/names/NameToken { public static final field Companion Lhep/dataforge/names/NameToken$Companion; public fun (Ljava/lang/String;Ljava/lang/String;)V @@ -813,17 +839,15 @@ public final class hep/dataforge/names/NameTokenKt { public static final fun withIndex (Lhep/dataforge/names/NameToken;Ljava/lang/String;)Lhep/dataforge/names/NameToken; } -public final class hep/dataforge/values/DoubleArrayValue : hep/dataforge/values/Value { +public final class hep/dataforge/values/DoubleArrayValue : hep/dataforge/values/Value, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { public fun ([D)V public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Double; - public synthetic fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public synthetic fun getValue ()Ljava/lang/Object; public fun getValue ()[D public fun hashCode ()I + public fun iterator ()Ljava/util/Iterator; public fun toString ()Ljava/lang/String; } @@ -831,8 +855,6 @@ public final class hep/dataforge/values/EnumValue : hep/dataforge/values/Value { public fun (Ljava/lang/Enum;)V public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public fun getValue ()Ljava/lang/Enum; public synthetic fun getValue ()Ljava/lang/Object; @@ -849,8 +871,6 @@ public final class hep/dataforge/values/False : hep/dataforge/values/Value { public static final field INSTANCE Lhep/dataforge/values/False; public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public fun getValue ()Ljava/lang/Object; public fun hashCode ()I @@ -861,33 +881,34 @@ public final class hep/dataforge/values/LazyParsedValue : hep/dataforge/values/V public fun (Ljava/lang/String;)V public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; + public final fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public fun getValue ()Ljava/lang/Object; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class hep/dataforge/values/ListValue : hep/dataforge/values/Value { +public final class hep/dataforge/values/ListValue : hep/dataforge/values/Value, java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker { + public static final field Companion Lhep/dataforge/values/ListValue$Companion; public fun (Ljava/util/List;)V public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public synthetic fun getValue ()Ljava/lang/Object; public fun getValue ()Ljava/util/List; public fun hashCode ()I + public fun iterator ()Ljava/util/Iterator; public fun toString ()Ljava/lang/String; } +public final class hep/dataforge/values/ListValue$Companion { + public final fun getEMPTY ()Lhep/dataforge/values/ListValue; +} + public final class hep/dataforge/values/Null : hep/dataforge/values/Value { public static final field INSTANCE Lhep/dataforge/values/Null; public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public fun getValue ()Ljava/lang/Object; public fun hashCode ()I @@ -898,8 +919,7 @@ public final class hep/dataforge/values/NumberValue : hep/dataforge/values/Value public fun (Ljava/lang/Number;)V public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; + public final fun getNumber ()Ljava/lang/Number; public fun getType ()Lhep/dataforge/values/ValueType; public fun getValue ()Ljava/lang/Object; public fun hashCode ()I @@ -910,8 +930,7 @@ public final class hep/dataforge/values/StringValue : hep/dataforge/values/Value public fun (Ljava/lang/String;)V public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; + public final fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public fun getValue ()Ljava/lang/Object; public fun hashCode ()I @@ -922,8 +941,6 @@ public final class hep/dataforge/values/True : hep/dataforge/values/Value { public static final field INSTANCE Lhep/dataforge/values/True; public fun equals (Ljava/lang/Object;)Z public fun getList ()Ljava/util/List; - public fun getNumber ()Ljava/lang/Number; - public fun getString ()Ljava/lang/String; public fun getType ()Lhep/dataforge/values/ValueType; public fun getValue ()Ljava/lang/Object; public fun hashCode ()I @@ -932,18 +949,17 @@ public final class hep/dataforge/values/True : hep/dataforge/values/Value { public abstract interface class hep/dataforge/values/Value { public static final field Companion Lhep/dataforge/values/Value$Companion; - public static final field TARGET Ljava/lang/String; + public static final field TYPE Ljava/lang/String; public abstract fun equals (Ljava/lang/Object;)Z public abstract fun getList ()Ljava/util/List; - public abstract fun getNumber ()Ljava/lang/Number; - public abstract fun getString ()Ljava/lang/String; public abstract fun getType ()Lhep/dataforge/values/ValueType; public abstract fun getValue ()Ljava/lang/Object; public abstract fun hashCode ()I + public abstract fun toString ()Ljava/lang/String; } public final class hep/dataforge/values/Value$Companion { - public static final field TARGET Ljava/lang/String; + public static final field TYPE Ljava/lang/String; public final fun of (Ljava/lang/Object;)Lhep/dataforge/values/Value; } @@ -976,6 +992,9 @@ public final class hep/dataforge/values/ValueKt { public static final fun asValue ([I)Lhep/dataforge/values/Value; public static final fun asValue ([J)Lhep/dataforge/values/Value; public static final fun asValue ([S)Lhep/dataforge/values/Value; + public static final fun getNumber (Lhep/dataforge/values/Value;)Ljava/lang/Number; + public static final fun getNumberOrNull (Lhep/dataforge/values/Value;)Ljava/lang/Number; + public static final fun getString (Lhep/dataforge/values/Value;)Ljava/lang/String; public static final fun parseValue (Ljava/lang/String;)Lhep/dataforge/values/Value; } @@ -991,6 +1010,7 @@ public final class hep/dataforge/values/ValueSerializer : kotlinx/serialization/ public final class hep/dataforge/values/ValueType : java/lang/Enum { public static final field BOOLEAN Lhep/dataforge/values/ValueType; public static final field Companion Lhep/dataforge/values/ValueType$Companion; + public static final field LIST Lhep/dataforge/values/ValueType; public static final field NULL Lhep/dataforge/values/ValueType; public static final field NUMBER Lhep/dataforge/values/ValueType; public static final field STRING Lhep/dataforge/values/ValueType; @@ -1000,6 +1020,7 @@ public final class hep/dataforge/values/ValueType : java/lang/Enum { public final class hep/dataforge/values/ValueType$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lhep/dataforge/values/ValueType$$serializer; + public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; public fun childSerializers ()[Lkotlinx/serialization/KSerializer; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lhep/dataforge/values/ValueType; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; diff --git a/dataforge-meta/build.gradle.kts b/dataforge-meta/build.gradle.kts index 980924a7..45ca68f3 100644 --- a/dataforge-meta/build.gradle.kts +++ b/dataforge-meta/build.gradle.kts @@ -9,4 +9,8 @@ kscience { } } -description = "Meta definition and basic operations on meta" \ No newline at end of file +description = "Meta definition and basic operations on meta" + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt index 2674d0a8..530fe9aa 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt @@ -10,16 +10,17 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlin.collections.set +import kotlin.jvm.Synchronized //TODO add validator to configuration -public data class MetaListener( +public data class ItemListener( val owner: Any? = null, - val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit + val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit ) -public interface ObservableMeta : Meta { - public fun onChange(owner: Any?, 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?) } @@ -27,37 +28,40 @@ public interface ObservableMeta : Meta { * Mutable meta representing object state */ @Serializable(Config.Companion::class) -public class Config() : AbstractMutableMeta(), ObservableMeta { +public class Config() : AbstractMutableMeta(), ObservableItemProvider { - private val listeners = HashSet() + private val listeners = HashSet() - private fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) { + @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 */ - override fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) { - listeners.add(MetaListener(owner, action)) + @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: MetaItem?, newItem: MetaItem?) { + override fun replaceItem(key: NameToken, oldItem: TypedMetaItem?, newItem: TypedMetaItem?) { if (newItem == null) { children.remove(key) - if (oldItem != null && oldItem is MetaItem.NodeItem) { + if (oldItem != null && oldItem is MetaItemNode) { oldItem.node.removeListener(this) } } else { children[key] = newItem - if (newItem is MetaItem.NodeItem) { + if (newItem is MetaItemNode) { newItem.node.onChange(this) { name, oldChild, newChild -> itemChanged(key + name, oldChild, newChild) } @@ -88,14 +92,23 @@ public class Config() : AbstractMutableMeta(), ObservableMeta { } } -public operator fun Config.get(token: NameToken): MetaItem? = items[token] +public operator fun Config.get(token: NameToken): TypedMetaItem? = items[token] -public fun Meta.asConfig(): Config = this as? Config ?: Config().also { builder -> +/** + * 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 MetaItem.ValueItem -> item.value - is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.asConfig()) + is MetaItemValue -> item.value + is MetaItemNode -> MetaItemNode(item.node.asConfig()) } } -} \ No newline at end of file +} + +/** + * Return this [Meta] as [Config] if it is [Config] and create a new copy otherwise + */ +public fun Meta.asConfig(): Config = this as? Config ?: toConfig() \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt index 764ee1ea..429ac360 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.misc.DFBuilder import hep.dataforge.names.Name import kotlin.properties.ReadWriteProperty diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemDelegate.kt index eab2573f..775bb243 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemDelegate.kt @@ -8,10 +8,10 @@ import kotlin.properties.ReadOnlyProperty /* Meta delegates */ -public typealias ItemDelegate = ReadOnlyProperty?> +public typealias ItemDelegate = ReadOnlyProperty public fun ItemProvider.item(key: Name? = null): ItemDelegate = ReadOnlyProperty { _, property -> - getItem(key ?: property.name.asName()) + get(key ?: property.name.asName()) } //TODO add caching for sealed nodes @@ -40,7 +40,7 @@ public fun ItemDelegate.convert( * A converter with a custom reader transformation */ public fun ItemDelegate.convert( - reader: (MetaItem<*>?) -> R, + reader: (MetaItem?) -> R, ): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> this@convert.getValue(thisRef, property).let(reader) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemProvider.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemProvider.kt new file mode 100644 index 00000000..a0f8a538 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemProvider.kt @@ -0,0 +1,88 @@ +package hep.dataforge.meta + +import hep.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 { + 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 = 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.getIndexed(name: Name): Map> = +// (this as Meta).getIndexed(name) as Map> +// +//public fun > M.getIndexed(name: String): Map> = +// getIndexed(name.toName()) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt index 2cabc8f1..c0628520 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt @@ -16,21 +16,17 @@ import kotlinx.serialization.json.* /** * @param descriptor reserved for custom serialization in future */ -public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement { - return if (isList()) { - JsonArray(list.map { it.toJson() }) - } else { - when (type) { - ValueType.NUMBER -> JsonPrimitive(number) - ValueType.STRING -> JsonPrimitive(string) - ValueType.BOOLEAN -> JsonPrimitive(boolean) - ValueType.NULL -> JsonNull - } - } +public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when (type) { + ValueType.NUMBER -> JsonPrimitive(numberOrNull) + ValueType.STRING -> JsonPrimitive(string) + ValueType.BOOLEAN -> JsonPrimitive(boolean) + ValueType.LIST -> JsonArray(list.map { it.toJson() }) + ValueType.NULL -> JsonNull } //Use these methods to customize JSON key mapping -private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attributes["jsonName"].string ?: toString() +@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER") +private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString() //private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key) @@ -41,11 +37,11 @@ private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String val elementMap = HashMap() - fun MetaItem<*>.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) { - is MetaItem.ValueItem -> { + fun MetaItem.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) { + is MetaItemValue -> { value.toJson(itemDescriptor as? ValueDescriptor) } - is MetaItem.NodeItem -> { + is MetaItemNode -> { node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index) } } @@ -53,7 +49,7 @@ private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String fun addElement(key: String) { val itemDescriptor = descriptor?.items?.get(key) val jsonKey = key.toJsonKey(itemDescriptor) - val items: Map> = getIndexed(key) + val items: Map = getIndexed(key) when (items.size) { 0 -> { //do nothing @@ -100,14 +96,14 @@ public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { } } -public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem = when (this) { +public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): TypedMetaItem = when (this) { is JsonPrimitive -> { val value = this.toValue(descriptor as? ValueDescriptor) - MetaItem.ValueItem(value) + MetaItemValue(value) } is JsonObject -> { val meta = JsonMeta(this, descriptor as? NodeDescriptor) - MetaItem.NodeItem(meta) + MetaItemNode(meta) } is JsonArray -> { if (this.all { it is JsonPrimitive }) { @@ -119,7 +115,7 @@ public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem< (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor) }.asValue() } - MetaItem.ValueItem(value) + MetaItemValue(value) } else { //We can't return multiple items therefore we create top level node buildJsonObject { put(JSON_ARRAY_KEY, this@toMetaItem) }.toMetaItem(descriptor) @@ -132,18 +128,18 @@ public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem< */ public class JsonMeta(private val json: JsonObject, private val descriptor: NodeDescriptor? = null) : MetaBase() { - private fun buildItems(): Map> { - val map = LinkedHashMap>() + private fun buildItems(): Map> { + val map = LinkedHashMap>() json.forEach { (jsonKey, value) -> val key = NameToken(jsonKey) val itemDescriptor = descriptor?.items?.get(jsonKey) when (value) { is JsonPrimitive -> { - map[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) + map[key] = MetaItemValue(value.toValue(itemDescriptor as? ValueDescriptor)) } is JsonObject -> { - map[key] = MetaItem.NodeItem( + map[key] = MetaItemNode( JsonMeta( value, itemDescriptor as? NodeDescriptor @@ -157,7 +153,7 @@ public class JsonMeta(private val json: JsonObject, private val descriptor: Node (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor) } ) - map[key] = MetaItem.ValueItem(listValue) + map[key] = MetaItemValue(listValue) } else value.forEachIndexed { index, jsonElement -> val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: DEFAULT_INDEX_KEY val indexValue: String = (jsonElement as? JsonObject) @@ -172,7 +168,7 @@ public class JsonMeta(private val json: JsonObject, private val descriptor: Node return map } - override val items: Map> by lazy(::buildItems) + override val items: Map> by lazy(::buildItems) public companion object { /** diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt index 5545358c..a51e9cf5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt @@ -17,7 +17,7 @@ public class Laminate(layers: List) : MetaBase() { } } - override val items: Map> by lazy { + override val items: Map> by lazy { layers.map { it.items.keys }.flatten().associateWith { key -> layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule) } @@ -40,15 +40,15 @@ public class Laminate(layers: List) : MetaBase() { * * TODO add picture */ - public val replaceRule: (Sequence>) -> MetaItem = { it.first().seal() } + public val replaceRule: (Sequence) -> TypedMetaItem = { it.first().seal() } - private fun Sequence>.merge(): MetaItem { + private fun Sequence.merge(): TypedMetaItem { return when { - all { it is MetaItem.ValueItem } -> //If all items are values, take first + all { it is MetaItemValue } -> //If all items are values, take first first().seal() - all { it is MetaItem.NodeItem } -> { + all { it is MetaItemNode } -> { //list nodes in item - val nodes = map { (it as MetaItem.NodeItem).node } + val nodes = map { (it as MetaItemNode).node } //represent as key->value entries val entries = nodes.flatMap { it.items.entries.asSequence() } //group by keys @@ -57,13 +57,13 @@ public class Laminate(layers: List) : MetaBase() { val items = groups.mapValues { entry -> entry.value.asSequence().map { it.value }.merge() } - MetaItem.NodeItem(SealedMeta(items)) + MetaItemNode(SealedMeta(items)) } else -> map { when (it) { - is MetaItem.ValueItem -> MetaItem.NodeItem(Meta { Meta.VALUE_KEY put it.value }) - is MetaItem.NodeItem -> it + is MetaItemValue -> MetaItemNode(Meta { Meta.VALUE_KEY put it.value }) + is MetaItemNode -> it } }.merge() } @@ -74,7 +74,7 @@ public class Laminate(layers: List) : MetaBase() { * The values a replaced but meta children are joined * TODO add picture */ - public val mergeRule: (Sequence>) -> MetaItem = { it.merge() } + public val mergeRule: (Sequence) -> TypedMetaItem = { it.merge() } } } @@ -84,7 +84,7 @@ public fun Laminate(vararg layers: Meta?): Laminate = Laminate(layers.filterNotN /** * Performance optimized version of get method */ -public fun Laminate.getFirst(name: Name): MetaItem<*>? { +public fun Laminate.getFirst(name: Name): MetaItem? { layers.forEach { layer -> layer[name]?.let { return it } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt index c8ed6755..4d5225d2 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -1,67 +1,10 @@ package hep.dataforge.meta -import hep.dataforge.meta.Meta.Companion.VALUE_KEY -import hep.dataforge.meta.MetaItem.NodeItem -import hep.dataforge.meta.MetaItem.ValueItem import hep.dataforge.names.* -import hep.dataforge.values.EnumValue -import hep.dataforge.values.Null import hep.dataforge.values.Value -import hep.dataforge.values.boolean import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -/** - * A member of the meta tree. Could be represented as one of following: - * * a [ValueItem] (leaf) - * * a [NodeItem] (node) - */ -@Serializable(MetaItemSerializer::class) -public sealed class MetaItem() { - - abstract override fun equals(other: Any?): Boolean - - abstract override fun hashCode(): Int - - @Serializable(MetaItemSerializer::class) - public class ValueItem(public val value: Value) : MetaItem() { - override fun toString(): String = value.toString() - - override fun equals(other: Any?): Boolean { - return this.value == (other as? ValueItem)?.value - } - - override fun hashCode(): Int { - return value.hashCode() - } - } - - @Serializable(MetaItemSerializer::class) - public class NodeItem(public val node: M) : MetaItem() { - //Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializeable - override fun toString(): String = node.toString() - - override fun equals(other: Any?): Boolean = node == (other as? NodeItem<*>)?.node - - override fun hashCode(): Int = node.hashCode() - } - - public companion object { - public fun of(arg: Any?): MetaItem<*> { - return when (arg) { - null -> ValueItem(Null) - is MetaItem<*> -> arg - is Meta -> NodeItem(arg) - else -> ValueItem(Value.of(arg)) - } - } - } -} - -public fun Value.asMetaItem(): ValueItem = ValueItem(this) -public fun M.asMetaItem(): NodeItem = NodeItem(this) - /** * 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. @@ -71,18 +14,10 @@ public interface MetaRepr { public fun toMeta(): Meta } -public fun interface ItemProvider { - public fun getItem(name: Name): MetaItem<*>? - - public companion object { - public val EMPTY: ItemProvider = ItemProvider { null } - } -} - /** - * Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities: - * * [MetaItem.ValueItem] (leaf) - * * [MetaItem.NodeItem] single node + * 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 */ @@ -90,10 +25,10 @@ public interface Meta : MetaRepr, ItemProvider { /** * Top level items of meta tree */ - public val items: Map> + public val items: Map - override fun getItem(name: Name): MetaItem<*>? { - if (name.isEmpty()) return NodeItem(this) + override fun getItem(name: Name): MetaItem? { + if (name.isEmpty()) return MetaItemNode(this) return name.firstOrNull()?.let { token -> val tail = name.cutFirst() when (tail.length) { @@ -103,7 +38,7 @@ public interface Meta : MetaRepr, ItemProvider { } } - override fun toMeta(): Meta = seal() + override fun toMeta(): Meta = this override fun equals(other: Any?): Boolean @@ -120,151 +55,39 @@ public interface Meta : MetaRepr, ItemProvider { public const val VALUE_KEY: String = "@value" public val EMPTY: Meta = object : MetaBase() { - override val items: Map> = emptyMap() + override val items: Map = emptyMap() } } } -/* Get operations*/ - -/** - * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node. - * - * If [name] is empty return current [Meta] as a [NodeItem] - */ -public operator fun Meta?.get(name: Name): MetaItem<*>? = this?.getItem(name) - -public operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token) - -/** - * Parse [Name] from [key] using full name notation and pass it to [Meta.get] - */ -public operator fun Meta?.get(key: String): MetaItem<*>? = get(key.toName()) +public operator fun Meta.get(token: NameToken): MetaItem? = items.get(token) /** * Get a sequence of [Name]-[Value] pairs */ -public fun Meta.values(): Sequence> { +public fun Meta.valueSequence(): Sequence> { return items.asSequence().flatMap { (key, item) -> when (item) { - is ValueItem -> sequenceOf(key.asName() to item.value) - is NodeItem -> item.node.values().map { pair -> (key.asName() + pair.first) to pair.second } + is MetaItemValue -> sequenceOf(key.asName() to item.value) + is MetaItemNode -> item.node.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second } } } } /** - * Get a sequence of all [Name]-[MetaItem] pairs for all items including nodes + * Get a sequence of all [Name]-[TypedMetaItem] pairs for all items including nodes */ -public fun Meta.sequence(): Sequence>> { - return sequence { - items.forEach { (key, item) -> - yield(key.asName() to item) - if (item is NodeItem<*>) { - yieldAll(item.node.sequence().map { (innerKey, innerItem) -> - (key + innerKey) to innerItem - }) - } +public fun Meta.itemSequence(): Sequence> = 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 + }) } } } -public operator fun Meta.iterator(): Iterator>> = sequence().iterator() - -/** - * A meta node that ensures that all of its descendants has at least the same type - */ -public interface MetaNode> : Meta { - override val items: Map> -} - -/** - * The same as [Meta.get], but with specific node type - */ -public operator fun > M?.get(name: Name): MetaItem? = if (this == null) { - null -} else { - @Suppress("UNCHECKED_CAST", "ReplaceGetOrSet") - (this as Meta).get(name) as MetaItem? // Do not change -} - -public operator fun > M?.get(key: String): MetaItem? = this[key.toName()] - -public operator fun > M?.get(key: NameToken): MetaItem? = this[key.asName()] - -/** - * Equals, hashcode and to string for any meta - */ -public abstract class MetaBase : Meta { - - override fun equals(other: Any?): Boolean = if (other is Meta) { - this.items == other.items - } 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 AbstractMetaNode> : MetaNode, MetaBase() - -/** - * The meta implementation which is guaranteed to be immutable. - * - * If the argument is possibly mutable node, it is copied on creation - */ -public class SealedMeta internal constructor( - override val items: Map>, -) : AbstractMetaNode() - -/** - * 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() }) - -@Suppress("UNCHECKED_CAST") -public fun MetaItem<*>.seal(): MetaItem = when (this) { - is ValueItem -> this - is NodeItem -> NodeItem(node.seal()) -} - -/** - * Unsafe methods to access values and nodes directly from [MetaItem] - */ -public val MetaItem<*>?.value: Value? - get() = (this as? ValueItem)?.value - ?: (this?.node?.get(VALUE_KEY) as? ValueItem)?.value - -public val MetaItem<*>?.string: String? get() = value?.string -public val MetaItem<*>?.boolean: Boolean? get() = value?.boolean -public val MetaItem<*>?.number: Number? get() = value?.number -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 > MetaItem<*>?.enum(): E? = if (this is ValueItem && this.value is EnumValue<*>) { - this.value.value as E -} else { - string?.let { enumValueOf(it) } -} - -public val MetaItem<*>.stringList: List? get() = value?.list?.map { it.string } - -public val MetaItem?.node: M? - get() = when (this) { - null -> null - is ValueItem -> null//error("Trying to interpret value meta item as node item") - is NodeItem -> node - } +public operator fun Meta.iterator(): Iterator> = itemSequence().iterator() public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY || this.items.isEmpty() \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt index cd041275..4fe5de9b 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.misc.DFBuilder import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.values.EnumValue @@ -12,10 +13,10 @@ import kotlin.jvm.JvmName */ @DFBuilder public class MetaBuilder : AbstractMutableMeta() { - override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder() + override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.toMutableMeta() override fun empty(): MetaBuilder = MetaBuilder() - public infix fun String.put(item: MetaItem<*>?) { + public infix fun String.put(item: MetaItem?) { set(this, item) } @@ -71,7 +72,7 @@ public class MetaBuilder : AbstractMutableMeta() { set(this,value.toList()) } - public infix fun String.put(metaBuilder: MetaBuilder.() -> Unit) { + public inline infix fun String.put(metaBuilder: MetaBuilder.() -> Unit) { this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder) } @@ -121,25 +122,20 @@ public class MetaBuilder : AbstractMutableMeta() { /** * For safety, builder always copies the initial meta even if it is builder itself */ -public fun Meta.builder(): MetaBuilder { +public fun Meta.toMutableMeta(): MetaBuilder { return MetaBuilder().also { builder -> items.mapValues { entry -> val item = entry.value builder[entry.key.asName()] = when (item) { - is MetaItem.ValueItem -> item.value - is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder()) + is MetaItemValue -> item.value + is MetaItemNode -> MetaItemNode(item.node.toMutableMeta()) } } } } -/** - * Create a deep copy of this meta and apply builder to it - */ -public fun Meta.edit(builder: MetaBuilder.() -> Unit): MetaBuilder = builder().apply(builder) - /** * Build a [MetaBuilder] using given transformation */ @Suppress("FunctionName") -public fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) \ No newline at end of file +public inline fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaItem.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaItem.kt new file mode 100644 index 00000000..d63238f9 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaItem.kt @@ -0,0 +1,90 @@ +package hep.dataforge.meta + +import hep.dataforge.values.* +import kotlinx.serialization.Serializable + +/** + * 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() { + + 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() { + 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(public val node: M) : TypedMetaItem() { + //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.asMetaItem(): MetaItemNode = 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 > MetaItem?.enum(): E? = + if (this is MetaItemValue && this.value is EnumValue<*>) { + this.value.value as E + } else { + string?.let { enumValueOf(it) } + } + +public val MetaItem.stringList: List? get() = value?.list?.map { it.string } + +public val TypedMetaItem?.node: M? + get() = when (this) { + null -> null + is MetaItemValue -> null//error("Trying to interpret value meta item as node item") + is MetaItemNode -> node + } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt index b20adba8..ed24b234 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt @@ -12,16 +12,15 @@ import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.JsonEncoder import kotlinx.serialization.json.JsonObject -@OptIn(ExperimentalSerializationApi::class) -public object MetaItemSerializer : KSerializer> { +public object MetaItemSerializer : KSerializer { - @OptIn(InternalSerializationApi::class) - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("hep.dataforge.meta.MetaItem") { + @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) + override val descriptor: SerialDescriptor = buildSerialDescriptor("MetaItem", PolymorphicKind.SEALED) { element("isNode") - element("content", buildSerialDescriptor("MetaItem.content", PolymorphicKind.SEALED)) + element("value", buildSerialDescriptor("MetaItem.value", SerialKind.CONTEXTUAL)) } - override fun deserialize(decoder: Decoder): MetaItem<*> { + override fun deserialize(decoder: Decoder): MetaItem { decoder.decodeStructure(descriptor) { //Force strict serialization order require(decodeElementIndex(descriptor) == 0) { "Node flag must be first item serialized" } @@ -37,12 +36,12 @@ public object MetaItemSerializer : KSerializer> { } } - override fun serialize(encoder: Encoder, value: MetaItem<*>) { + override fun serialize(encoder: Encoder, value: MetaItem) { encoder.encodeStructure(descriptor) { - encodeBooleanElement(descriptor, 0, value is MetaItem.NodeItem) + encodeBooleanElement(descriptor, 0, value is MetaItemNode) when (value) { - is MetaItem.ValueItem -> encodeSerializableElement(descriptor, 1, ValueSerializer, value.value) - is MetaItem.NodeItem -> encodeSerializableElement(descriptor, 1, MetaSerializer, value.node) + is MetaItemValue -> encodeSerializableElement(descriptor, 1, ValueSerializer, value.value) + is MetaItemNode -> encodeSerializableElement(descriptor, 1, MetaSerializer, value.node) } } } @@ -53,19 +52,19 @@ public object MetaItemSerializer : KSerializer> { */ public object MetaSerializer : KSerializer { - private val mapSerializer: KSerializer>> = MapSerializer( + private val mapSerializer: KSerializer>> = MapSerializer( NameToken, MetaItemSerializer//MetaItem.serializer(MetaSerializer) ) - override val descriptor: SerialDescriptor get() = mapSerializer.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> = mapSerializer.deserialize(decoder) + override val items: Map = mapSerializer.deserialize(decoder) } } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaWithDefault.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaWithDefault.kt deleted file mode 100644 index 2a225121..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaWithDefault.kt +++ /dev/null @@ -1,18 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.names.NameToken - -/** - * Meta object with default provider for items not present in the initial meta. - */ -public class MetaWithDefault(public val meta: Meta, public val default: ItemProvider) : MetaBase() { - override val items: Map> - get() = meta.items - - override fun getItem(name: Name): MetaItem<*>? { - return meta[name] ?: default.getItem(name) - } -} - -public fun Meta.withDefault(default: ItemProvider): MetaWithDefault = MetaWithDefault(this, default) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemDelegate.kt index 32a7c7d4..e9190b20 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemDelegate.kt @@ -3,32 +3,29 @@ package hep.dataforge.meta import hep.dataforge.meta.transformations.MetaConverter import hep.dataforge.names.Name import hep.dataforge.names.asName -import hep.dataforge.values.DoubleArrayValue -import hep.dataforge.values.Value -import hep.dataforge.values.asValue -import hep.dataforge.values.doubleArray +import hep.dataforge.values.* import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /* Read-write delegates */ -public typealias MutableItemDelegate = ReadWriteProperty?> +public typealias MutableItemDelegate = ReadWriteProperty public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = object : MutableItemDelegate { - override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? { - return getItem(key ?: property.name.asName()) + override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem? { + return get(key ?: property.name.asName()) } - override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) { + override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem?) { val name = key ?: property.name.asName() - setItem(name, value) + set(name, value) } } /* Mutable converters */ /** - * A type converter for a mutable [MetaItem] delegate + * A type converter for a mutable [TypedMetaItem] delegate */ public fun MutableItemDelegate.convert( converter: MetaConverter, @@ -58,8 +55,8 @@ public fun MutableItemDelegate.convert( } public fun MutableItemDelegate.convert( - reader: (MetaItem<*>?) -> R, - writer: (R) -> MetaItem<*>?, + reader: (MetaItem?) -> R, + writer: (R) -> MetaItem?, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): R = @@ -72,7 +69,6 @@ public fun MutableItemDelegate.convert( } - /* Read-write delegates for [MutableItemProvider] */ /** @@ -117,8 +113,13 @@ public inline fun > MutableItemProvider.enum( ): ReadWriteProperty = item(key).convert(MetaConverter.enum()) { default } +public fun MutableItemProvider.node(key: Name? = null): ReadWriteProperty = item(key).convert( + reader = { it.node }, + writer = { it?.asMetaItem() } +) + public inline fun > M.node(key: Name? = null): ReadWriteProperty = - item(key).convert(reader = { it?.let { it.node as M } }, writer = { it?.let { MetaItem.NodeItem(it) } }) + item(key).convert(reader = { it?.let { it.node as M } }, writer = { it?.let { MetaItemNode(it) } }) /* Number delegates */ @@ -171,7 +172,7 @@ public fun MutableItemProvider.numberList( vararg default: Number, key: Name? = null, ): ReadWriteProperty> = item(key).convert( - reader = { it?.value?.list?.map { value -> value.number } ?: listOf(*default) }, + reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) }, writer = { it.map { num -> num.asValue() }.asValue().asMetaItem() } ) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemProvider.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemProvider.kt index 6a43af6e..c06c57d8 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemProvider.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemProvider.kt @@ -2,32 +2,20 @@ package hep.dataforge.meta import hep.dataforge.names.* import hep.dataforge.values.Value -import kotlin.properties.ReadWriteProperty public interface MutableItemProvider : ItemProvider { - public fun setItem(name: Name, item: MetaItem<*>?) + public fun setItem(name: Name, item: MetaItem?) } -public fun MutableItemProvider.getItem(key: String): MetaItem<*>? = getItem(key.toName()) +public operator fun MutableItemProvider.set(name: Name, item: MetaItem?): Unit = setItem(name, item) -public fun MutableItemProvider.setValue(name: Name, value: Value?): Unit = - setItem(name, value?.let { MetaItem.ValueItem(value) }) +public operator fun MutableItemProvider.set(name: Name, value: Value?): Unit = set(name, value?.asMetaItem()) -public fun MutableItemProvider.setNode(name: Name, meta: Meta?): Unit = - setItem(name, meta?.let { MetaItem.NodeItem(meta) }) +public operator fun MutableItemProvider.set(name: Name, meta: Meta?): Unit = set(name, meta?.asMetaItem()) -public fun MutableItemProvider.setItem(key: String, item: MetaItem<*>?): Unit = setItem(key.toName(), item) +public operator fun MutableItemProvider.set(key: String, item: MetaItem?): Unit = set(key.toName(), item) -public fun MutableItemProvider.setValue(key: String, value: Value?): Unit = - setItem(key, value?.let { MetaItem.ValueItem(value) }) - -public fun MutableItemProvider.setNode(key: String, meta: Meta?): Unit = - setItem(key, meta?.let { MetaItem.NodeItem(meta) }) - -public fun MutableItemProvider.node(key: Name? = null): ReadWriteProperty = item(key).convert( - reader = { it.node }, - writer = { it?.let { MetaItem.NodeItem(it) } } -) +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) @@ -35,17 +23,13 @@ public inline fun MutableItemProvider.remove(name: Name): Unit = setItem(name, n @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) - is MetaItem<*> -> setItem(name, value) - is Meta -> setNode(name, value) - is Configurable -> setNode(name, value.config) - else -> setValue(name, Value.of(value)) + else -> set(name, MetaItem.of(value)) } } @@ -59,30 +43,87 @@ public operator fun MutableItemProvider.set(key: String, index: String, value: A set(key.toName().withIndex(index), value) - /* Same name siblings generation */ public fun MutableItemProvider.setIndexedItems( name: Name, - items: Iterable>, - indexFactory: (MetaItem<*>, index: Int) -> String = { _, index -> index.toString() } + items: Iterable, + 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 - setItem(Name(tokens), meta) + set(Name(tokens), meta) } } public fun MutableItemProvider.setIndexed( name: Name, metas: Iterable, - indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() } + indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() }, ) { - setIndexedItems(name, metas.map { MetaItem.NodeItem(it) }) { item, index -> indexFactory(item.node!!, index) } + setIndexedItems(name, metas.map { MetaItemNode(it) }) { item, index -> indexFactory(item.node!!, index) } } public operator fun MutableItemProvider.set(name: Name, metas: Iterable): Unit = setIndexed(name, metas) -public operator fun MutableItemProvider.set(name: String, metas: Iterable): Unit = setIndexed(name.toName(), metas) +public operator fun MutableItemProvider.set(name: String, metas: Iterable): 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) + } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt index 7903ba87..4524f11e 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -1,11 +1,10 @@ package hep.dataforge.meta +import hep.dataforge.misc.DFExperimental import hep.dataforge.names.* -public interface MutableMeta> : MetaNode, MutableItemProvider { - override val items: Map> -// fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) -// fun removeListener(owner: Any? = null) +public interface MutableMeta> : TypedMeta, MutableItemProvider { + override val items: Map> } /** @@ -13,15 +12,15 @@ public interface MutableMeta> : MetaNode, MutableItemP * * Changes in Meta are not thread safe. */ -public abstract class AbstractMutableMeta> : AbstractMetaNode(), MutableMeta { - protected val children: MutableMap> = LinkedHashMap() +public abstract class AbstractMutableMeta> : AbstractTypedMeta(), MutableMeta { + protected val children: MutableMap> = LinkedHashMap() - override val items: Map> + override val items: Map> get() = children //protected abstract fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) - protected open fun replaceItem(key: NameToken, oldItem: MetaItem?, newItem: MetaItem?) { + protected open fun replaceItem(key: NameToken, oldItem: TypedMetaItem?, newItem: TypedMetaItem?) { if (newItem == null) { children.remove(key) } else { @@ -30,11 +29,10 @@ public abstract class AbstractMutableMeta> : AbstractMetaNode //itemChanged(key.asName(), oldItem, newItem) } - @Suppress("UNCHECKED_CAST") - protected fun wrapItem(item: MetaItem<*>?): MetaItem? = when (item) { + private fun wrapItem(item: MetaItem?): TypedMetaItem? = when (item) { null -> null - is MetaItem.ValueItem -> item - is MetaItem.NodeItem -> MetaItem.NodeItem(wrapNode(item.node)) + is MetaItemValue -> item + is MetaItemNode -> MetaItemNode(wrapNode(item.node)) } /** @@ -47,57 +45,41 @@ public abstract class AbstractMutableMeta> : AbstractMetaNode */ internal abstract fun empty(): M - override fun setItem(name: Name, item: MetaItem<*>?) { + override fun setItem(name: Name, item: MetaItem?) { when (name.length) { - 0 -> error("Can't setValue meta item for empty name") + 0 -> error("Can't set a meta item for empty name") 1 -> { val token = name.firstOrNull()!! - @Suppress("UNCHECKED_CAST") val oldItem: MetaItem? = get(name) as? MetaItem + val oldItem: TypedMetaItem? = getItem(name) replaceItem(token, oldItem, wrapItem(item)) } else -> { val token = name.firstOrNull()!! //get existing or create new node. Query is ignored for new node if (items[token] == null) { - replaceItem(token, null, MetaItem.NodeItem(empty())) + replaceItem(token, null, MetaItemNode(empty())) } - items[token]?.node!!.setItem(name.cutFirst(), item) + items[token]?.node!!.set(name.cutFirst(), item) } } } } -/** - * 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 > M.update(meta: Meta) { - meta.items.forEach { entry -> - when (val value = entry.value) { - is MetaItem.ValueItem -> setValue(entry.key.asName(), value.value) - is MetaItem.NodeItem -> (this[entry.key.asName()] as? MetaItem.NodeItem)?.node?.update(value.node) - ?: run { setNode(entry.key.asName(), value.node) } - } - } -} - /** * Append the node with a same-name-sibling, automatically generating numerical index */ -public fun > M.append(name: Name, value: Any?) { +public fun MutableItemProvider.append(name: Name, value: Any?) { require(!name.isEmpty()) { "Name could not be empty for append operation" } val newIndex = name.lastOrNull()!!.index if (newIndex != null) { set(name, value) } else { - val index = (getIndexed(name).keys.mapNotNull { it.toIntOrNull() }.maxOrNull() ?: -1) + 1 + val index = (getIndexed(name).keys.mapNotNull { it?.toIntOrNull() }.maxOrNull() ?: -1) + 1 set(name.withIndex(index.toString()), value) } } -public fun > M.append(name: String, value: Any?): Unit = append(name.toName(), value) +public fun MutableItemProvider.append(name: String, value: Any?): Unit = append(name.toName(), value) /** * Apply existing node with given [builder] or create a new element with it. @@ -106,7 +88,7 @@ public fun > M.append(name: String, value: Any?): Unit = appe public fun > M.edit(name: Name, builder: M.() -> Unit) { val item = when (val existingItem = get(name)) { null -> empty().also { set(name, it) } - is MetaItem.NodeItem -> existingItem.node + is MetaItemNode -> existingItem.node else -> error("Can't edit value meta item") } item.apply(builder) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt index 1fc74316..1f1b3b4d 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt @@ -9,35 +9,39 @@ import hep.dataforge.names.asName * 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 */ -public open class Scheme( - config: Config = Config(), - internal var default: ItemProvider? = null, - descriptor: NodeDescriptor? = null, -) : Configurable, MutableItemProvider, Described, MetaRepr { +public open class Scheme() : MutableItemProvider, Described, MetaRepr { - override var config: Config = config - internal set(value) { - //Fix problem with `init` blocks in specifications - field = value.apply { update(field) } - } + private var items: MutableItemProvider = Config() - override var descriptor: NodeDescriptor? = descriptor - internal set + private var default: ItemProvider? = null + final override var descriptor: NodeDescriptor? = null - public fun getDefaultItem(name: Name): MetaItem<*>? { - return default?.getItem(name) ?: descriptor?.get(name)?.defaultItem() + internal fun wrap( + items: MutableItemProvider, + default: ItemProvider? = null, + descriptor: NodeDescriptor? = null, + ) { + //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() } /** * Get a property with default */ - override fun getItem(name: Name): MetaItem<*>? = config[name] ?: getDefaultItem(name) + override fun getItem(name: Name): MetaItem? = items[name] ?: getDefaultItem(name) /** * Check if property with given [name] could be assigned to [item] */ - public fun validateItem(name: Name, item: MetaItem<*>?): Boolean { + public open fun validateItem(name: Name, item: MetaItem?): Boolean { val descriptor = descriptor?.get(name) return descriptor?.validateItem(item) ?: true } @@ -45,26 +49,26 @@ public open class Scheme( /** * Set a configurable property */ - override fun setItem(name: Name, item: MetaItem<*>?) { + override fun setItem(name: Name, item: MetaItem?) { if (validateItem(name, item)) { - config.setItem(name, item) + items[name] = item } else { error("Validation failed for property $name with value $item") } } /** - * Provide a default layer which returns items from [defaultProvider] and falls back to descriptor + * Provide a default layer which returns items from [default] and falls back to descriptor * values if default value is unavailable. - * Values from [defaultProvider] completely replace + * Values from [default] completely replace */ public open val defaultLayer: Meta get() = object : MetaBase() { - override val items: Map> = buildMap { + override val items: Map = buildMap { descriptor?.items?.forEach { (key, itemDescriptor) -> val token = NameToken(key) val name = token.asName() - val item = default?.getItem(name) ?: itemDescriptor.defaultItem() + val item = default?.get(name) ?: itemDescriptor.defaultItem() if (item != null) { put(token, item) } @@ -72,11 +76,32 @@ public open class Scheme( } } - override fun toMeta(): Laminate = Laminate(config, defaultLayer) - - public fun isEmpty(): Boolean = config.isEmpty() + override fun toMeta(): Laminate = Laminate(items[Name.EMPTY].node, defaultLayer) } +/** + * 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 > 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. + * Current state of the scheme used as a default. + */ +public fun T.retarget(provider: MutableItemProvider) :T = apply { wrap(provider) } + /** * A shortcut to edit a [Scheme] object in-place */ @@ -85,46 +110,20 @@ public inline operator fun T.invoke(block: T.() -> Unit): T = apply /** * A specification for simplified generation of wrappers */ -public open class SchemeSpec( - private val builder: (config: Config, defaultProvider: ItemProvider, descriptor: NodeDescriptor?) -> T, +public open class SchemeSpec( + private val builder: () -> T, ) : Specification, Described { - public constructor(emptyBuilder: () -> T) : this({ config: Config, defaultProvider: ItemProvider, descriptor: NodeDescriptor? -> - emptyBuilder().apply { - this.config = config - this.default = defaultProvider - this.descriptor = descriptor - } - }) + override fun empty(): T = builder() - override fun read(meta: Meta, defaultProvider: ItemProvider): T = - builder(Config(), meta.withDefault(defaultProvider), descriptor) + override fun read(items: ItemProvider): T = wrap(Config(), items, descriptor) - override fun wrap(config: Config, defaultProvider: ItemProvider): T { - return builder(config, defaultProvider, descriptor) - } + override fun write(target: MutableItemProvider, defaultProvider: ItemProvider): T = + wrap(target, defaultProvider, descriptor) //TODO Generate descriptor from Scheme class override val descriptor: NodeDescriptor? get() = null @Suppress("OVERRIDE_BY_INLINE") final override inline operator fun invoke(action: T.() -> Unit): T = empty().apply(action) -} - -///** -// * A scheme that uses [Meta] as a default layer -// */ -//public open class MetaScheme( -// private val meta: Meta, -// override val descriptor: NodeDescriptor? = null, -// config: Config = Config(), -//) : Scheme(config, meta) { -// override val defaultLayer: Meta get() = Laminate(meta, descriptor?.defaultItem().node) -//} - -public fun Meta.asScheme(): Scheme = Scheme().apply { - config = this@asScheme.asConfig() -} - -public fun Meta.toScheme(spec: Specification, block: T.() -> Unit = {}): T = - spec.read(this).apply(block) +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/SealedMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/SealedMeta.kt new file mode 100644 index 00000000..2e559796 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/SealedMeta.kt @@ -0,0 +1,23 @@ +package hep.dataforge.meta + +import hep.dataforge.names.NameToken + +/** + * The meta implementation which is guaranteed to be immutable. + * + * If the argument is possibly mutable node, it is copied on creation + */ +public class SealedMeta internal constructor( + override val items: Map>, +) : AbstractTypedMeta() + +/** + * 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() }) + +@Suppress("UNCHECKED_CAST") +public fun MetaItem.seal(): TypedMetaItem = when (this) { + is MetaItemValue -> this + is MetaItemNode -> MetaItemNode(node.seal()) +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt index 05c31dc3..e6edd22e 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt @@ -2,31 +2,21 @@ package hep.dataforge.meta import hep.dataforge.names.Name import hep.dataforge.names.asName -import kotlin.jvm.JvmName import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -/** - * Allows to apply custom configuration in a type safe way to simple untyped configuration. - * By convention [Scheme] companion should inherit this class - * - */ -public interface Specification { +public interface ReadOnlySpecification { + /** * Read generic read-only meta with this [Specification] producing instance of desired type. */ - public fun read(meta: Meta, defaultProvider: ItemProvider = ItemProvider.EMPTY): T + public fun read(items: ItemProvider): T - /** - * Wrap mutable [Config], using it as inner storage (changes to [Specification] are reflected on [Config] - */ - public fun wrap(config: Config, defaultProvider: ItemProvider = ItemProvider.EMPTY): T = - read(config as Meta, defaultProvider) /** * Generate an empty object */ - public fun empty(): T = read(Meta.EMPTY) + public fun empty(): T /** * A convenience method to use specifications in builders @@ -34,18 +24,25 @@ public interface Specification { public operator fun invoke(action: T.() -> Unit): T = empty().apply(action) } -/** - * Update given configuration using given type as a builder - */ -public fun Specification.update(meta: Meta, action: T.() -> Unit): T = - read(meta).apply(action) - /** - * Apply specified configuration to configurable + * Allows to apply custom configuration in a type safe way to simple untyped configuration. + * By convention [Scheme] companion should inherit this class + * */ -public fun > T.configure(spec: S, action: C.() -> Unit): T = - apply { spec.update(toMeta(), action) } +public interface Specification: ReadOnlySpecification { + /** + * Wrap [MutableItemProvider], using it as inner storage (changes to [Specification] are reflected on [MutableItemProvider] + */ + public fun write(target: MutableItemProvider, defaultProvider: ItemProvider = ItemProvider.EMPTY): T +} + +/** + * Update a [MutableItemProvider] using given specification + */ +public fun MutableItemProvider.update(spec: Specification, action: T.() -> Unit) { + spec.write(this).apply(action) +} /** * Update configuration using given specification @@ -53,50 +50,24 @@ public fun > T.confi public fun > Configurable.update( spec: S, action: C.() -> Unit, -): Configurable = - apply { spec.update(config, action) } - -/** - * Create a style based on given specification - */ -public fun > S.createStyle(action: C.() -> Unit): Meta = - Config().also { update(it, action) } - -public fun MetaItem<*>.spec(spec: Specification): T? = node?.let { - spec.wrap( - Config(), it - ) +) { + config.update(spec, action) } -@JvmName("configSpec") -public fun MetaItem.spec(spec: Specification): T? = node?.let { spec.wrap(it) } - -public fun MutableItemProvider.spec( - spec: Specification, key: Name? = null, -): ReadWriteProperty = object : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): T? { - val name = key ?: property.name.asName() - return getItem(name).node?.let { spec.read(it) } - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { - val name = key ?: property.name.asName() - setItem(name, value?.toMeta()?.asMetaItem()) - } -} +public fun TypedMetaItem>.withSpec(spec: Specification): T? = + node?.let { spec.write(it) } public fun MutableItemProvider.spec( spec: Specification, - default: T, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): T { val name = key ?: property.name.asName() - return getItem(name).node?.let { spec.read(it) } ?: default + return getChild(name).let { spec.write(it) } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { val name = key ?: property.name.asName() - setItem(name, value.toMeta().asMetaItem()) + set(name, value.toMeta().asMetaItem()) } -} \ No newline at end of file +} diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/TypedMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/TypedMeta.kt new file mode 100644 index 00000000..786054c1 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/TypedMeta.kt @@ -0,0 +1,51 @@ +package hep.dataforge.meta + +import hep.dataforge.names.Name +import hep.dataforge.names.NameToken +import hep.dataforge.names.toName +import kotlinx.serialization.json.Json + +/** + * A meta node that ensures that all of its descendants has at least the same type + */ +public interface TypedMeta> : Meta { + override val items: Map> + + @Suppress("UNCHECKED_CAST") + override fun getItem(name: Name): TypedMetaItem? = super.getItem(name)?.let { it as TypedMetaItem } + //Typed meta guarantees that all children have M type +} + + +/** + * The same as [Meta.get], but with specific node type + */ +public operator fun > M.get(name: Name): TypedMetaItem? = getItem(name) + + +public operator fun > M.get(key: String): TypedMetaItem? = this[key.toName()] +public operator fun > M.get(key: NameToken): TypedMetaItem? = 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) { + this.items == other.items + } 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> : TypedMeta, MetaBase() diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt deleted file mode 100644 index 69ffa8aa..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hep.dataforge.meta - -/** - * General marker for dataforge builders - */ -@DslMarker -public annotation class DFBuilder - -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Retention(AnnotationRetention.BINARY) -public annotation class DFExperimental \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt index 9a5b915d..6181bfbb 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt @@ -1,16 +1,13 @@ package hep.dataforge.meta.descriptors -import hep.dataforge.meta.Laminate -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBase -import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.* import hep.dataforge.names.NameToken /** * A [Meta] that is constructed from [NodeDescriptor] */ private class DescriptorMeta(val descriptor: NodeDescriptor) : Meta, MetaBase() { - override val items: Map> + override val items: Map get() = buildMap { descriptor.items.forEach { (token, descriptorItem) -> val item = descriptorItem.defaultItem() @@ -27,22 +24,22 @@ private class DescriptorMeta(val descriptor: NodeDescriptor) : Meta, MetaBase() public fun NodeDescriptor.defaultMeta(): Laminate = Laminate(default, DescriptorMeta(this)) /** - * Build a default [MetaItem.NodeItem] from this node descriptor + * Build a default [MetaItemNode] from this node descriptor */ -internal fun NodeDescriptor.defaultItem(): MetaItem.NodeItem<*> = - MetaItem.NodeItem(defaultMeta()) +internal fun NodeDescriptor.defaultItem(): MetaItemNode<*> = + MetaItemNode(defaultMeta()) /** - * Build a default [MetaItem.ValueItem] from this descriptor + * Build a default [MetaItemValue] from this descriptor */ -internal fun ValueDescriptor.defaultItem(): MetaItem.ValueItem? { - return MetaItem.ValueItem(default ?: return null) +internal fun ValueDescriptor.defaultItem(): MetaItemValue? { + return MetaItemValue(default ?: return null) } /** - * Build a default [MetaItem] from descriptor. + * Build a default [TypedMetaItem] from descriptor. */ -public fun ItemDescriptor.defaultItem(): MetaItem<*>? { +public fun ItemDescriptor.defaultItem(): MetaItem? { return when (this) { is ValueDescriptor -> defaultItem() is NodeDescriptor -> defaultItem() diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt index cbb56fa1..33343fe1 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt @@ -1,14 +1,15 @@ package hep.dataforge.meta.descriptors import hep.dataforge.meta.* +import hep.dataforge.misc.DFBuilder import hep.dataforge.names.* import hep.dataforge.values.* /** - * A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [MetaItem] or a group of same-name-siblings. + * A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings. */ @DFBuilder -public sealed class ItemDescriptor(public val config: Config) { +public sealed class ItemDescriptor(final override val config: Config) : Configurable { /** * True if same name siblings with this name are allowed @@ -38,7 +39,9 @@ public sealed class ItemDescriptor(public val config: Config) { */ public var indexKey: String by config.string(DEFAULT_INDEX_KEY) - public companion object{ + public abstract fun copy(): ItemDescriptor + + public companion object { public const val DEFAULT_INDEX_KEY: String = "@index" } } @@ -46,14 +49,14 @@ public sealed class ItemDescriptor(public val config: Config) { /** * Configure attributes of the descriptor, creating an attributes node if needed. */ -public fun ItemDescriptor.attributes(block: Config.() -> Unit) { +public inline fun ItemDescriptor.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 { +public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean { if (item == null) return !required return when (this) { is ValueDescriptor -> isAllowedValue(item.value ?: return false) @@ -93,12 +96,13 @@ public class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) * The map of children item descriptors (both nodes and values) */ public val items: Map - get() = config.getIndexed(ITEM_KEY).mapValues { (_, item) -> + 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) { - NodeDescriptor(node) + name to NodeDescriptor(node as Config) } else { - ValueDescriptor(node) + name to ValueDescriptor(node as Config) } } @@ -110,8 +114,9 @@ public class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) 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 NodeDescriptor(node) + name to NodeDescriptor(node as Config) } /** @@ -121,8 +126,9 @@ public class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) 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 ValueDescriptor(node) + name to ValueDescriptor(node as Config) } private fun buildNode(name: Name): NodeDescriptor { @@ -180,17 +186,20 @@ public class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) value(name.toName(), block) } + override fun copy(): NodeDescriptor = NodeDescriptor(config.toConfig()) + public companion object { internal val ITEM_KEY: Name = "item".asName() internal val IS_NODE_KEY: Name = "@isNode".asName() - public inline operator fun invoke(block: NodeDescriptor.() -> Unit): NodeDescriptor = NodeDescriptor().apply(block) - //TODO infer descriptor from spec } } +public inline fun NodeDescriptor(block: NodeDescriptor.() -> Unit): NodeDescriptor = + NodeDescriptor().apply(block) + /** * Get a descriptor item associated with given name or null if item for given name not provided */ @@ -266,12 +275,12 @@ public class ValueDescriptor(config: Config = Config()) : ItemDescriptor(config) val value = it.value when { value?.list != null -> value.list - type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN} ?: false -> listOf(True, False) + type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False) else -> emptyList() } }, writer = { - MetaItem.ValueItem(it.asValue()) + MetaItemValue(it.asValue()) } ) @@ -281,6 +290,8 @@ public class ValueDescriptor(config: Config = Config()) : ItemDescriptor(config) public fun allow(vararg v: Any) { this.allowedValues = v.map { Value.of(it) } } + + override fun copy(): ValueDescriptor = ValueDescriptor(config.toConfig()) } /** diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/descriptorExtensions.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/descriptorExtensions.kt new file mode 100644 index 00000000..f05af69e --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/descriptorExtensions.kt @@ -0,0 +1,18 @@ +package hep.dataforge.meta.descriptors + +import hep.dataforge.names.Name +import hep.dataforge.values.ValueType +import hep.dataforge.values.asValue + +public inline fun > NodeDescriptor.enum( + key: Name, + default: E?, + crossinline modifier: ValueDescriptor.() -> Unit = {}, +): Unit = value(key) { + type(ValueType.STRING) + default?.let { + default(default) + } + allowedValues = enumValues().map { it.asValue() } + modifier() +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/getIndexed.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/getIndexed.kt deleted file mode 100644 index 20ce93dc..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/getIndexed.kt +++ /dev/null @@ -1,36 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.* - -/** - * 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> { - val root = when (name.length) { - 0 -> error("Can't use empty name for 'getIndexed'") - 1 -> this - 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 Meta.getIndexed(name: String): Map> = this@getIndexed.getIndexed(name.toName()) - -/** - * Get all items matching given name. - */ -@Suppress("UNCHECKED_CAST") -public fun > M.getIndexed(name: Name): Map> = - (this as Meta).getIndexed(name) as Map> - -public fun > M.getIndexed(name: String): Map> = - getIndexed(name.toName()) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt index 774a736a..371a67a0 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt @@ -1,6 +1,7 @@ package hep.dataforge.meta import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.misc.DFExperimental import hep.dataforge.names.toName import hep.dataforge.values.ListValue import hep.dataforge.values.Value @@ -11,36 +12,36 @@ import hep.dataforge.values.Value public fun Meta.toMap(descriptor: NodeDescriptor? = null): Map { return items.entries.associate { (token, item) -> token.toString() to when (item) { - is MetaItem.NodeItem -> item.node.toMap() - is MetaItem.ValueItem -> item.value.value + is MetaItemNode -> item.node.toMap() + is MetaItemValue -> item.value.value } } } /** - * Convert map of maps to meta. This method will recognize [MetaItem], [Map] and [List] of all mentioned above as value. + * Convert map of maps to meta. This method will recognize [TypedMetaItem], [Map] and [List] of all mentioned above as value. * All other values will be converted to values. */ @DFExperimental public fun Map.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta { @Suppress("UNCHECKED_CAST") - fun toItem(value: Any?): MetaItem<*> = when (value) { - is MetaItem<*> -> value - is Meta -> MetaItem.NodeItem(value) - is Map<*, *> -> MetaItem.NodeItem((value as Map).toMeta()) - else -> MetaItem.ValueItem(Value.of(value)) + fun toItem(value: Any?): MetaItem = when (value) { + is MetaItem -> value + is Meta -> MetaItemNode(value) + is Map<*, *> -> MetaItemNode((value as Map).toMeta()) + else -> MetaItemValue(Value.of(value)) } entries.forEach { (key, value) -> if (value is List<*>) { val items = value.map { toItem(it) } - if (items.all { it is MetaItem.ValueItem }) { - setValue(key, ListValue(items.map { it.value!! })) + if (items.all { it is MetaItemValue }) { + set(key, ListValue(items.map { it.value!! })) } else { setIndexedItems(key.toName(), value.map { toItem(it) }) } } else { - setItem(key, toItem(value)) + set(key, toItem(value)) } } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt index 0e4d6261..9d1c08d8 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt @@ -4,121 +4,121 @@ import hep.dataforge.meta.* import hep.dataforge.values.* /** - * A converter of generic object to and from [MetaItem] + * A converter of generic object to and from [TypedMetaItem] */ public interface MetaConverter { - public fun itemToObject(item: MetaItem<*>): T - public fun objectToMetaItem(obj: T): MetaItem<*> + public fun itemToObject(item: MetaItem): T + public fun objectToMetaItem(obj: T): MetaItem public companion object { - public val item: MetaConverter> = object : MetaConverter> { - override fun itemToObject(item: MetaItem<*>): MetaItem<*> = item - override fun objectToMetaItem(obj: MetaItem<*>): MetaItem<*> = obj + public val item: MetaConverter = object : MetaConverter { + override fun itemToObject(item: MetaItem): MetaItem = item + override fun objectToMetaItem(obj: MetaItem): MetaItem = obj } public val meta: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Meta = when (item) { - is MetaItem.NodeItem -> item.node - is MetaItem.ValueItem -> item.value.toMeta() + override fun itemToObject(item: MetaItem): Meta = when (item) { + is MetaItemNode -> item.node + is MetaItemValue -> item.value.toMeta() } - override fun objectToMetaItem(obj: Meta): MetaItem<*> = MetaItem.NodeItem(obj) + override fun objectToMetaItem(obj: Meta): MetaItem = MetaItemNode(obj) } public val value: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Value = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + override fun 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<*> = MetaItem.ValueItem(obj) + override fun objectToMetaItem(obj: Value): MetaItem = MetaItemValue(obj) } public val string: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): String = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + 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<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: String): MetaItem = MetaItemValue(obj.asValue()) } public val boolean: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Boolean = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + 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<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: Boolean): MetaItem = MetaItemValue(obj.asValue()) } public val number: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Number = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + override fun 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<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: Number): MetaItem = MetaItemValue(obj.asValue()) } public val double: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Double = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + 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 objectToMetaItem(obj: Double): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: Double): MetaItem = MetaItemValue(obj.asValue()) } public val float: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Float = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + override fun 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 objectToMetaItem(obj: Float): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: Float): MetaItem = MetaItemValue(obj.asValue()) } public val int: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Int = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + 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 objectToMetaItem(obj: Int): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: Int): MetaItem = MetaItemValue(obj.asValue()) } public val long: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem<*>): Long = when (item) { - is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItem.ValueItem -> item.value + override fun 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 objectToMetaItem(obj: Long): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: Long): MetaItem = MetaItemValue(obj.asValue()) } public inline fun > enum(): MetaConverter = object : MetaConverter { @Suppress("USELESS_CAST") - override fun itemToObject(item: MetaItem<*>): E = item.enum() as? E ?: error("The Item is not a Enum") + override fun itemToObject(item: MetaItem): E = item.enum() as? E ?: error("The Item is not a Enum") - override fun objectToMetaItem(obj: E): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + override fun objectToMetaItem(obj: E): MetaItem = MetaItemValue(obj.asValue()) } public fun valueList(writer: (T) -> Value = {Value.of(it)}, reader: (Value) -> T): MetaConverter> = object : MetaConverter> { - override fun itemToObject(item: MetaItem<*>): List = + override fun itemToObject(item: MetaItem): List = item.value?.list?.map(reader) ?: error("The item is not a value list") - override fun objectToMetaItem(obj: List): MetaItem<*> = - MetaItem.ValueItem(obj.map(writer).asValue()) + override fun objectToMetaItem(obj: List): MetaItem = + MetaItemValue(obj.map(writer).asValue()) } } } -public fun MetaConverter.nullableItemToObject(item: MetaItem<*>?): T? = item?.let { itemToObject(it) } -public fun MetaConverter.nullableObjectToMetaItem(obj: T?): MetaItem<*>? = obj?.let { objectToMetaItem(it) } +public fun MetaConverter.nullableItemToObject(item: MetaItem?): T? = item?.let { itemToObject(it) } +public fun MetaConverter.nullableObjectToMetaItem(obj: T?): MetaItem? = obj?.let { objectToMetaItem(it) } -public fun MetaConverter.metaToObject(meta: Meta): T = itemToObject(MetaItem.NodeItem(meta)) -public fun MetaConverter.valueToObject(value: Value): T = itemToObject(MetaItem.ValueItem(value)) +public fun MetaConverter.metaToObject(meta: Meta): T = itemToObject(MetaItemNode(meta)) +public fun MetaConverter.valueToObject(value: Value): T = itemToObject(MetaItemValue(value)) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt index 9529b28e..0e41b016 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt @@ -1,6 +1,7 @@ package hep.dataforge.meta.transformations import hep.dataforge.meta.* +import hep.dataforge.misc.DFExperimental import hep.dataforge.names.Name /** @@ -11,7 +12,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: MetaItem?): Boolean /** * Select all items to be transformed. Item could be a value as well as node @@ -19,12 +20,12 @@ public interface TransformationRule { * @return a sequence of item paths to be transformed */ public fun selectItems(meta: Meta): Sequence = - meta.sequence().filter { matches(it.first, it.second) }.map { it.first } + meta.itemSequence().filter { matches(it.first, it.second) }.map { it.first } /** * Apply transformation for a single item (Node or Value) to the target */ - public fun > transformItem(name: Name, item: MetaItem<*>?, target: M): Unit + public fun > transformItem(name: Name, item: MetaItem?, target: M): Unit } /** @@ -32,15 +33,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: MetaItem?): Boolean { return selector(name) } override fun selectItems(meta: Meta): Sequence = - meta.sequence().map { it.first }.filter(selector) + meta.itemSequence().map { it.first }.filter(selector) - override fun > transformItem(name: Name, item: MetaItem<*>?, target: M) { - if (selector(name)) target.setItem(name, item) + override fun > transformItem(name: Name, item: MetaItem?, target: M) { + if (selector(name)) target.set(name, item) } } @@ -49,15 +50,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, MetaItem?) -> Unit, ) : TransformationRule { - override fun matches(name: Name, item: MetaItem<*>?): Boolean { + override fun matches(name: Name, item: MetaItem?): Boolean { return name == from } override fun selectItems(meta: Meta): Sequence = sequenceOf(from) - override fun > transformItem(name: Name, item: MetaItem<*>?, target: M) { + override fun > transformItem(name: Name, item: MetaItem?, target: M) { if (name == this.from) { target.transform(name, item) } @@ -66,13 +67,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, MetaItem?) -> Unit, ) : TransformationRule { - override fun matches(name: Name, item: MetaItem<*>?): Boolean { + override fun matches(name: Name, item: MetaItem?): Boolean { return from.matches(name.toString()) } - override fun > transformItem(name: Name, item: MetaItem<*>?, target: M) { + override fun > transformItem(name: Name, item: MetaItem?, target: M) { val match = from.matchEntire(name.toString()) if (match != null) { target.transform(name, match, item) @@ -102,7 +103,7 @@ public inline class MetaTransformation(public val transformations: Collection rule.selectItems(source).forEach { name -> rule.transformItem(name, source[name], this) @@ -116,7 +117,7 @@ public inline class MetaTransformation(public val transformations: Collection rule.selectItems(source).forEach { name -> remove(name) @@ -170,14 +171,14 @@ public class MetaTransformationBuilder { public fun keep(regex: String) { transformations.add( RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem -> - setItem(name, metaItem) + set(name, metaItem) }) } /** * 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: (MetaItem?) -> Any? = { it }) { transformations.add( SingleItemTransformationRule(from) { _, item -> set(to, operation(item)) diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Named.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/Named.kt similarity index 97% rename from dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Named.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/Named.kt index e32946e0..466f5ee0 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Named.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/Named.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package hep.dataforge.context +package hep.dataforge.misc import hep.dataforge.names.Name import hep.dataforge.names.asName diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Type.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/Type.kt similarity index 90% rename from dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Type.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/Type.kt index e07cfd10..6f6410b5 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Type.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/Type.kt @@ -1,4 +1,4 @@ -package hep.dataforge.provider +package hep.dataforge.misc /** * A text label for internal DataForge type classification. Alternative for mime container type. diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/annotations.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/annotations.kt new file mode 100644 index 00000000..7b271a4b --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/misc/annotations.kt @@ -0,0 +1,21 @@ +package hep.dataforge.misc + +/** + * General marker for dataforge builders + */ +@DslMarker +public annotation class DFBuilder + +/** + * The declaration is experimental and could be changed in future + */ +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Retention(AnnotationRetention.BINARY) +public annotation class DFExperimental + +/** + * The declaration is internal to the DataForge and could use unsafe or unstable features. + */ +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Retention(AnnotationRetention.BINARY) +public annotation class DFInternal \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt index 9cce4314..3bb24ab1 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -1,5 +1,6 @@ package hep.dataforge.names +import hep.dataforge.misc.DFExperimental import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -39,6 +40,18 @@ public class Name(public val tokens: List) { public companion object : KSerializer { public const val NAME_SEPARATOR: String = "." + /** + * Match any single token (both body and index) + */ + @DFExperimental + public val MATCH_ANY_TOKEN: NameToken = NameToken("*") + + /** + * Token that allows to match the whole tail or the whole head of the name. Must match at least one token. + */ + @DFExperimental + public val MATCH_ALL_TOKEN: NameToken = NameToken("**") + public val EMPTY: Name = Name(emptyList()) override val descriptor: SerialDescriptor = @@ -114,7 +127,7 @@ public fun String.toName(): Name { } else -> when (it) { '.' -> { - val query = if(queryBuilder.isEmpty()) null else queryBuilder.toString() + val query = if (queryBuilder.isEmpty()) null else queryBuilder.toString() yield(NameToken(bodyBuilder.toString(), query)) bodyBuilder = StringBuilder() queryBuilder = StringBuilder() @@ -128,7 +141,7 @@ public fun String.toName(): Name { } } } - val query = if(queryBuilder.isEmpty()) null else queryBuilder.toString() + val query = if (queryBuilder.isEmpty()) null else queryBuilder.toString() yield(NameToken(bodyBuilder.toString(), query)) } return Name(tokens.toList()) @@ -173,7 +186,6 @@ public fun Name.withIndex(index: String): Name { * Fast [String]-based accessor for item map */ public operator fun Map.get(body: String, query: String? = null): T? = get(NameToken(body, query)) - public operator fun Map.get(name: String): T? = get(name.toName()) public operator fun MutableMap.set(name: String, value: T): Unit = set(name.toName(), value) @@ -184,7 +196,16 @@ public fun Name.startsWith(token: NameToken): Boolean = firstOrNull() == token public fun Name.endsWith(token: NameToken): Boolean = lastOrNull() == token public fun Name.startsWith(name: Name): Boolean = - this.length >= name.length && tokens.subList(0, name.length) == name.tokens + this.length >= name.length && (this == name || tokens.subList(0, name.length) == name.tokens) public fun Name.endsWith(name: Name): Boolean = - this.length >= name.length && tokens.subList(length - name.length, length) == name.tokens \ No newline at end of file + 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 + */ +public fun Name.removeHeadOrNull(head: Name): Name? = if (startsWith(head)) { + Name(tokens.subList(head.length, length)) +} else { + null +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/nameMatcher.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/nameMatcher.kt new file mode 100644 index 00000000..5b786917 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/nameMatcher.kt @@ -0,0 +1,47 @@ +package hep.dataforge.names + +import hep.dataforge.misc.DFExperimental + + +/** + * Checks if this token matches a given [NameToken]. The match successful if: + * * Token body matches pattern body as a regex + * * Index body matches pattern body as a regex of both are null + */ +@DFExperimental +public fun NameToken.matches(pattern: NameToken): Boolean { + if (pattern == Name.MATCH_ANY_TOKEN) return true + val bodyMatches = body.matches(pattern.body.toRegex()) + val indexMatches = (index == null && pattern.index == null) || pattern.index?.let { patternIndex -> + (index ?: "").matches(patternIndex.toRegex()) + } ?: false + return bodyMatches && indexMatches +} + + +/** + * Matches all names in pattern according to [NameToken.matches] rules. + */ +@DFExperimental +public fun Name.matches(pattern: Name): Boolean = when { + pattern.endsWith(Name.MATCH_ALL_TOKEN) -> { + length >= pattern.length + && Name(tokens.subList(0, pattern.length - 1)).matches(pattern.cutLast()) + } + pattern.startsWith(Name.MATCH_ALL_TOKEN) -> { + length >= pattern.length + && Name(tokens.subList(tokens.size - pattern.length + 1, tokens.size)).matches(pattern.cutFirst()) + } + else -> { + tokens.indices.forEach { + val thisToken = tokens.getOrNull(it) ?: return false + if (thisToken == Name.MATCH_ALL_TOKEN) error("Match-all token in the middle of the name is not supported yet") + val patternToken = pattern.tokens.getOrNull(it) ?: return false + if (!thisToken.matches(patternToken)) return false + } + true + } +} + +@OptIn(DFExperimental::class) +public fun Name.matches(pattern: String): Boolean = matches(pattern.toName()) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt index ca78f0ae..73b1318c 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt @@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable */ @Serializable public enum class ValueType { - NUMBER, STRING, BOOLEAN, NULL + NUMBER, STRING, BOOLEAN, LIST, NULL } /** @@ -31,16 +31,6 @@ public interface Value { */ public val type: ValueType - /** - * get this value represented as Number - */ - public val number: Number - - /** - * get this value represented as String - */ - public val string: String - /** * get this value represented as List */ @@ -50,8 +40,10 @@ public interface Value { override fun hashCode(): Int + override fun toString(): String + public companion object { - public const val TARGET: String = "value" + public const val TYPE: String = "value" /** * Convert object to value @@ -86,16 +78,27 @@ public interface Value { } } +public val Value.string: String get() = toString() + +/** + * get this value represented as Number + */ +public val Value.numberOrNull: Number? + get() = if (this is NumberValue) number else string.toDoubleOrNull() + +/** + * Return [Value] number content or throw error if value is not a number + */ +public val Value.number: Number + get() = (if (this is NumberValue) number else numberOrNull ?: error("The value is not a number")) + /** * A singleton null value */ public object Null : Value { override val value: Any? get() = null override val type: ValueType get() = ValueType.NULL - override val number: Number get() = Double.NaN - override val string: String get() = "@null" - - override fun toString(): String = value.toString() + override fun toString(): String = "@null" override fun equals(other: Any?): Boolean = other === Null override fun hashCode(): Int = 0 @@ -105,11 +108,8 @@ public object Null : Value { * Singleton true value */ public object True : Value { - override val value: Any? get() = true + override val value: Any get() = true override val type: ValueType get() = ValueType.BOOLEAN - override val number: Number get() = 1.0 - override val string: String get() = "true" - override fun toString(): String = value.toString() override fun equals(other: Any?): Boolean = other === True @@ -120,44 +120,42 @@ public object True : Value { * Singleton false value */ public object False : Value { - override val value: Any? get() = false + override val value: Any get() = false override val type: ValueType get() = ValueType.BOOLEAN - override val number: Number get() = -1.0 - override val string: String get() = "false" - override fun toString(): String = value.toString() override fun equals(other: Any?): Boolean = other === False override fun hashCode(): Int = -1 } -public class NumberValue(override val number: Number) : Value { - override val value: Any? get() = number +public class NumberValue(public val number: Number) : Value { + override val value: Any get() = number override val type: ValueType get() = ValueType.NUMBER - override val string: String get() = number.toString() + + override fun toString(): String = number.toString() override fun equals(other: Any?): Boolean { if (other !is Value) return false - return when (number) { - is Short -> number.toShort() == other.number.toShort() - is Long -> number.toLong() == other.number.toLong() - is Byte -> number.toByte() == other.number.toByte() - is Int -> number.toInt() == other.number.toInt() - is Float -> number.toFloat() == other.number.toFloat() - is Double -> number.toDouble() == other.number.toDouble() - else -> number.toString() == other.number.toString() + + val otherNumber = other.numberOrNull ?: return false + + return when (numberOrNull) { + is Short -> number.toShort() == otherNumber.toShort() + is Long -> number.toLong() == otherNumber.toLong() + is Byte -> number.toByte() == otherNumber.toByte() + is Int -> number.toInt() == otherNumber.toInt() + is Float -> number.toFloat() == otherNumber.toFloat() + is Double -> number.toDouble() == otherNumber.toDouble() + else -> number.toString() == otherNumber.toString() } } - override fun hashCode(): Int = number.hashCode() - - override fun toString(): String = value.toString() + override fun hashCode(): Int = numberOrNull.hashCode() } -public class StringValue(override val string: String) : Value { - override val value: Any? get() = string +public class StringValue(public val string: String) : Value { + override val value: Any get() = string override val type: ValueType get() = ValueType.STRING - override val number: Number get() = string.toDouble() override fun equals(other: Any?): Boolean { return this.string == (other as? Value)?.string @@ -165,40 +163,34 @@ public class StringValue(override val string: String) : Value { override fun hashCode(): Int = string.hashCode() - override fun toString(): String = "\"${value.toString()}\"" + override fun toString(): String = string } public class EnumValue>(override val value: E) : Value { override val type: ValueType get() = ValueType.STRING - override val number: Number get() = value.ordinal - override val string: String get() = value.name + + override fun toString(): String = value.toString() override fun equals(other: Any?): Boolean { return string == (other as? Value)?.string } override fun hashCode(): Int = value.hashCode() - - override fun toString(): String = value.toString() } -public class ListValue(override val list: List) : Value { - init { - require(list.isNotEmpty()) { "Can't create list value from empty list" } - } - +public class ListValue(override val list: List) : Value, Iterable { override val value: List get() = list - override val type: ValueType get() = list.first().type - override val number: Number get() = list.first().number - override val string: String get() = list.first().string + override val type: ValueType get() = ValueType.LIST override fun toString(): String = list.joinToString(prefix = "[", postfix = "]") + override fun iterator(): Iterator = list.iterator() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Value) return false if (other is DoubleArrayValue) { - return DoubleArray(list.size) { list[it].number.toDouble() }.contentEquals(other.value) + return DoubleArray(list.size) { list[it].numberOrNull?.toDouble() ?: Double.NaN }.contentEquals(other.value) } return list == other.list } @@ -207,7 +199,9 @@ public class ListValue(override val list: List) : Value { return list.hashCode() } - + public companion object{ + public val EMPTY: ListValue = ListValue(emptyList()) + } } public fun Number.asValue(): Value = NumberValue(this) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt index 08b87c18..d46dd4be 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt @@ -24,10 +24,10 @@ public object ValueSerializer : KSerializer { ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate? ValueType.BOOLEAN -> decodeBoolean().asValue() ValueType.STRING -> decodeString().asValue() + ValueType.LIST -> decodeSerializableValue(ListSerializer(ValueSerializer)).asValue() } } - override fun deserialize(decoder: Decoder): Value { val isList = decoder.decodeBoolean() return if (isList) { @@ -46,6 +46,7 @@ public object ValueSerializer : KSerializer { ValueType.NUMBER -> encodeDouble(value.double) ValueType.BOOLEAN -> encodeBoolean(value.boolean) ValueType.STRING -> encodeString(value.string) + ValueType.LIST -> encodeSerializableValue(ListSerializer(ValueSerializer),value.list) } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/exoticValues.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/exoticValues.kt index 4b04a036..b04524dc 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/exoticValues.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/exoticValues.kt @@ -4,18 +4,17 @@ package hep.dataforge.values /** * A value built from string which content and type are parsed on-demand */ -public class LazyParsedValue(override val string: String) : Value { +public class LazyParsedValue(public val string: String) : Value { private val parsedValue by lazy { string.parseValue() } override val value: Any? get() = parsedValue.value override val type: ValueType get() = parsedValue.type - override val number: Number get() = parsedValue.number override fun toString(): String = string override fun equals(other: Any?): Boolean = other is Value && this.parsedValue == other - override fun hashCode(): Int = string.hashCode() + override fun hashCode(): Int = string.hashCode() } public fun String.lazyParseValue(): LazyParsedValue = LazyParsedValue(this) @@ -23,10 +22,8 @@ public fun String.lazyParseValue(): LazyParsedValue = LazyParsedValue(this) /** * A performance optimized version of list value for doubles */ -public class DoubleArrayValue(override val value: DoubleArray) : Value { - override val type: ValueType get() = ValueType.NUMBER - override val number: Double get() = value.first() - override val string: String get() = value.first().toString() +public class DoubleArrayValue(override val value: DoubleArray) : Value, Iterable { + override val type: ValueType get() = ValueType.LIST override val list: List get() = value.map { NumberValue(it) } override fun equals(other: Any?): Boolean { @@ -43,7 +40,9 @@ public class DoubleArrayValue(override val value: DoubleArray) : Value { return value.contentHashCode() } - override fun toString(): String = list.joinToString (prefix = "[", postfix = "]") + override fun toString(): String = list.joinToString(prefix = "[", postfix = "]") + + override fun iterator(): Iterator = value.iterator() } -public fun DoubleArray.asValue(): Value = if(isEmpty()) Null else DoubleArrayValue(this) +public fun DoubleArray.asValue(): Value = if (isEmpty()) Null else DoubleArrayValue(this) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt index 2171c4ea..9347642b 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt @@ -9,9 +9,9 @@ import hep.dataforge.meta.MetaBuilder public fun Value.isNull(): Boolean = this == Null /** - * Check if value is list + * Check if value is list. */ -public fun Value.isList(): Boolean = this.list.size > 1 +public fun Value.isList(): Boolean = this.type == ValueType.LIST public val Value.boolean: Boolean get() = this == True diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt index 593401d1..ca5898e6 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt @@ -30,7 +30,7 @@ class MetaBuilderTest { "b.a[$it]" put it } }.seal() - assertEquals(10, meta.values().count()) + assertEquals(10, meta.valueSequence().count()) val nodes = meta.getIndexed("b.a") diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 6b8442d1..c460c602 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -30,7 +30,7 @@ class MetaDelegateTest { fun delegateTest() { val testObject = TestScheme.empty() - testObject.setValue("myValue","theString".asValue()) + testObject.set("myValue","theString".asValue()) testObject.enumValue = TestEnum.NO testObject.inner = InnerSpec { innerValue = "ddd" } @@ -38,7 +38,7 @@ class MetaDelegateTest { assertEquals("theString", testObject.myValue) assertEquals(TestEnum.NO, testObject.enumValue) assertEquals(2.2, testObject.safeValue) - assertEquals("ddd", testObject.inner?.innerValue) + assertEquals("ddd", testObject.inner.innerValue) } diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt index 12569bc3..89786ea1 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.misc.DFExperimental import hep.dataforge.values.NumberValue import hep.dataforge.values.True import hep.dataforge.values.Value diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt index 1f7d7e97..2dfd746c 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt @@ -3,30 +3,21 @@ package hep.dataforge.meta import kotlin.test.Test import kotlin.test.assertEquals - -class SchemeTest{ +class SchemeTest { @Test - fun testMetaScheme(){ - val styled = Meta { - repeat(10){ - "b.a[$it]" put { - "d" put it - } - } - }.asScheme() - - val meta = styled.toMeta() - - assertEquals(10, meta.values().count()) - - val bNode = styled.getItem("b").node - - val aNodes = bNode?.getIndexed("a") - - val allNodes = meta.getIndexed("b.a") - - assertEquals(3, aNodes?.get("3").node["d"].int) - assertEquals(3, allNodes["3"].node["d"].int) + fun testSchemeWrappingBeforeEdit(){ + val config = Config() + val scheme = TestScheme.wrap(config) + scheme.a = 29 + assertEquals(29, config["a"].int) } + @Test + fun testSchemeWrappingAfterEdit(){ + val scheme = TestScheme.empty() + scheme.a = 29 + val config = Config() + scheme.retarget(config) + assertEquals(29, scheme.a) + } } \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt index 587e2049..cd8b2f82 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt @@ -3,25 +3,79 @@ package hep.dataforge.meta import kotlin.test.Test import kotlin.test.assertEquals -class SpecificationTest { - class TestStyled : Scheme() { - var list by numberList(1, 2, 3) +internal class TestScheme : Scheme() { + var list by numberList(1, 2, 3) - companion object : Specification { - override fun read(meta: Meta, defaultProvider: ItemProvider): TestStyled = - TestStyled().apply { - this.config = meta.asConfig() + var a by int() + var b by string() + + companion object : Specification { + 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) - } - } } +} + +class SpecificationTest { + +// @Test +// fun testMetaScheme(){ +// val styled = Meta { +// repeat(10){ +// "b.a[$it]" put { +// "d" put it +// } +// } +// }.asScheme() +// +// val meta = styled.toMeta() +// +// assertEquals(10, meta.valueSequence().count()) +// +// val bNode = styled["b"].node +// +// val aNodes = bNode?.getIndexed("a") +// +// val allNodes = meta.getIndexed("b.a") +// +// assertEquals(3, aNodes?.get("3").node["d"].int) +// assertEquals(3, allNodes["3"].node["d"].int) +// } @Test fun testSpecific() { - val testObject = TestStyled { + val testObject = TestScheme { list = emptyList() } assertEquals(emptyList(), testObject.list) } + + @Test + fun testChildModification() { + val config = Config() + val child = config.getChild("child") + val scheme = TestScheme.write(child) + scheme.a = 22 + scheme.b = "test" + assertEquals(22, config["child.a"].int) + assertEquals("test", config["child.b"].string) + } + + @Test + fun testChildUpdate() { + val config = Config() + val child = config.getChild("child") + child.update(TestScheme) { + a = 22 + b = "test" + } + assertEquals(22, config["child.a"].int) + assertEquals("test", config["child.b"].string) + } } \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/names/NameMatchTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/names/NameMatchTest.kt new file mode 100644 index 00000000..1ff4381f --- /dev/null +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/names/NameMatchTest.kt @@ -0,0 +1,32 @@ +package hep.dataforge.names + +import hep.dataforge.misc.DFExperimental +import kotlin.test.Test +import kotlin.test.assertFails +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@OptIn(DFExperimental::class) +class NameMatchTest { + @Test + fun matchWildCards() { + val theName = "a.b.c.d".toName() + assertTrue { theName.matches("a.b.**") } + assertTrue { theName.matches("a.*.c.**") } + assertTrue { theName.matches("**.d") } + assertTrue { theName.matches("**.b.**") } + assertTrue { theName.matches("a.*.*.d") } + assertFails { theName.matches("a.**.d") } + assertFalse { theName.matches("a.b.c.d.**") } + } + + @Test + fun matchPattern() { + val theName = "a[dd+2].b[13].c.d[\"d\"]".toName() + assertTrue { theName.matches("a[.*].b[.*].c[.*].d[.*]") } + assertTrue { theName.matches("a[.*].b[.*].c.d[.*]") } + assertFalse { theName.matches("a[.*].b[.*].*.d") } + assertTrue { theName.matches("""\\w[dd\\+2].b[.*].c[.*].d[.*]""") } + assertFalse { theName.matches("""\\s[dd\\+2].b[.*].c[.*].d[.*]""") } + } +} \ No newline at end of file diff --git a/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt b/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt index 9d76b606..b75d487d 100644 --- a/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt +++ b/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt @@ -22,9 +22,9 @@ public fun Value.toDynamic(): dynamic { public fun Meta.toDynamic(): dynamic { if (this is DynamicMeta) return this.obj - fun MetaItem<*>.toDynamic(): dynamic = when (this) { - is MetaItem.ValueItem -> this.value.toDynamic() - is MetaItem.NodeItem -> this.node.toDynamic() + fun MetaItem.toDynamic(): dynamic = when (this) { + is MetaItemValue -> this.value.toDynamic() + is MetaItemNode -> this.node.toDynamic() } val res = js("{}") @@ -48,27 +48,27 @@ public class DynamicMeta(internal val obj: dynamic) : MetaBase() { (jsTypeOf(obj) != "object") @Suppress("UNCHECKED_CAST", "USELESS_CAST") - private fun asItem(obj: dynamic): MetaItem? { + private fun asItem(obj: dynamic): TypedMetaItem? { return when { - obj == null -> MetaItem.ValueItem(Null) - isArray(obj) && (obj as Array).all { isPrimitive(it) } -> MetaItem.ValueItem(Value.of(obj as Array)) + obj == null -> MetaItemValue(Null) + isArray(obj) && (obj as Array).all { isPrimitive(it) } -> MetaItemValue(Value.of(obj as Array)) else -> when (jsTypeOf(obj)) { - "boolean" -> MetaItem.ValueItem(Value.of(obj as Boolean)) - "number" -> MetaItem.ValueItem(Value.of(obj as Number)) - "string" -> MetaItem.ValueItem(Value.of(obj as String)) - "object" -> MetaItem.NodeItem(DynamicMeta(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 } } } - override val items: Map> - get() = keys().flatMap>> { key -> + override val items: Map> + get() = keys().flatMap>> { key -> val value = obj[key] ?: return@flatMap emptyList() if (isArray(value)) { val array = value as Array return@flatMap if (array.all { isPrimitive(it) }) { - listOf(NameToken(key) to MetaItem.ValueItem(Value.of(array))) + listOf(NameToken(key) to MetaItemValue(Value.of(array))) } else { array.mapIndexedNotNull { index, it -> val item = asItem(it) ?: return@mapIndexedNotNull null diff --git a/dataforge-meta/src/jsTest/kotlin/hep/dataforge/meta/DynamicMetaTest.kt b/dataforge-meta/src/jsTest/kotlin/hep/dataforge/meta/DynamicMetaTest.kt index b546ae6d..1fabf061 100644 --- a/dataforge-meta/src/jsTest/kotlin/hep/dataforge/meta/DynamicMetaTest.kt +++ b/dataforge-meta/src/jsTest/kotlin/hep/dataforge/meta/DynamicMetaTest.kt @@ -19,6 +19,7 @@ class DynamicMetaTest { d.ob.booleanNode = true val meta = DynamicMeta(d) + println(meta) assertEquals(true, meta["ob.booleanNode"].boolean) assertEquals(2, meta["array"].value?.list?.get(1)?.int) assertEquals(4, meta.items.size) @@ -37,17 +38,11 @@ class DynamicMetaTest { } val dynamic = meta.toDynamic() - assertEquals(2,dynamic.array[1]) - assertEquals(22, dynamic.a) - val keys = js("Object.keys(dynamic)") as Array - assertTrue { keys.contains("ob") } - assertEquals(18, dynamic.ob.childNode) - assertEquals(meta, DynamicMeta(dynamic)) } diff --git a/dataforge-scripting/build.gradle.kts b/dataforge-scripting/build.gradle.kts index cb16b66e..e0c47057 100644 --- a/dataforge-scripting/build.gradle.kts +++ b/dataforge-scripting/build.gradle.kts @@ -22,4 +22,8 @@ kotlin { } } } +} + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE } \ No newline at end of file diff --git a/dataforge-scripting/src/jvmMain/kotlin/hep/dataforge/scripting/Builders.kt b/dataforge-scripting/src/jvmMain/kotlin/hep/dataforge/scripting/Builders.kt index e8b1d378..1842ba45 100644 --- a/dataforge-scripting/src/jvmMain/kotlin/hep/dataforge/scripting/Builders.kt +++ b/dataforge-scripting/src/jvmMain/kotlin/hep/dataforge/scripting/Builders.kt @@ -2,7 +2,7 @@ package hep.dataforge.scripting import hep.dataforge.context.Context import hep.dataforge.context.Global -import hep.dataforge.workspace.SimpleWorkspaceBuilder +import hep.dataforge.context.logger import hep.dataforge.workspace.Workspace import hep.dataforge.workspace.WorkspaceBuilder import java.io.File @@ -16,7 +16,7 @@ import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost public object Builders { private fun buildWorkspace(source: SourceCode, context: Context = Global): Workspace { - val builder = SimpleWorkspaceBuilder(context) + val builder = WorkspaceBuilder(context) val workspaceScriptConfiguration = ScriptCompilationConfiguration { // baseClass(Any::class) @@ -29,6 +29,7 @@ public object Builders { dependenciesFromCurrentContext(wholeClasspath = true) } hostConfiguration(defaultJvmScriptingHostConfiguration) + compilerOptions("-jvm-target", Runtime.version().feature().toString()) } val evaluationConfiguration = ScriptEvaluationConfiguration { diff --git a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt index 6dd61105..cd0985a7 100644 --- a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt +++ b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt @@ -3,8 +3,8 @@ package hep.dataforge.scripting import hep.dataforge.context.Global import hep.dataforge.meta.get import hep.dataforge.meta.int -import hep.dataforge.workspace.SimpleWorkspaceBuilder -import hep.dataforge.workspace.context +import hep.dataforge.workspace.WorkspaceBuilder + import hep.dataforge.workspace.target import kotlin.test.Test import kotlin.test.assertEquals @@ -13,7 +13,7 @@ import kotlin.test.assertEquals class BuildersKtTest { @Test fun checkBuilder(){ - val workspace = SimpleWorkspaceBuilder(Global).apply { + val workspace = WorkspaceBuilder(Global).apply { println("I am working") context("test") diff --git a/dataforge-tables/api/dataforge-tables.api b/dataforge-tables/api/dataforge-tables.api deleted file mode 100644 index e037773a..00000000 --- a/dataforge-tables/api/dataforge-tables.api +++ /dev/null @@ -1,302 +0,0 @@ -public final class hep/dataforge/tables/CachedTransformationColumn : hep/dataforge/tables/Column { - public fun (Lhep/dataforge/tables/Table;Lkotlin/reflect/KClass;Ljava/lang/String;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;)V - public fun get (I)Ljava/lang/Object; - public final fun getMapper ()Lkotlin/jvm/functions/Function1; - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public fun getSize ()I - public final fun getTable ()Lhep/dataforge/tables/Table; - public fun getType ()Lkotlin/reflect/KClass; -} - -public final class hep/dataforge/tables/CastColumn : hep/dataforge/tables/Column { - public fun (Lhep/dataforge/tables/Column;Lkotlin/reflect/KClass;)V - public fun get (I)Ljava/lang/Object; - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public final fun getOrigin ()Lhep/dataforge/tables/Column; - public fun getSize ()I - public fun getType ()Lkotlin/reflect/KClass; -} - -public final class hep/dataforge/tables/CastColumnKt { - public static final fun cast (Lhep/dataforge/tables/Column;Lkotlin/reflect/KClass;)Lhep/dataforge/tables/Column; - public static final fun get (Ljava/util/Collection;Lhep/dataforge/tables/ColumnHeader;)Lhep/dataforge/tables/Column; -} - -public abstract interface class hep/dataforge/tables/Column : hep/dataforge/tables/ColumnHeader { - public abstract fun get (I)Ljava/lang/Object; - public abstract fun getSize ()I -} - -public final class hep/dataforge/tables/ColumnDef : hep/dataforge/tables/ColumnHeader { - public fun (Ljava/lang/String;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lkotlin/reflect/KClass; - public final fun component3 ()Lhep/dataforge/meta/Meta; - public final fun copy (Ljava/lang/String;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;)Lhep/dataforge/tables/ColumnDef; - public static synthetic fun copy$default (Lhep/dataforge/tables/ColumnDef;Ljava/lang/String;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)Lhep/dataforge/tables/ColumnDef; - public fun equals (Ljava/lang/Object;)Z - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public fun getType ()Lkotlin/reflect/KClass; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class hep/dataforge/tables/ColumnHeader { - public abstract fun getMeta ()Lhep/dataforge/meta/Meta; - public abstract fun getName ()Ljava/lang/String; - public abstract fun getType ()Lkotlin/reflect/KClass; -} - -public final class hep/dataforge/tables/ColumnHeaderKt { - public static final fun getTextWidth (Lhep/dataforge/tables/ColumnHeader;)I - public static final fun getValueType (Lhep/dataforge/tables/ColumnHeader;)Lhep/dataforge/values/ValueType; -} - -public final class hep/dataforge/tables/ColumnProperty : kotlin/properties/ReadOnlyProperty { - public fun (Lhep/dataforge/tables/Table;Lkotlin/reflect/KClass;)V - public final fun getTable ()Lhep/dataforge/tables/Table; - public final fun getType ()Lkotlin/reflect/KClass; - public fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Lhep/dataforge/tables/Column; - public synthetic fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; -} - -public class hep/dataforge/tables/ColumnScheme : hep/dataforge/meta/Scheme { - public static final field Companion Lhep/dataforge/tables/ColumnScheme$Companion; - public fun ()V - public final fun getTitle ()Ljava/lang/String; - public final fun setTitle (Ljava/lang/String;)V -} - -public final class hep/dataforge/tables/ColumnScheme$Companion : hep/dataforge/meta/SchemeSpec { -} - -public final class hep/dataforge/tables/ColumnTable : hep/dataforge/tables/Table { - public fun (Ljava/util/Collection;)V - public fun getColumns ()Ljava/util/Collection; - public fun getHeader ()Ljava/util/List; - public fun getRows ()Ljava/util/List; - public fun getValue (ILjava/lang/String;)Ljava/lang/Object; - public fun rowFlow ()Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/IntColumn : hep/dataforge/tables/Column { - public static final field Companion Lhep/dataforge/tables/IntColumn$Companion; - public fun (Ljava/lang/String;[ILhep/dataforge/meta/Meta;)V - public synthetic fun (Ljava/lang/String;[ILhep/dataforge/meta/Meta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun equals (Ljava/lang/Object;)Z - public fun get (I)Ljava/lang/Integer; - public synthetic fun get (I)Ljava/lang/Object; - public final fun getData ()[I - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public fun getSize ()I - public fun getType ()Lkotlin/reflect/KClass; - public fun hashCode ()I -} - -public final class hep/dataforge/tables/IntColumn$Companion { -} - -public final class hep/dataforge/tables/ListColumn : hep/dataforge/tables/Column { - public static final field Companion Lhep/dataforge/tables/ListColumn$Companion; - public fun (Ljava/lang/String;Ljava/util/List;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;)V - public fun get (I)Ljava/lang/Object; - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public fun getSize ()I - public fun getType ()Lkotlin/reflect/KClass; -} - -public final class hep/dataforge/tables/ListColumn$Companion { -} - -public final class hep/dataforge/tables/MapRow : hep/dataforge/tables/Row { - public static final synthetic fun box-impl (Ljava/util/Map;)Lhep/dataforge/tables/MapRow; - public static fun constructor-impl (Ljava/util/Map;)Ljava/util/Map; - public fun equals (Ljava/lang/Object;)Z - public static fun equals-impl (Ljava/util/Map;Ljava/lang/Object;)Z - public static final fun equals-impl0 (Ljava/util/Map;Ljava/util/Map;)Z - public fun getValue (Ljava/lang/String;)Ljava/lang/Object; - public static fun getValue-impl (Ljava/util/Map;Ljava/lang/String;)Ljava/lang/Object; - public fun hashCode ()I - public static fun hashCode-impl (Ljava/util/Map;)I - public fun toString ()Ljava/lang/String; - public static fun toString-impl (Ljava/util/Map;)Ljava/lang/String; - public final synthetic fun unbox-impl ()Ljava/util/Map; -} - -public final class hep/dataforge/tables/MutableColumnTable : hep/dataforge/tables/Table { - public fun (I)V - public final fun add (Lhep/dataforge/tables/Column;)V - public synthetic fun getColumns ()Ljava/util/Collection; - public fun getColumns ()Ljava/util/List; - public fun getHeader ()Ljava/util/List; - public fun getRows ()Ljava/util/List; - public final fun getSize ()I - public fun getValue (ILjava/lang/String;)Ljava/lang/Object; - public final fun insert (ILhep/dataforge/tables/Column;)V - public fun rowFlow ()Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/MutableTable : hep/dataforge/tables/RowTable { - public fun (Ljava/util/List;Ljava/util/List;)V - public final fun column (Ljava/lang/String;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;)Lhep/dataforge/tables/ColumnHeader; - public fun getHeader ()Ljava/util/List; - public fun getRows ()Ljava/util/List; - public final fun row (Ljava/util/Map;)Lhep/dataforge/tables/Row; - public final fun row ([Lkotlin/Pair;)Lhep/dataforge/tables/Row; -} - -public final class hep/dataforge/tables/MutableTableKt { - public static final fun edit (Lhep/dataforge/tables/Table;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/tables/Table; - public static final fun row (Lhep/dataforge/tables/MutableTable;[Lkotlin/Pair;)Lhep/dataforge/tables/Row; -} - -public final class hep/dataforge/tables/RealColumn : hep/dataforge/tables/Column { - public static final field Companion Lhep/dataforge/tables/RealColumn$Companion; - public fun (Ljava/lang/String;[DLhep/dataforge/meta/Meta;)V - public synthetic fun (Ljava/lang/String;[DLhep/dataforge/meta/Meta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun equals (Ljava/lang/Object;)Z - public fun get (I)Ljava/lang/Double; - public synthetic fun get (I)Ljava/lang/Object; - public final fun getData ()[D - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public fun getSize ()I - public fun getType ()Lkotlin/reflect/KClass; - public fun hashCode ()I -} - -public final class hep/dataforge/tables/RealColumn$Companion { -} - -public abstract interface class hep/dataforge/tables/Row { - public abstract fun getValue (Ljava/lang/String;)Ljava/lang/Object; -} - -public class hep/dataforge/tables/RowTable : hep/dataforge/tables/Table { - public fun (Ljava/util/List;Ljava/util/List;)V - public synthetic fun getColumns ()Ljava/util/Collection; - public fun getColumns ()Ljava/util/List; - public fun getHeader ()Ljava/util/List; - public fun getRows ()Ljava/util/List; - public fun getValue (ILjava/lang/String;)Ljava/lang/Object; - public fun rowFlow ()Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/RowTableKt { - public static final fun collect (Lhep/dataforge/tables/Rows;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public abstract interface class hep/dataforge/tables/Rows { - public abstract fun getHeader ()Ljava/util/List; - public abstract fun rowFlow ()Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/SimpleColumnHeader : hep/dataforge/tables/ColumnHeader { - public fun (Ljava/lang/String;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Lkotlin/reflect/KClass; - public final fun component3 ()Lhep/dataforge/meta/Meta; - public final fun copy (Ljava/lang/String;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;)Lhep/dataforge/tables/SimpleColumnHeader; - public static synthetic fun copy$default (Lhep/dataforge/tables/SimpleColumnHeader;Ljava/lang/String;Lkotlin/reflect/KClass;Lhep/dataforge/meta/Meta;ILjava/lang/Object;)Lhep/dataforge/tables/SimpleColumnHeader; - public fun equals (Ljava/lang/Object;)Z - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public fun getType ()Lkotlin/reflect/KClass; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public abstract interface class hep/dataforge/tables/Table : hep/dataforge/tables/Rows { - public static final field Companion Lhep/dataforge/tables/Table$Companion; - public abstract fun getColumns ()Ljava/util/Collection; - public abstract fun getHeader ()Ljava/util/List; - public abstract fun getRows ()Ljava/util/List; - public abstract fun getValue (ILjava/lang/String;)Ljava/lang/Object; - public abstract fun rowFlow ()Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/Table$Companion { - public final fun invoke (Lkotlin/jvm/functions/Function1;)Lhep/dataforge/tables/Table; -} - -public final class hep/dataforge/tables/Table$DefaultImpls { - public static fun getHeader (Lhep/dataforge/tables/Table;)Ljava/util/List; - public static fun rowFlow (Lhep/dataforge/tables/Table;)Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/TableKt { - public static final fun get (Lhep/dataforge/tables/Row;Lhep/dataforge/tables/ColumnHeader;)Ljava/lang/Object; - public static final fun get (Lhep/dataforge/tables/Table;ILhep/dataforge/tables/ColumnHeader;)Ljava/lang/Object; - public static final fun get (Ljava/util/Collection;Ljava/lang/String;)Lhep/dataforge/tables/Column; - public static final fun getIndices (Lhep/dataforge/tables/Column;)Lkotlin/ranges/IntRange; - public static final fun getValue (Lhep/dataforge/tables/Row;Ljava/lang/String;Lkotlin/reflect/KClass;)Ljava/lang/Object; - public static final fun getValue (Lhep/dataforge/tables/Table;ILjava/lang/String;Lkotlin/reflect/KClass;)Ljava/lang/Object; - public static final fun iterator (Lhep/dataforge/tables/Column;)Ljava/util/Iterator; -} - -public final class hep/dataforge/tables/TransformationColumn : hep/dataforge/tables/Column { - public fun (Lhep/dataforge/tables/Table;Lkotlin/reflect/KClass;Ljava/lang/String;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;)V - public fun get (I)Ljava/lang/Object; - public final fun getMapper ()Lkotlin/jvm/functions/Function1; - public fun getMeta ()Lhep/dataforge/meta/Meta; - public fun getName ()Ljava/lang/String; - public fun getSize ()I - public final fun getTable ()Lhep/dataforge/tables/Table; - public fun getType ()Lkotlin/reflect/KClass; -} - -public final class hep/dataforge/tables/TransformationColumnKt { - public static final fun mapRowsToDouble (Lhep/dataforge/tables/Table;Ljava/lang/String;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/tables/RealColumn; - public static synthetic fun mapRowsToDouble$default (Lhep/dataforge/tables/Table;Ljava/lang/String;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/tables/RealColumn; - public static final fun mapRowsToInt (Lhep/dataforge/tables/Table;Ljava/lang/String;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/tables/IntColumn; - public static synthetic fun mapRowsToInt$default (Lhep/dataforge/tables/Table;Ljava/lang/String;Lhep/dataforge/meta/Meta;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/tables/IntColumn; -} - -public final class hep/dataforge/tables/ValueColumnScheme : hep/dataforge/tables/ColumnScheme { - public fun ()V - public final fun getValueType ()Lhep/dataforge/values/ValueType; - public final fun setValueType (Lhep/dataforge/values/ValueType;)V -} - -public final class hep/dataforge/tables/io/TextRows : hep/dataforge/tables/Rows { - public static final field Companion Lhep/dataforge/tables/io/TextRows$Companion; - public fun (Ljava/util/List;Lkotlinx/io/Binary;)V - public fun getHeader ()Ljava/util/List; - public final fun indexFlow ()Lkotlinx/coroutines/flow/Flow; - public fun rowFlow ()Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/io/TextRows$Companion { -} - -public final class hep/dataforge/tables/io/TextRowsKt { - public static final fun buildRowIndex (Lhep/dataforge/tables/io/TextRows;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun writeRows (Lkotlinx/io/Output;Lhep/dataforge/tables/Rows;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class hep/dataforge/tables/io/TextTable : hep/dataforge/tables/Table { - public static final field Companion Lhep/dataforge/tables/io/TextTable$Companion; - public fun (Ljava/util/List;Lkotlinx/io/Binary;Ljava/util/List;)V - public fun getColumns ()Ljava/util/Collection; - public fun getHeader ()Ljava/util/List; - public final fun getIndex ()Ljava/util/List; - public fun getRows ()Ljava/util/List; - public fun getValue (ILjava/lang/String;)Lhep/dataforge/values/Value; - public synthetic fun getValue (ILjava/lang/String;)Ljava/lang/Object; - public fun rowFlow ()Lkotlinx/coroutines/flow/Flow; -} - -public final class hep/dataforge/tables/io/TextTable$Companion { - public final fun invoke (Ljava/util/List;Lkotlinx/io/Binary;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - -public final class hep/dataforge/tables/io/TextTableEnvelopeKt { - public static final fun readEnvelope (Lhep/dataforge/tables/io/TextRows$Companion;Lhep/dataforge/io/Envelope;)Lhep/dataforge/tables/io/TextRows; - public static final fun wrap (Lhep/dataforge/tables/Table;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - diff --git a/dataforge-tables/build.gradle.kts b/dataforge-tables/build.gradle.kts index e88f70d1..d0f008c3 100644 --- a/dataforge-tables/build.gradle.kts +++ b/dataforge-tables/build.gradle.kts @@ -12,4 +12,8 @@ kotlin { } } } +} + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt index a226de26..4aeb3124 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt @@ -32,5 +32,6 @@ public val ColumnHeader.textWidth: Int ValueType.STRING -> 16 ValueType.BOOLEAN -> 5 ValueType.NULL -> 5 + ValueType.LIST -> 32 null -> 16 } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt index 31f5baaa..e9e6e31e 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt @@ -111,8 +111,8 @@ public class TextTable( private fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { require(width > 5) { "Width could not be less than 5" } val str: String = when (value.type) { - ValueType.NUMBER -> value.number.toString() //TODO apply decimal format - ValueType.STRING -> value.string.take(width) + ValueType.NUMBER -> value.numberOrNull.toString() //TODO apply decimal format + ValueType.STRING, ValueType.LIST -> value.string.take(width) ValueType.BOOLEAN -> if (value.boolean) { "true" } else { diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt index 4deb84e6..b6be63e0 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt @@ -2,6 +2,7 @@ package hep.dataforge.tables.io import hep.dataforge.io.Envelope import hep.dataforge.meta.* +import hep.dataforge.misc.DFExperimental import hep.dataforge.tables.SimpleColumnHeader import hep.dataforge.tables.Table import hep.dataforge.values.Value diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt index a0bcb75e..0ab6c515 100644 --- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt +++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt @@ -4,11 +4,11 @@ import hep.dataforge.meta.Meta import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KClass import kotlin.reflect.KProperty -import kotlin.reflect.full.cast import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.safeCast @Suppress("UNCHECKED_CAST") -fun Column<*>.cast(type: KClass): Column { +public fun Column<*>.cast(type: KClass): Column { return if (type.isSubclassOf(this.type)) { this as Column } else { @@ -16,21 +16,21 @@ fun Column<*>.cast(type: KClass): Column { } } -class CastColumn(val origin: Column<*>, override val type: KClass) : Column { +public class CastColumn(private val origin: Column<*>, override val type: KClass) : Column { override val name: String get() = origin.name override val meta: Meta get() = origin.meta override val size: Int get() = origin.size - override fun get(index: Int): T? = type.cast(origin[index]) + override fun get(index: Int): T? = type.safeCast(origin[index]) } -class ColumnProperty(val table: Table, val type: KClass) : ReadOnlyProperty> { +public class ColumnProperty(public val table: Table, public val type: KClass) : ReadOnlyProperty> { override fun getValue(thisRef: Any?, property: KProperty<*>): Column { val name = property.name return (table.columns[name] ?: error("Column with name $name not found in the table")).cast(type) } } -operator fun Collection>.get(header: ColumnHeader): Column? = +public operator fun Collection>.get(header: ColumnHeader): Column? = find { it.name == header.name }?.cast(header.type) diff --git a/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt index 02d97caf..90fa3a01 100644 --- a/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt +++ b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt @@ -1,11 +1,12 @@ package hep.dataforge.tables.io -import hep.dataforge.meta.DFExperimental +import hep.dataforge.misc.DFExperimental import hep.dataforge.tables.Table import hep.dataforge.tables.get import hep.dataforge.tables.row import hep.dataforge.values.Value import hep.dataforge.values.int +import hep.dataforge.values.string import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import kotlinx.io.ExperimentalIoApi diff --git a/dataforge-workspace/api/dataforge-workspace.api b/dataforge-workspace/api/dataforge-workspace.api index c410f094..d510660b 100644 --- a/dataforge-workspace/api/dataforge-workspace.api +++ b/dataforge-workspace/api/dataforge-workspace.api @@ -65,7 +65,6 @@ public final class hep/dataforge/workspace/SimpleWorkspace : hep/dataforge/works public fun getData ()Lhep/dataforge/data/DataNode; public fun getDefaultChainTarget ()Ljava/lang/String; public fun getDefaultTarget ()Ljava/lang/String; - public fun getLogger ()Lmu/KLogger; public fun getTargets ()Ljava/util/Map; public fun getTasks ()Ljava/util/Map; public fun run (Lhep/dataforge/workspace/Task;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode; @@ -211,15 +210,12 @@ public abstract interface class hep/dataforge/workspace/Workspace : hep/dataforg public final class hep/dataforge/workspace/Workspace$Companion { public static final field TYPE Ljava/lang/String; - public final fun invoke (Lhep/dataforge/context/Context;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/workspace/SimpleWorkspace; - public static synthetic fun invoke$default (Lhep/dataforge/workspace/Workspace$Companion;Lhep/dataforge/context/Context;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/workspace/SimpleWorkspace; } public final class hep/dataforge/workspace/Workspace$DefaultImpls { public static fun content (Lhep/dataforge/workspace/Workspace;Ljava/lang/String;)Ljava/util/Map; public static fun getDefaultChainTarget (Lhep/dataforge/workspace/Workspace;)Ljava/lang/String; public static fun getDefaultTarget (Lhep/dataforge/workspace/Workspace;)Ljava/lang/String; - public static fun getLogger (Lhep/dataforge/workspace/Workspace;)Lmu/KLogger; public static fun run (Lhep/dataforge/workspace/Workspace;Lhep/dataforge/workspace/Task;Lhep/dataforge/meta/Meta;)Lhep/dataforge/data/DataNode; } @@ -237,6 +233,8 @@ public abstract interface class hep/dataforge/workspace/WorkspaceBuilder { } public final class hep/dataforge/workspace/WorkspaceBuilderKt { + public static final fun Workspace (Lhep/dataforge/context/Context;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/workspace/Workspace; + public static synthetic fun Workspace$default (Lhep/dataforge/context/Context;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lhep/dataforge/workspace/Workspace; public static final fun context (Lhep/dataforge/workspace/WorkspaceBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public static synthetic fun context$default (Lhep/dataforge/workspace/WorkspaceBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public static final fun rawData (Lhep/dataforge/workspace/WorkspaceBuilder;Lhep/dataforge/names/Name;Lkotlin/jvm/functions/Function1;)Lhep/dataforge/data/DataNode; diff --git a/dataforge-workspace/build.gradle.kts b/dataforge-workspace/build.gradle.kts index 2bb03cec..ae83b062 100644 --- a/dataforge-workspace/build.gradle.kts +++ b/dataforge-workspace/build.gradle.kts @@ -13,4 +13,8 @@ kotlin { } } } +} + +readme{ + maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/ContextGoalLogger.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/ContextGoalLogger.kt new file mode 100644 index 00000000..aae36ac6 --- /dev/null +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/ContextGoalLogger.kt @@ -0,0 +1,17 @@ +package hep.dataforge.workspace + +import hep.dataforge.context.Context +import hep.dataforge.context.logger +import hep.dataforge.data.GoalLogger +import kotlinx.coroutines.launch + +public class ContextGoalLogger(public val context: Context) : GoalLogger { + override fun emit(vararg tags: String, message: suspend () -> String) { + context.launch { + val text = message() + context.logger.info { text } + } + } +} + +public val Workspace.goalLogger: GoalLogger get() = ContextGoalLogger(context) \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt deleted file mode 100644 index e58a988b..00000000 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt +++ /dev/null @@ -1,100 +0,0 @@ -package hep.dataforge.workspace - -import hep.dataforge.data.DataFilter -import hep.dataforge.data.DataNode -import hep.dataforge.data.filter -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder -import hep.dataforge.meta.MetaRepr -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.names.isEmpty -import hep.dataforge.names.plus - -/** - * A dependency of the task which allows to lazily create a data tree for single dependency - */ -public sealed class Dependency : MetaRepr { - public abstract fun apply(workspace: Workspace): DataNode -} - -public class DataDependency(private val filter: DataFilter, private val placement: Name = Name.EMPTY) : Dependency() { - override fun apply(workspace: Workspace): DataNode { - val result = workspace.data.filter(filter) - return if (placement.isEmpty()) { - result - } else { - DataNode.invoke(Any::class) { this[placement] = result } - } - } - - override fun toMeta(): Meta = Meta { - "data" put filter.toMeta() - "to" put placement.toString() - } -} - -public class AllDataDependency(private val placement: Name = Name.EMPTY) : Dependency() { - override fun apply(workspace: Workspace): DataNode = if (placement.isEmpty()) { - workspace.data - } else { - DataNode.invoke(Any::class) { this[placement] = workspace.data } - } - - override fun toMeta(): MetaBuilder = Meta { - "data" put "@all" - "to" put placement.toString() - } -} - -public abstract class TaskDependency( - public val meta: Meta, - public val placement: Name = Name.EMPTY -) : Dependency() { - public abstract fun resolveTask(workspace: Workspace): Task - - /** - * A name of the dependency for logging and serialization - */ - public abstract val name: Name - - override fun apply(workspace: Workspace): DataNode { - val task = resolveTask(workspace) - if (task.isTerminal) TODO("Support terminal task") - val result = workspace.run(task, meta) - return if (placement.isEmpty()) { - result - } else { - DataNode(task.type) { this[placement] = result } - } - } - - override fun toMeta(): Meta = Meta { - "task" put name.toString() - "meta" put meta - "to" put placement.toString() - } -} - -public class DirectTaskDependency( - public val task: Task, - meta: Meta, - placement: Name -) : TaskDependency(meta, placement) { - override fun resolveTask(workspace: Workspace): Task = task - - override val name: Name get() = DIRECT_TASK_NAME + task.name - - public companion object { - public val DIRECT_TASK_NAME: Name = "@direct".asName() - } -} - -public class WorkspaceTaskDependency( - override val name: Name, - meta: Meta, - placement: Name -) : TaskDependency(meta, placement) { - override fun resolveTask(workspace: Workspace): Task<*> = - workspace.tasks[name] ?: error("Task with name $name is not found in the workspace") -} \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt deleted file mode 100644 index d3844096..00000000 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt +++ /dev/null @@ -1,54 +0,0 @@ -package hep.dataforge.workspace - -import hep.dataforge.data.DataNode -import hep.dataforge.meta.Meta -import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.get -import hep.dataforge.meta.node -import hep.dataforge.names.Name -import kotlin.reflect.KClass - -//data class TaskEnv(val workspace: Workspace, val model: TaskModel) - - -public class GenericTask( - override val name: Name, - override val type: KClass, - override val descriptor: NodeDescriptor, - private val modelTransform: TaskModelBuilder.(Meta) -> Unit, - private val dataTransform: Workspace.() -> TaskModel.(DataNode) -> DataNode -) : Task { - - override fun run(workspace: Workspace, model: TaskModel): DataNode { - //validate model - validate(model) - - // gather data - val input = model.buildInput(workspace)// gather(workspace, model) - - //execute - workspace.context.logger.info{"Starting task '$name' on ${model.target} with meta: \n${model.meta}"} - val output = dataTransform(workspace).invoke(model, input) - - //handle result - //output.handle(model.context.dispatcher) { this.handle(it) } - - return output - } - - /** - * Build new TaskModel and apply specific model transformation for this - * task. By default model uses the meta node with the same node as the name of the task. - * - * @param workspace - * @param taskConfig - * @return - */ - override fun build(workspace: Workspace, taskConfig: Meta): TaskModel { - val taskMeta = taskConfig[name]?.node ?: taskConfig - val builder = TaskModelBuilder(name, taskMeta) - builder.modelTransform(taskMeta) - return builder.build() - } - //TODO add validation -} \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/SimpleWorkspace.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/SimpleWorkspace.kt index adec8bb9..ab8d0465 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/SimpleWorkspace.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/SimpleWorkspace.kt @@ -2,8 +2,7 @@ package hep.dataforge.workspace import hep.dataforge.context.Context import hep.dataforge.context.gather -import hep.dataforge.context.toMap -import hep.dataforge.data.DataNode +import hep.dataforge.data.DataSet import hep.dataforge.meta.Meta import hep.dataforge.names.Name @@ -13,16 +12,14 @@ import hep.dataforge.names.Name */ public class SimpleWorkspace( override val context: Context, - override val data: DataNode, + data: DataSet<*>, override val targets: Map, - tasks: Collection> + private val externalTasks: Map>, ) : Workspace { - override val tasks: Map> by lazy { - context.gather>(Task.TYPE) + tasks.toMap() - } + override val data: TaskResult<*> = internalize(data, Name.EMPTY, Meta.EMPTY) - public companion object { + override val tasks: Map> + get() = context.gather>(Task.TYPE) + externalTasks - } } \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt index 6792adfa..6e6c590e 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt @@ -1,54 +1,74 @@ package hep.dataforge.workspace -import hep.dataforge.context.Named -import hep.dataforge.data.DataNode +import hep.dataforge.data.DataSetBuilder +import hep.dataforge.data.DataTree +import hep.dataforge.data.GoalExecutionRestriction import hep.dataforge.meta.Meta import hep.dataforge.meta.descriptors.Described -import hep.dataforge.provider.Type +import hep.dataforge.meta.descriptors.ItemDescriptor +import hep.dataforge.misc.DFInternal +import hep.dataforge.misc.Type +import hep.dataforge.names.Name import hep.dataforge.workspace.Task.Companion.TYPE -import kotlin.reflect.KClass +import kotlinx.coroutines.withContext +import kotlin.reflect.KType +import kotlin.reflect.typeOf @Type(TYPE) -public interface Task : Named, Described { - /** - * Terminal task is the one that could not build model lazily - */ - public val isTerminal: Boolean get() = false +public interface Task : Described { /** - * The explicit type of the node returned by the task - */ - public val type: KClass - - /** - * Build a model for this task + * Compute a [TaskResult] using given meta. In general, the result is lazy and represents both computation model + * and a handler for actual result * - * @param workspace - * @param taskConfig - * @return + * @param workspace a workspace to run task in + * @param taskName the name of the task in this workspace + * @param taskMeta configuration for current stage computation */ - public fun build(workspace: Workspace, taskConfig: Meta): TaskModel - - /** - * Check if the model is valid and is acceptable by the task. Throw exception if not. - * - * @param model - */ - public fun validate(model: TaskModel) { - if(this.name != model.name) error("The task $name could not be run with model from task ${model.name}") - } - - /** - * Run given task model. Type check expected to be performed before actual - * calculation. - * - * @param workspace - a workspace to run task model in - * @param model - a model to be executed - * @return - */ - public fun run(workspace: Workspace, model: TaskModel): DataNode + public suspend fun execute(workspace: Workspace, taskName: Name, taskMeta: Meta): TaskResult public companion object { - public const val TYPE: String = "task" + public const val TYPE: String = "workspace.stage" } -} \ No newline at end of file +} + +public class TaskResultBuilder( + public val workspace: Workspace, + public val taskName: Name, + public val taskMeta: Meta, + private val dataDrop: DataSetBuilder, +) : DataSetBuilder by dataDrop + +/** + * Create a [Task] that composes a result using [builder]. Only data from the workspace could be used. + * Data dependency cycles are not allowed. + */ +@Suppress("FunctionName") +@DFInternal +public fun Task( + resultType: KType, + descriptor: ItemDescriptor? = null, + builder: suspend TaskResultBuilder.() -> Unit, +): Task = object : Task { + + override val descriptor: ItemDescriptor? = descriptor + + override suspend fun execute( + workspace: Workspace, + taskName: Name, + taskMeta: Meta, + ): TaskResult = withContext(GoalExecutionRestriction() + workspace.goalLogger) { + //TODO use safe builder and check for external data on add and detects cycles + val dataset = DataTree(resultType) { + TaskResultBuilder(workspace,taskName, taskMeta, this).apply { builder() } + } + workspace.internalize(dataset, taskName, taskMeta) + } +} + +@OptIn(DFInternal::class) +@Suppress("FunctionName") +public inline fun Task( + descriptor: ItemDescriptor? = null, + noinline builder: suspend TaskResultBuilder.() -> Unit, +): Task = Task(typeOf(), descriptor, builder) \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt deleted file mode 100644 index 4cb48843..00000000 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt +++ /dev/null @@ -1,245 +0,0 @@ -package hep.dataforge.workspace - -import hep.dataforge.context.Context -import hep.dataforge.data.* -import hep.dataforge.meta.DFBuilder -import hep.dataforge.meta.Meta -import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.get -import hep.dataforge.meta.string -import hep.dataforge.names.Name -import hep.dataforge.names.isEmpty -import hep.dataforge.names.toName -import kotlin.jvm.JvmName -import kotlin.reflect.KClass - -@DFBuilder -public class TaskBuilder(public val name: Name, public val type: KClass) { - private var modelTransform: TaskModelBuilder.(Meta) -> Unit = { allData() } - - // private val additionalDependencies = HashSet() - private var descriptor: NodeDescriptor? = null - private val dataTransforms: MutableList = ArrayList() - - /** - * TODO will look better as extension class - */ - private inner class DataTransformation( - val from: String = "", - val to: String = "", - val transform: (Context, TaskModel, DataNode) -> DataNode - ) { - operator fun invoke(workspace: Workspace, model: TaskModel, node: DataNode): DataNode? { - val localData = if (from.isEmpty()) { - node - } else { - node[from].node ?: return null - } - return transform(workspace.context, model, localData) - } - } - -// override fun add(dependency: Dependency) { -// additionalDependencies.add(dependency) -// } - - public fun model(modelTransform: TaskModelBuilder.(Meta) -> Unit) { - this.modelTransform = modelTransform - } - - /** - * Add a transformation on untyped data - */ - @JvmName("rawTransform") - public fun transform( - from: String = "", - to: String = "", - block: TaskEnv.(DataNode<*>) -> DataNode - ) { - dataTransforms += DataTransformation(from, to) { context, model, data -> - val env = TaskEnv(Name.EMPTY, model.meta, context, data) - env.block(data) - } - } - - public fun transform( - inputType: KClass, - from: String = "", - to: String = "", - block: TaskEnv.(DataNode) -> DataNode - ) { - dataTransforms += DataTransformation(from, to) { context, model, data -> - data.ensureType(inputType) - val env = TaskEnv(Name.EMPTY, model.meta, context, data) - env.block(data.cast(inputType)) - } - } - - public inline fun transform( - from: String = "", - to: String = "", - noinline block: TaskEnv.(DataNode) -> DataNode - ) { - transform(T::class, from, to, block) - } - - /** - * Perform given action on data elements in `from` node in input and put the result to `to` node - */ - public inline fun action( - from: String = "", - to: String = "", - crossinline block: TaskEnv.() -> Action - ) { - transform(from, to) { data: DataNode -> - block().invoke(data, meta) - } - } - - public class TaskEnv(public val name: Name, public val meta: Meta, public val context: Context, public val data: DataNode) { - public operator fun DirectTaskDependency.invoke(): DataNode = if (placement.isEmpty()) { - data.cast(task.type) - } else { - data[placement].node?.cast(task.type) - ?: error("Could not find results of direct task dependency $this at \"$placement\"") - } - } - - /** - * A customized map action with ability to change meta and name - */ - public inline fun mapAction( - from: String = "", - to: String = "", - crossinline block: MapActionBuilder.(TaskEnv) -> Unit - ) { - action(from, to) { - val env = this - MapAction( - inputType = T::class, - outputType = type - ) { - block(env) - } - } - } - - /** - * A simple map action without changing meta or name - */ - public inline fun map( - from: String = "", - to: String = "", - crossinline block: suspend TaskEnv.(T) -> R - ) { - action(from, to) { - MapAction( - inputType = T::class, - outputType = type - ) { - //TODO automatically append task meta - result = { data -> - block(data) - } - } - } - } - - /** - * Join elements in gathered data by multiple groups - */ - public inline fun reduceByGroup( - from: String = "", - to: String = "", - crossinline block: ReduceGroupBuilder.(TaskEnv) -> Unit //TODO needs KEEP-176 - ) { - action(from, to) { - val env = this - ReduceAction( - inputType = T::class, - outputType = type - ) { block(env) } - } - } - - /** - * Join all elemlents in gathered data matching input type - */ - public inline fun reduce( - from: String = "", - to: String = "", - crossinline block: suspend TaskEnv.(Map) -> R - ) { - action(from, to) { - ReduceAction( - inputType = T::class, - outputType = type, - action = { - result( - actionMeta[TaskModel.MODEL_TARGET_KEY]?.string ?: "@anonymous" - ) { data -> - block(data) - } - } - ) - } - } - - /** - * Split each element in gathered data into fixed number of fragments - */ - public inline fun split( - from: String = "", - to: String = "", - crossinline block: SplitBuilder.(TaskEnv) -> Unit //TODO needs KEEP-176 - ) { - action(from, to) { - val env = this - SplitAction( - inputType = T::class, - outputType = type - ) { block(env) } - } - } - - /** - * Use DSL to create a descriptor for this task - */ - public fun description(transform: NodeDescriptor.() -> Unit) { - this.descriptor = NodeDescriptor().apply(transform) - } - - internal fun build(): GenericTask { - return GenericTask( - name, - type, - descriptor ?: NodeDescriptor(), - modelTransform - ) { - val workspace = this - return@GenericTask { data -> - val model = this - if (dataTransforms.isEmpty()) { - //return data node as is - logger.warn { "No transformation present, returning input data" } - data.ensureType(type) - data.cast(type) - } else { - val builder = DataTreeBuilder(type) - dataTransforms.forEach { transformation -> - val res = transformation(workspace, model, data) - if (res != null) { - if (transformation.to.isEmpty()) { - builder.update(res) - } else { - builder[transformation.to.toName()] = res - } - } - } - builder.build() - } - } - } - } -} - diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskData.kt new file mode 100644 index 00000000..f317b00a --- /dev/null +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskData.kt @@ -0,0 +1,47 @@ +package hep.dataforge.workspace + +import hep.dataforge.data.Data +import hep.dataforge.data.NamedData +import hep.dataforge.meta.Meta +import hep.dataforge.names.Name + +/** + * A [Workspace]-locked [NamedData], that serves as a computation model. + */ +public interface TaskData : NamedData { + /** + * The [Workspace] this data belongs to + */ + public val workspace: Workspace + + /** + * The name of the stage that produced this data. [Name.EMPTY] if the workspace intrinsic data is used. + */ + public val task: Name + + /** + * Stage configuration used to produce this data. + */ + public val taskMeta: Meta + + /** + * Dependencies that allow to compute transitive dependencies as well. + */ +// override val dependencies: Collection> +} + +private class TaskDataImpl( + override val workspace: Workspace, + override val data: Data, + override val name: Name, + override val task: Name, + override val taskMeta: Meta, +) : TaskData, Data by data { +// override val dependencies: Collection> = data.dependencies.map { +// it as? TaskData<*> ?: error("TaskData can't depend on external data") +// } +} + +internal fun Workspace.internalize(data: Data, name: Name, stage: Name, stageMeta: Meta): TaskData = + TaskDataImpl(this, data, name, stage, stageMeta) + diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt deleted file mode 100644 index 5053292e..00000000 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package hep.dataforge.workspace - -import hep.dataforge.data.DataFilter -import hep.dataforge.data.DataTree -import hep.dataforge.data.DataTreeBuilder -import hep.dataforge.meta.* -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.names.toName -import hep.dataforge.workspace.TaskModel.Companion.MODEL_TARGET_KEY - - -/** - * A model for task execution - * @param name the name of the task - * @param meta the meta for the task (not for the whole configuration) - * @param dependencies a list of direct dependencies for this task - */ -public data class TaskModel( - val name: Name, - val meta: Meta, - val dependencies: Collection -) : MetaRepr { - //TODO provide a way to get task descriptor - //TODO add pre-run check of task result type? - - override fun toMeta(): Meta = Meta { - "name" put name.toString() - "meta" put meta - "dependsOn" put { - val dataDependencies = dependencies.filterIsInstance() - val taskDependencies = dependencies.filterIsInstance>() - setIndexed("data".toName(), dataDependencies.map { it.toMeta() }) - setIndexed( - "task".toName(), - taskDependencies.map { it.toMeta() }) { _, index -> - taskDependencies[index].name.toString() - } - //TODO ensure all dependencies are listed - } - } - - public companion object { - public val MODEL_TARGET_KEY: Name = "@target".asName() - } -} - -/** - * Build input for the task - */ -public fun TaskModel.buildInput(workspace: Workspace): DataTree { - return DataTreeBuilder(Any::class).apply { - dependencies.forEach { dep -> - update(dep.apply(workspace)) - } - }.build() -} - -public interface TaskDependencyContainer { - public val defaultMeta: Meta - public fun add(dependency: Dependency) -} - -/** - * Add dependency for a task defined in a workspace and resolved by - */ -public fun TaskDependencyContainer.dependsOn( - name: Name, - placement: Name = Name.EMPTY, - meta: Meta = defaultMeta -): WorkspaceTaskDependency = - WorkspaceTaskDependency(name, meta, placement).also { add(it) } - -public fun TaskDependencyContainer.dependsOn( - name: String, - placement: Name = Name.EMPTY, - meta: Meta = defaultMeta -): WorkspaceTaskDependency = - dependsOn(name.toName(), placement, meta) - -public fun TaskDependencyContainer.dependsOn( - task: Task, - placement: Name = Name.EMPTY, - meta: Meta = defaultMeta -): DirectTaskDependency = - DirectTaskDependency(task, meta, placement).also { add(it) } - -public fun TaskDependencyContainer.dependsOn( - task: Task, - placement: String, - meta: Meta = defaultMeta -): DirectTaskDependency = - DirectTaskDependency(task, meta, placement.toName()).also { add(it) } - -public fun TaskDependencyContainer.dependsOn( - task: Task, - placement: Name = Name.EMPTY, - metaBuilder: MetaBuilder.() -> Unit -): DirectTaskDependency = - dependsOn(task, placement, Meta(metaBuilder)) - -/** - * Add custom data dependency - */ -public fun TaskDependencyContainer.data(action: DataFilter.() -> Unit): DataDependency = - DataDependency(DataFilter(action)).also { add(it) } - -/** - * User-friendly way to add data dependency - */ -public fun TaskDependencyContainer.data(pattern: String? = null, from: String? = null, to: String? = null): DataDependency = - data { - pattern?.let { this.pattern = it } - from?.let { this.from = it } - to?.let { this.to = it } - } - -/** - * Add all data as root node - */ -public fun TaskDependencyContainer.allData(to: Name = Name.EMPTY): AllDataDependency = AllDataDependency(to).also { add(it) } - -/** - * A builder for [TaskModel] - */ -public class TaskModelBuilder(public val name: Name, meta: Meta = Meta.EMPTY) : TaskDependencyContainer { - /** - * Meta for current task. By default uses the whole input meta - */ - public var meta: MetaBuilder = meta.builder() - private val dependencies: HashSet = HashSet() - - override val defaultMeta: Meta get() = meta - - override fun add(dependency: Dependency) { - dependencies.add(dependency) - } - - public var target: String by this.meta.string(key = MODEL_TARGET_KEY, default = "") - - - public fun build(): TaskModel = TaskModel(name, meta.seal(), dependencies) -} - - -public val TaskModel.target: String get() = meta[MODEL_TARGET_KEY]?.string ?: "" \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskResult.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskResult.kt new file mode 100644 index 00000000..c8adcd9a --- /dev/null +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskResult.kt @@ -0,0 +1,49 @@ +package hep.dataforge.workspace + +import hep.dataforge.data.DataSet +import hep.dataforge.meta.Meta +import hep.dataforge.names.Name +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * A result of a [Task] + */ +public interface TaskResult : DataSet { + /** + * The [Workspace] this [DataSet] belongs to + */ + public val workspace: Workspace + + /** + * The [Name] of the stage that produced this [DataSet] + */ + public val taskName: Name + + /** + * The configuration of the stage that produced this [DataSet] + */ + public val taskMeta: Meta + + override fun flow(): Flow> + override suspend fun getData(name: Name): TaskData? +} + +private class TaskResultImpl( + override val workspace: Workspace, + val dataSet: DataSet, + override val taskName: Name, + override val taskMeta: Meta, +) : TaskResult, DataSet by dataSet { + + override fun flow(): Flow> = dataSet.flow().map { + workspace.internalize(it, it.name, taskName, taskMeta) + } + + override suspend fun getData(name: Name): TaskData? = dataSet.getData(name)?.let { + workspace.internalize(it, name, taskName, taskMeta) + } +} + +internal fun Workspace.internalize(dataSet: DataSet, stage: Name, stageMeta: Meta): TaskResult = + TaskResultImpl(this, dataSet, stage, stageMeta) \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt index b9aaad18..af5ee317 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt @@ -1,17 +1,12 @@ package hep.dataforge.workspace -import hep.dataforge.context.Context import hep.dataforge.context.ContextAware -import hep.dataforge.context.Global -import hep.dataforge.data.Data -import hep.dataforge.data.DataNode -import hep.dataforge.data.dataSequence import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder +import hep.dataforge.misc.Type import hep.dataforge.names.Name import hep.dataforge.names.toName import hep.dataforge.provider.Provider -import hep.dataforge.provider.Type @Type(Workspace.TYPE) @@ -19,7 +14,7 @@ public interface Workspace : ContextAware, Provider { /** * The whole data node for current workspace */ - public val data: DataNode + public val data: TaskResult<*> /** * All targets associated with the workspace @@ -27,7 +22,7 @@ public interface Workspace : ContextAware, Provider { public val targets: Map /** - * All tasks associated with the workspace + * All stages associated with the workspace */ public val tasks: Map> @@ -35,45 +30,30 @@ public interface Workspace : ContextAware, Provider { return when (target) { "target", Meta.TYPE -> targets.mapKeys { it.key.toName() } Task.TYPE -> tasks - Data.TYPE -> data.dataSequence().toMap() - //DataNode.TYPE -> data.nodes.toMap() + //Data.TYPE -> data.flow().toMap() else -> emptyMap() } } - /** - * Invoke a task in the workspace utilizing caching if possible - */ - public fun run(task: Task, config: Meta): DataNode { - val model = task.build(this, config) - task.validate(model) - return task.run(this, model) + public suspend fun produce(taskName: Name, taskMeta: Meta): TaskResult<*> { + if (taskName == Name.EMPTY) return data + val task = tasks[taskName] ?: error("Task with name $taskName not found in the workspace") + return task.execute(this, taskName, taskMeta) } + public suspend fun produceData(taskName: Name, taskMeta: Meta, name: Name): TaskData<*>? = + produce(taskName, taskMeta).getData(name) + public companion object { public const val TYPE: String = "workspace" - public operator fun invoke( - parent: Context = Global, - block: SimpleWorkspaceBuilder.() -> Unit, - ): SimpleWorkspace = - SimpleWorkspaceBuilder(parent).apply(block).build() } } -public fun Workspace.run(task: Task<*>, target: String): DataNode { - val meta = targets[target] ?: error("A target with name $target not found in $this") - return run(task, meta) -} +public suspend fun Workspace.produce(task: String, target: String): TaskResult<*> = + produce(task.toName(), targets[target] ?: error("Target with key $target not found in $this")) +public suspend fun Workspace.produce(task: String, meta: Meta): TaskResult<*> = + produce(task.toName(), meta) -public fun Workspace.run(task: String, target: String): DataNode = - tasks[task.toName()]?.let { run(it, target) } ?: error("Task with name $task not found") - -public fun Workspace.run(task: String, meta: Meta): DataNode = - tasks[task.toName()]?.let { run(it, meta) } ?: error("Task with name $task not found") - -public fun Workspace.run(task: String, block: MetaBuilder.() -> Unit = {}): DataNode = - run(task, Meta(block)) - -public fun Workspace.run(task: Task, metaBuilder: MetaBuilder.() -> Unit = {}): DataNode = - run(task, Meta(metaBuilder)) \ No newline at end of file +public suspend fun Workspace.produce(task: String, block: MetaBuilder.() -> Unit = {}): TaskResult<*> = + produce(task, Meta(block)) diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt index b7f74079..cd3e6d92 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt @@ -2,97 +2,95 @@ package hep.dataforge.workspace import hep.dataforge.context.Context import hep.dataforge.context.ContextBuilder -import hep.dataforge.data.DataNode -import hep.dataforge.data.DataTreeBuilder -import hep.dataforge.meta.* +import hep.dataforge.context.Global +import hep.dataforge.data.ActiveDataTree +import hep.dataforge.data.DataSet +import hep.dataforge.data.DataSetBuilder +import hep.dataforge.data.DataTree +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.misc.DFBuilder +import hep.dataforge.misc.DFExperimental import hep.dataforge.names.Name -import hep.dataforge.names.isEmpty import hep.dataforge.names.toName -import kotlin.jvm.JvmName -import kotlin.reflect.KClass +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadOnlyProperty + +public data class TaskReference(public val taskName: Name, public val task: Task) + +public interface TaskContainer { + public fun registerTask(taskName: Name, task: Task<*>) +} + + +public inline fun TaskContainer.registerTask( + name: String, + noinline descriptorBuilder: NodeDescriptor.() -> Unit = {}, + noinline builder: suspend TaskResultBuilder.() -> Unit, +): Unit = registerTask(name.toName(), Task(NodeDescriptor(descriptorBuilder), builder)) + +public inline fun TaskContainer.task( + noinline descriptorBuilder: NodeDescriptor.() -> Unit = {}, + noinline builder: suspend TaskResultBuilder.() -> Unit, +): PropertyDelegateProvider>> = PropertyDelegateProvider { _, property -> + val taskName = property.name.toName() + val task = Task(NodeDescriptor(descriptorBuilder), builder) + registerTask(taskName, task) + ReadOnlyProperty { _, _ -> TaskReference(taskName, task) } +} + + +public class WorkspaceBuilder(private val parentContext: Context = Global) : TaskContainer { + private var context: Context? = null + private var data: DataSet<*>? = null + private val targets: HashMap = HashMap() + private val tasks = HashMap>() + + /** + * Define a context for the workspace + */ + public fun context(name: String = "workspace", block: ContextBuilder.() -> Unit = {}) { + this.context = ContextBuilder(parentContext, name).apply(block).build() + } + + /** + * Define intrinsic data for the workspace + */ + public suspend fun buildData(builder: suspend DataSetBuilder.() -> Unit) { + data = DataTree(builder) + } + + @DFExperimental + public suspend fun buildActiveData(builder: suspend ActiveDataTree.() -> Unit) { + data = ActiveDataTree(builder) + } + + /** + * Define a new target + */ + public fun target(name: String, meta: Meta?) { + if (meta == null) { + targets.remove(name) + } else { + targets[name] = meta + } + } + + override fun registerTask(taskName: Name, task: Task<*>) { + tasks[taskName] = task + } + + public fun build(): Workspace = SimpleWorkspace(context ?: parentContext, data ?: DataSet.EMPTY, targets, tasks) +} + +/** + * Define a new target with a builder + */ +public inline fun WorkspaceBuilder.target(name: String, metaBuilder: MetaBuilder.() -> Unit): Unit = + target(name, Meta(metaBuilder)) @DFBuilder -public interface WorkspaceBuilder { - public val parentContext: Context - public var context: Context - public var data: DataTreeBuilder - public var tasks: MutableSet> - public var targets: MutableMap - - public fun build(): Workspace -} - - -/** - * Set the context for future workspcace - */ -public fun WorkspaceBuilder.context(name: String = "WORKSPACE", block: ContextBuilder.() -> Unit = {}) { - context = ContextBuilder(parentContext, name).apply(block).build() -} - -public inline fun WorkspaceBuilder.data( - name: Name = Name.EMPTY, - noinline block: DataTreeBuilder.() -> Unit -): DataNode { - val node = DataTreeBuilder(T::class).apply(block) - if (name.isEmpty()) { - @Suppress("UNCHECKED_CAST") - data = node as DataTreeBuilder - } else { - data[name] = node - } - return node.build() -} - -@JvmName("rawData") -public fun WorkspaceBuilder.data( - name: Name = Name.EMPTY, - block: DataTreeBuilder.() -> Unit -): DataNode = data(name, block) - - -public fun WorkspaceBuilder.target(name: String, block: MetaBuilder.() -> Unit) { - targets[name] = Meta(block).seal() -} - -/** - * Use existing target as a base updating it with the block - */ -public fun WorkspaceBuilder.target(name: String, base: String, block: MetaBuilder.() -> Unit) { - val parentTarget = targets[base] ?: error("Base target with name $base not found") - targets[name] = parentTarget.builder() - .apply { "@baseTarget" put base } - .apply(block) - .seal() -} - -public fun WorkspaceBuilder.task( - name: String, - type: KClass, - builder: TaskBuilder.() -> Unit -): Task = TaskBuilder(name.toName(), type).apply(builder).build().also { tasks.add(it) } - -public inline fun WorkspaceBuilder.task( - name: String, - noinline builder: TaskBuilder.() -> Unit -): Task = task(name, T::class, builder) - -@JvmName("rawTask") -public fun WorkspaceBuilder.task( - name: String, - builder: TaskBuilder.() -> Unit -): Task = task(name, Any::class, builder) - -/** - * A builder for a simple workspace - */ -public class SimpleWorkspaceBuilder(override val parentContext: Context) : WorkspaceBuilder { - override var context: Context = parentContext - override var data: DataTreeBuilder = DataTreeBuilder(Any::class) - override var tasks: MutableSet> = HashSet() - override var targets: MutableMap = HashMap() - - override fun build(): SimpleWorkspace { - return SimpleWorkspace(context, data.build(), targets, tasks) - } +public fun Workspace(parentContext: Context = Global, builder: WorkspaceBuilder.() -> Unit): Workspace { + return WorkspaceBuilder(parentContext).apply(builder).build() } \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspacePlugin.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspacePlugin.kt deleted file mode 100644 index 9079139d..00000000 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspacePlugin.kt +++ /dev/null @@ -1,42 +0,0 @@ -package hep.dataforge.workspace - -import hep.dataforge.context.AbstractPlugin -import hep.dataforge.context.toMap -import hep.dataforge.names.Name -import hep.dataforge.names.toName -import kotlin.reflect.KClass - -/** - * An abstract plugin with some additional boilerplate to effectively work with workspace context - */ -public abstract class WorkspacePlugin : AbstractPlugin() { - private val _tasks = HashSet>() - public val tasks: Collection> get() = _tasks - - override fun content(target: String): Map { - return when (target) { - Task.TYPE -> tasks.toMap() - else -> emptyMap() - } - } - - public fun task(task: Task<*>){ - _tasks.add(task) - } - - public fun task( - name: String, - type: KClass, - builder: TaskBuilder.() -> Unit - ): GenericTask = TaskBuilder(name.toName(), type).apply(builder).build().also { - _tasks.add(it) - } - - public inline fun task( - name: String, - noinline builder: TaskBuilder.() -> Unit - ): GenericTask = task(name, T::class, builder) - -// -////TODO add delegates to build gradle-like tasks -} \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt index 461409e2..cebba975 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt @@ -3,13 +3,16 @@ package hep.dataforge.workspace import hep.dataforge.data.Data import hep.dataforge.data.await import hep.dataforge.io.* -import kotlin.reflect.KClass +import hep.dataforge.misc.DFInternal /** * Convert an [Envelope] to a data via given format. The actual parsing is done lazily. */ -public fun Envelope.toData(type: KClass, format: IOFormat): Data = Data(type, meta) { - data?.readWith(format) ?: error("Can't convert envelope without data to Data") +@OptIn(DFInternal::class) +public fun Envelope.toData(format: IOFormat): Data { + return Data(format.type, meta) { + data?.readWith(format) ?: error("Can't convert envelope without data to Data") + } } public suspend fun Data.toEnvelope(format: IOFormat): Envelope { diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/WorkspacePlugin.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/WorkspacePlugin.kt new file mode 100644 index 00000000..b30d141b --- /dev/null +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/WorkspacePlugin.kt @@ -0,0 +1,22 @@ +package hep.dataforge.workspace + +import hep.dataforge.context.AbstractPlugin +import hep.dataforge.names.Name + +/** + * An abstract plugin with some additional boilerplate to effectively work with workspace context + */ +public abstract class WorkspacePlugin : AbstractPlugin(), TaskContainer { + private val tasks = HashMap>() + + override fun content(target: String): Map { + return when (target) { + Task.TYPE -> tasks + else -> emptyMap() + } + } + + override fun registerTask(taskName: Name, task: Task<*>) { + tasks[taskName] = task + } +} \ No newline at end of file diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt index 97ae77e6..a8378046 100644 --- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt @@ -4,7 +4,9 @@ package hep.dataforge.workspace import hep.dataforge.data.* import hep.dataforge.io.* import hep.dataforge.meta.* +import hep.dataforge.misc.DFExperimental import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlinx.io.asOutput import java.nio.file.FileSystem @@ -14,11 +16,25 @@ import java.nio.file.StandardOpenOption import java.nio.file.spi.FileSystemProvider import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream -import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf +import kotlin.streams.toList -typealias FileFormatResolver = (Path, Meta) -> IOFormat +//public typealias FileFormatResolver = (Path, Meta) -> IOFormat +public interface FileFormatResolver { + public val type: KType + public operator fun invoke(path: Path, meta: Meta): IOFormat +} +@PublishedApi +internal inline fun IOPlugin.formatResolver(): FileFormatResolver = + object : FileFormatResolver { + override val type: KType = typeOf() + + override fun invoke(path: Path, meta: Meta): IOFormat = + resolveIOFormat() ?: error("Can't resolve IO format for ${T::class}") + } private fun newZFS(path: Path): FileSystem { val fsProvider = FileSystemProvider.installedProviders().find { it.scheme == "jar" } @@ -38,44 +54,40 @@ private fun newZFS(path: Path): FileSystem { @DFExperimental public fun IOPlugin.readDataFile( path: Path, - type: KClass, - formatResolver: FileFormatResolver + formatResolver: FileFormatResolver, ): Data { val envelope = readEnvelopeFile(path, true) ?: error("Can't read data from $path") val format = formatResolver(path, envelope.meta) - return envelope.toData(type, format) + return envelope.toData(format) } @DFExperimental -public inline fun IOPlugin.readDataFile(path: Path): Data = - readDataFile(path, T::class) { _, _ -> - resolveIOFormat() ?: error("Can't resolve IO format for ${T::class}") - } +public inline fun IOPlugin.readDataFile(path: Path): Data = readDataFile(path, formatResolver()) /** * Add file/directory-based data tree item */ @DFExperimental -public fun DataTreeBuilder.file( +public suspend fun DataSetBuilder.file( plugin: IOPlugin, path: Path, - formatResolver: FileFormatResolver + formatResolver: FileFormatResolver, ) { //If path is a single file or a special directory, read it as single datum if (!Files.isDirectory(path) || Files.list(path).allMatch { it.fileName.toString().startsWith("@") }) { plugin.run { - val data = readDataFile(path, type, formatResolver) + val data = readDataFile(path, formatResolver) val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string ?: path.fileName.toString().replace(".df", "") - datum(name, data) + emit(name, data) } } else { //otherwise, read as directory plugin.run { - val data = readDataDirectory(path, type, formatResolver) - val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string + val data = readDataDirectory(path, formatResolver) + val name = data.getMeta()[Envelope.ENVELOPE_NAME_KEY].string ?: path.fileName.toString().replace(".df", "") - node(name, data) + emit(name, data) } } } @@ -84,35 +96,34 @@ public fun DataTreeBuilder.file( * Read the directory as a data node. If [path] is a zip archive, read it as directory */ @DFExperimental -fun IOPlugin.readDataDirectory( +public suspend fun IOPlugin.readDataDirectory( path: Path, - type: KClass, - formatResolver: FileFormatResolver -): DataNode { + formatResolver: FileFormatResolver, +): DataTree { //read zipped data node if (path.fileName != null && path.fileName.toString().endsWith(".zip")) { //Using explicit Zip file system to avoid bizarre compatibility bugs val fs = newZFS(path) - return readDataDirectory(fs.rootDirectories.first(), type, formatResolver) + return readDataDirectory(fs.rootDirectories.first(), formatResolver) } if (!Files.isDirectory(path)) error("Provided path $path is not a directory") - return DataNode(type) { - Files.list(path).forEach { path -> + return DataTree(formatResolver.type) { + Files.list(path).toList().forEach { path -> val fileName = path.fileName.toString() if (fileName.startsWith(IOPlugin.META_FILE_NAME)) { meta(readMetaFile(path)) } else if (!fileName.startsWith("@")) { - file(this@readDataDirectory, path, formatResolver) + runBlocking { + file(this@readDataDirectory, path, formatResolver) + } } } } } @DFExperimental -public inline fun IOPlugin.readDataDirectory(path: Path): DataNode = - readDataDirectory(path, T::class) { _, _ -> - resolveIOFormat() ?: error("Can't resolve IO format for ${T::class}") - } +public suspend inline fun IOPlugin.readDataDirectory(path: Path): DataTree = + readDataDirectory(path, formatResolver()) /** * Write data tree to existing directory or create a new one using default [java.nio.file.FileSystem] provider @@ -120,10 +131,10 @@ public inline fun IOPlugin.readDataDirectory(path: Path): Data @DFExperimental public suspend fun IOPlugin.writeDataDirectory( path: Path, - node: DataNode, + tree: DataTree, format: IOFormat, envelopeFormat: EnvelopeFormat? = null, - metaFormat: MetaFormatFactory? = null + metaFormat: MetaFormatFactory? = null, ) { withContext(Dispatchers.IO) { if (!Files.exists(path)) { @@ -131,13 +142,13 @@ public suspend fun IOPlugin.writeDataDirectory( } else if (!Files.isDirectory(path)) { error("Can't write a node into file") } - node.items.forEach { (token, item) -> + tree.items().forEach { (token, item) -> val childPath = path.resolve(token.toString()) when (item) { - is DataItem.Node -> { - writeDataDirectory(childPath, item.node, format, envelopeFormat) + is DataTreeItem.Node -> { + writeDataDirectory(childPath, item.tree, format, envelopeFormat) } - is DataItem.Leaf -> { + is DataTreeItem.Leaf -> { val envelope = item.data.toEnvelope(format) if (envelopeFormat != null) { writeEnvelopeFile(childPath, envelope, envelopeFormat, metaFormat) @@ -147,8 +158,9 @@ public suspend fun IOPlugin.writeDataDirectory( } } } - if (!node.meta.isEmpty()) { - writeMetaFile(path, node.meta, metaFormat ?: JsonMetaFormat) + val treeMeta = tree.getMeta() + if (treeMeta != null) { + writeMetaFile(path, treeMeta, metaFormat ?: JsonMetaFormat) } } } @@ -156,26 +168,26 @@ public suspend fun IOPlugin.writeDataDirectory( private suspend fun ZipOutputStream.writeNode( name: String, - item: DataItem, + treeItem: DataTreeItem, dataFormat: IOFormat, - envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat + envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat, ) { withContext(Dispatchers.IO) { - when (item) { - is DataItem.Leaf -> { + when (treeItem) { + is DataTreeItem.Leaf -> { //TODO add directory-based envelope writer - val envelope = item.data.toEnvelope(dataFormat) + val envelope = treeItem.data.toEnvelope(dataFormat) val entry = ZipEntry(name) putNextEntry(entry) envelopeFormat.run { writeObject(asOutput(), envelope) } } - is DataItem.Node -> { + is DataTreeItem.Node -> { val entry = ZipEntry("$name/") putNextEntry(entry) closeEntry() - item.node.items.forEach { (token, item) -> + treeItem.tree.items().forEach { (token, item) -> val childName = "$name/$token" writeNode(childName, item, dataFormat, envelopeFormat) } @@ -187,9 +199,9 @@ private suspend fun ZipOutputStream.writeNode( @DFExperimental suspend fun IOPlugin.writeZip( path: Path, - node: DataNode, + tree: DataTree, format: IOFormat, - envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat + envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat, ) { withContext(Dispatchers.IO) { val actualFile = if (path.toString().endsWith(".zip")) { @@ -197,10 +209,13 @@ suspend fun IOPlugin.writeZip( } else { path.resolveSibling(path.fileName.toString() + ".zip") } - val fos = Files.newOutputStream(actualFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING) + val fos = Files.newOutputStream(actualFile, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING) val zos = ZipOutputStream(fos) zos.use { - it.writeNode("", DataItem.Node(node), format, envelopeFormat) + it.writeNode("", DataTreeItem.Node(tree), format, envelopeFormat) } // if (Files.exists(actualFile) && Files.size(path) == 0.toLong()) { diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/taskBuilders.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/taskBuilders.kt new file mode 100644 index 00000000..fce1a372 --- /dev/null +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/taskBuilders.kt @@ -0,0 +1,24 @@ +package hep.dataforge.workspace + +import hep.dataforge.data.DataSet +import hep.dataforge.data.select +import hep.dataforge.meta.Meta +import hep.dataforge.names.Name + +public suspend inline fun TaskResultBuilder.from( + task: Name, + taskMeta: Meta = Meta.EMPTY, +): DataSet = workspace.produce(task, taskMeta).select() + + +@Suppress("UNCHECKED_CAST") +public suspend fun TaskResultBuilder<*>.from( + reference: TaskReference, + taskMeta: Meta = Meta.EMPTY, +): DataSet { + if (workspace.tasks[reference.taskName] == reference.task) { + return workspace.produce(reference.taskName, taskMeta) as TaskResult + } else { + throw error("Task ${reference.taskName} does not belong to the workspace") + } +} diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/workspaceExtensions.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/workspaceExtensions.kt new file mode 100644 index 00000000..00f213f5 --- /dev/null +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/workspaceExtensions.kt @@ -0,0 +1,8 @@ +package hep.dataforge.workspace + +import hep.dataforge.data.DataSetBuilder +import kotlinx.coroutines.runBlocking + +public fun WorkspaceBuilder.data(builder: suspend DataSetBuilder.() -> Unit): Unit = runBlocking { + buildData(builder) +} \ No newline at end of file diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt index 083d3f57..3b70ed5e 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt @@ -5,49 +5,28 @@ import hep.dataforge.context.PluginFactory import hep.dataforge.context.PluginTag import hep.dataforge.data.* import hep.dataforge.meta.Meta -import hep.dataforge.names.asName +import hep.dataforge.names.toName +import kotlinx.coroutines.flow.single +import kotlinx.coroutines.runBlocking import kotlin.reflect.KClass import kotlin.test.Test import kotlin.test.assertEquals - class DataPropagationTestPlugin : WorkspacePlugin() { override val tag: PluginTag = Companion.tag - val testAllData = task("allData", Int::class) { - model { - allData() - } - transform { data -> - return@transform DataNode { - val result = data.dataSequence().map { it.second.get() }.reduce { acc, pair -> acc + pair } - set("result".asName(), Data { result }) - } + val allData by task { + val selectedData = workspace.data.select() + val result: Data = selectedData.flow().foldToData(0) { result, data -> + result + data.await() } + emit("result", result) } - val testSingleData = task("singleData", Int::class) { - model { - data("myData\\[12\\]") - } - transform { data -> - return@transform DataNode { - val result = data.dataSequence().map { it.second.get() }.reduce { acc, pair -> acc + pair } - set("result".asName(), Data { result }) - } - } - } - - val testAllRegexData = task("allRegexData", Int::class) { - model { - data(pattern = "myData.*") - } - transform { data -> - return@transform DataNode { - val result = data.dataSequence().map { it.second.get() }.reduce { acc, pair -> acc + pair } - set("result".asName(), Data { result }) - } + val singleData by task { + workspace.data.select().getData("myData[12]".toName())?.let { + emit("result", it) } } @@ -56,8 +35,7 @@ class DataPropagationTestPlugin : WorkspacePlugin() { override val type: KClass = DataPropagationTestPlugin::class - override fun invoke(meta: Meta, context: Context): DataPropagationTestPlugin = - DataPropagationTestPlugin(meta) + override fun invoke(meta: Meta, context: Context): DataPropagationTestPlugin = DataPropagationTestPlugin() override val tag: PluginTag = PluginTag("Test") } @@ -66,30 +44,30 @@ class DataPropagationTestPlugin : WorkspacePlugin() { class DataPropagationTest { val testWorkspace = Workspace { context { - plugin(DataPropagationTestPlugin()) + plugin(DataPropagationTestPlugin) } - data { - repeat(100) { - static("myData[$it]", it) + runBlocking { + data { + repeat(100) { + static("myData[$it]", it) + } } } } @Test fun testAllData() { - val node = testWorkspace.run("Test.allData") - assertEquals(4950, node.first()!!.get()) - } - - @Test - fun testAllRegexData() { - val node = testWorkspace.run("Test.allRegexData") - assertEquals(4950, node.first()!!.get()) + runBlocking { + val node = testWorkspace.produce("Test.allData") + assertEquals(4950, node.flow().single().await()) + } } @Test fun testSingleData() { - val node = testWorkspace.run("Test.singleData") - assertEquals(12, node.first()!!.get()) + runBlocking { + val node = testWorkspace.produce("Test.singleData") + assertEquals(12, node.flow().single().await()) + } } } \ No newline at end of file diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt index 7357c5c5..df8e2673 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt @@ -4,32 +4,40 @@ import hep.dataforge.context.Global import hep.dataforge.data.* import hep.dataforge.io.IOFormat import hep.dataforge.io.io -import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta +import hep.dataforge.misc.DFExperimental import kotlinx.coroutines.runBlocking import kotlinx.io.Input import kotlinx.io.Output import kotlinx.io.text.readUtf8String import kotlinx.io.text.writeUtf8String import java.nio.file.Files +import java.nio.file.Path +import kotlin.reflect.KType +import kotlin.reflect.typeOf import kotlin.test.Test import kotlin.test.assertEquals class FileDataTest { - val dataNode = DataNode { - node("dir") { - static("a", "Some string") { - "content" put "Some string" + val dataNode = runBlocking { + DataTree { + emit("dir") { + static("a", "Some string") { + "content" put "Some string" + } + } + static("b", "root data") + meta { + "content" put "This is root meta node" } - } - static("b", "root data") - meta { - "content" put "This is root meta node" } } object StringIOFormat : IOFormat { + + override val type: KType = typeOf() + override fun writeObject(output: Output, obj: String) { output.writeUtf8String(obj) } @@ -44,6 +52,13 @@ class FileDataTest { } + object StringFormatResolver : FileFormatResolver { + override val type: KType = typeOf() + + override fun invoke(path: Path, meta: Meta): IOFormat = StringIOFormat + + } + @Test @DFExperimental fun testDataWriteRead() { @@ -51,11 +66,11 @@ class FileDataTest { val dir = Files.createTempDirectory("df_data_node") runBlocking { writeDataDirectory(dir, dataNode, StringIOFormat) + println(dir.toUri().toString()) + val reconstructed = readDataDirectory(dir, StringFormatResolver) + assertEquals(dataNode.getData("dir.a")?.meta, reconstructed.getData("dir.a")?.meta) + assertEquals(dataNode.getData("b")?.await(), reconstructed.getData("b")?.await()) } - println(dir.toUri().toString()) - val reconstructed = readDataDirectory(dir, String::class) { _, _ -> StringIOFormat } - assertEquals(dataNode["dir.a"]?.meta, reconstructed["dir.a"]?.meta) - assertEquals(dataNode["b"]?.data?.get(), reconstructed["b"]?.data?.get()) } } @@ -67,11 +82,11 @@ class FileDataTest { val zip = Files.createTempFile("df_data_node", ".zip") runBlocking { writeZip(zip, dataNode, StringIOFormat) + println(zip.toUri().toString()) + val reconstructed = readDataDirectory(zip, StringFormatResolver) + assertEquals(dataNode.getData("dir.a")?.meta, reconstructed.getData("dir.a")?.meta) + assertEquals(dataNode.getData("b")?.await(), reconstructed.getData("b")?.await()) } - println(zip.toUri().toString()) - val reconstructed = readDataDirectory(zip, String::class) { _, _ -> StringIOFormat } - assertEquals(dataNode["dir.a"]?.meta, reconstructed["dir.a"]?.meta) - assertEquals(dataNode["b"]?.data?.get(), reconstructed["b"]?.data?.get()) } } } \ No newline at end of file diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt index a4df6a4b..5ee08ea5 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt @@ -1,32 +1,57 @@ package hep.dataforge.workspace -import hep.dataforge.context.PluginTag +import hep.dataforge.context.* import hep.dataforge.data.* -import hep.dataforge.meta.boolean -import hep.dataforge.meta.builder -import hep.dataforge.meta.get -import hep.dataforge.meta.int +import hep.dataforge.meta.* +import hep.dataforge.names.get import hep.dataforge.names.plus +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.single +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Timeout +import kotlin.reflect.KClass import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +/** + * Make a fake-factory for a one single plugin. Useful for unique or test plugins + */ +public inline fun P.toFactory(): PluginFactory

    = object : PluginFactory

    { + override fun invoke(meta: Meta, context: Context): P = this@toFactory + + override val tag: PluginTag = this@toFactory.tag + override val type: KClass = P::class +} + +public fun Workspace.runBlocking(task: String, block: MetaBuilder.() -> Unit = {}): DataSet = runBlocking { + produce(task, block) +} + + class SimpleWorkspaceTest { val testPlugin = object : WorkspacePlugin() { override val tag: PluginTag = PluginTag("test") - val contextTask = task("test", Any::class) { - map { - context.logger.info { "Test: $it" } - } + val test by task { + populate( + workspace.data.map { + it.also { + logger.info { "Test: $it" } + } + } + ) } } + + val testPluginFactory = testPlugin.toFactory() + val workspace = Workspace { context { - plugin(testPlugin) + plugin(testPluginFactory) } data { @@ -35,98 +60,82 @@ class SimpleWorkspaceTest { } } - val filterTask = task("filterOne") { - model { - data("myData\\[12\\]") - } - map{ - it + val filterOne by task { + workspace.data.selectOne("myData[12]")?.let { source -> + emit(source.name, source.map { it }) } } - val square = task("square") { - map { data -> - if (meta["testFlag"].boolean == true) { + val square by task { + workspace.data.select().forEach { data -> + if (data.meta["testFlag"].boolean == true) { println("flag") } - context.logger.info { "Starting square on $data" } - data * data + val value = data.await() + workspace.logger.info { "Starting square on $value" } + emit(data.name, data.map { it * it }) } } - val linear = task("linear") { - map { data -> - context.logger.info { "Starting linear on $data" } - data * 2 + 1 + val linear by task { + workspace.data.select().forEach { data -> + workspace.logger.info { "Starting linear on $data" } + emit(data.name, data.data.map { it * 2 + 1 }) } } - val fullSquare = task("fullsquare") { - model { - val squareDep = dependsOn(square, placement = "square") - val linearDep = dependsOn(linear, placement = "linear") - } - transform { data -> - val squareNode = data["square"].node!!.cast()//squareDep() - val linearNode = data["linear"].node!!.cast()//linearDep() - return@transform DataNode(Int::class) { - squareNode.dataSequence().forEach { (name, _) -> - val newData = Data { - val squareValue = squareNode[name].data!!.get() - val linearValue = linearNode[name].data!!.get() - squareValue + linearValue - } - set(name, newData) - } + val fullSquare by task { + val squareData = from(square) + val linearData = from(linear) + squareData.forEach { data -> + val newData: Data = data.combine(linearData.getData(data.name)!!) { l, r -> + l + r } + emit(data.name, newData) } } - task("sum") { - model { - dependsOn(square) - } - reduce { data -> - context.logger.info { "Starting sum" } - data.values.sum() + val sum by task { + workspace.logger.info { "Starting sum" } + val res = from(square).foldToData(0) { l, r -> + l + r.await() } + emit("sum", res) } - val average = task("average") { - reduceByGroup { env -> - group("even", filter = { name, _ -> name.toString().toInt() % 2 == 0 }) { - result { data -> - env.context.logger.info { "Starting even" } - data.values.average() - } - } - group("odd", filter = { name, _ -> name.toString().toInt() % 2 == 1 }) { - result { data -> - env.context.logger.info { "Starting odd" } - data.values.average() - } - } + val averageByGroup by task { + val evenSum = workspace.data.filter { name, _ -> + name.toString().toInt() % 2 == 0 + }.select().foldToData(0) { l, r -> + l + r.await() } + + emit("even", evenSum) + val oddSum = workspace.data.filter { name, _ -> + name.toString().toInt() % 2 == 1 + }.select().foldToData(0) { l, r -> + l + r.await() + } + emit("odd", oddSum) } - task("delta") { - model { - dependsOn(average) - } - reduce { data -> - data["even"]!! - data["odd"]!! + val delta by task { + val averaged = from(averageByGroup) + val even = averaged.getData("event")!! + val odd = averaged.getData("odd")!! + val res = even.combine(odd) { l, r -> + l - r } + emit("res", res) } - val customPipeTask = task("custom") { - mapAction { - meta = meta.builder().apply { + val customPipe by task { + workspace.data.select().forEach { data -> + val meta = data.meta.toMutableMeta().apply { "newValue" put 22 } - name += "new" - result { - meta["value"].int ?: 0 + it - } + emit(data.name + "new", data.map { (data.meta["value"].int ?: 0) + it }) + } } @@ -134,16 +143,22 @@ class SimpleWorkspaceTest { } @Test + @Timeout(1) fun testWorkspace() { - val node = workspace.run("sum") - val res = node.first() - assertEquals(328350, res?.get()) + runBlocking { + val node = workspace.runBlocking("sum") + val res = node.flow().single() + assertEquals(328350, res.await()) + } } @Test + @Timeout(1) fun testMetaPropagation() { - val node = workspace.run("sum") { "testFlag" put true } - val res = node.first()?.get() + runBlocking { + val node = workspace.produce("sum") { "testFlag" put true } + val res = node.flow().single().await() + } } @Test @@ -155,13 +170,17 @@ class SimpleWorkspaceTest { @Test fun testFullSquare() { - val node = workspace.run("fullsquare") - println(node.toMeta()) + runBlocking { + val node = workspace.produce("fullSquare") + println(node.toMeta()) + } } @Test - fun testGather() { - val node = workspace.run("filterOne") - assertEquals(12, node.first()?.get()) + fun testFilter() { + runBlocking { + val node = workspace.produce("filterOne") + assertEquals(12, node.flow().first().await()) + } } } \ No newline at end of file diff --git a/docs/templates/ARTIFACT-TEMPLATE.md b/docs/templates/ARTIFACT-TEMPLATE.md new file mode 100644 index 00000000..410c6cad --- /dev/null +++ b/docs/templates/ARTIFACT-TEMPLATE.md @@ -0,0 +1,32 @@ +> #### Artifact: +> +> This module artifact: `${group}:${name}:${version}`. +> +> Bintray release version: [ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/${name}/images/download.svg) ](https://bintray.com/mipt-npm/kscience/${name}/_latestVersion) +> +> Bintray development version: [ ![Download](https://api.bintray.com/packages/mipt-npm/dev/${name}/images/download.svg) ](https://bintray.com/mipt-npm/dev/${name}/_latestVersion) +> +> **Gradle:** +> +> ```gradle +> repositories { +> maven { url 'https://dl.bintray.com/mipt-npm/dataforge' } +> maven { url 'https://dl.bintray.com/mipt-npm/dev' } +> } +> +> dependencies { +> implementation '${group}:${name}:${version}' +> } +> ``` +> **Gradle Kotlin DSL:** +> +> ```kotlin +> repositories { +> maven("https://dl.bintray.com/mipt-npm/kscience") +> maven("https://dl.bintray.com/mipt-npm/dev") +> } +> +> dependencies { +> implementation("${group}:${name}:${version}") +> } +> ``` \ No newline at end of file diff --git a/docs/templates/README-TEMPLATE.md b/docs/templates/README-TEMPLATE.md new file mode 100644 index 00000000..e0d7bb23 --- /dev/null +++ b/docs/templates/README-TEMPLATE.md @@ -0,0 +1,8 @@ +[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) +[![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678) + +![Gradle build](https://github.com/mipt-npm/dataforge-core/workflows/Gradle%20build/badge.svg) + +[ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/dataforge-meta/images/download.svg) ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion) + +$modules diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 12d38de6..28ff446a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 1d15b239..defbc0e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,8 +10,8 @@ pluginManagement { maven("https://dl.bintray.com/mipt-npm/dev") } - val toolsVersion = "0.7.0" - val kotlinVersion = "1.4.20" + val toolsVersion = "0.7.6" + val kotlinVersion = "1.4.30" plugins { id("ru.mipt.npm.project") version toolsVersion @@ -27,7 +27,7 @@ pluginManagement { include( ":dataforge-meta", ":dataforge-io", -// ":dataforge-io:dataforge-io-yaml", + ":dataforge-io:dataforge-io-yaml", ":dataforge-context", ":dataforge-data", // ":dataforge-output",