From d3ce88eb3f8532106de150f2481b149171ee7917 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 6 Nov 2018 14:09:43 +0300 Subject: [PATCH] Context module --- build.gradle | 2 - dataforge-context/build.gradle | 32 ++++ .../kotlin/hep/dataforge/context/Context.kt | 106 +++++++++++++ .../kotlin/hep/dataforge/context/Named.kt | 57 +++++++ .../kotlin/hep/dataforge/context/Plugin.kt | 78 ++++++++++ .../hep/dataforge/context/PluginManager.kt | 145 ++++++++++++++++++ .../hep/dataforge/context/PluginRepository.kt | 25 +++ .../kotlin/hep/dataforge/context/PluginTag.kt | 62 ++++++++ .../kotlin/hep/dataforge/provider/Path.kt | 75 +++++++++ .../kotlin/hep/dataforge/provider/Provider.kt | 89 +++++++++++ .../kotlin/hep/dataforge/context/Global.kt | 76 +++++++++ .../hep/dataforge/context/JVMContext.kt | 125 +++++++++++++++ dataforge-data/build.gradle | 25 +++ dataforge-io/build.gradle | 25 +++ .../hep/dataforge/meta/io/MetaFormat.kt | 16 +- .../hep/dataforge/meta/io/MetaFormatTest.kt | 14 ++ .../kotlin/hep/dataforge/meta/io/JSMeta.kt | 4 +- .../hep/dataforge/meta/io/JSONMetaFormat.kt | 16 +- .../kotlin/hep/dataforge/meta/Config.kt | 3 +- .../kotlin/hep/dataforge/meta/Delegates.kt | 2 + .../kotlin/hep/dataforge/meta/Laminate.kt | 12 +- .../kotlin/hep/dataforge/meta/Meta.kt | 35 ++++- .../kotlin/hep/dataforge/meta/MetaBuilder.kt | 4 +- .../hep/dataforge/meta/MutableMetaNode.kt | 30 ++-- .../kotlin/hep/dataforge/meta/Styleable.kt | 4 +- .../kotlin/hep/dataforge/names/Name.kt | 17 +- .../hep/dataforge/{meta => values}/Value.kt | 38 ++++- .../hep/dataforge/meta/MetaBuilderTest.kt | 1 + .../kotlin/hep/dataforge/meta/MetaTest.kt | 33 ++++ .../kotlin/hep/dataforge/names/NameTest.kt | 7 + dataforge-tables/build.gradle | 25 +++ dataforge-workspace/build.gradle | 25 +++ settings.gradle | 8 + 33 files changed, 1151 insertions(+), 65 deletions(-) create mode 100644 dataforge-context/build.gradle create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Named.kt create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt create mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt create mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt create mode 100644 dataforge-data/build.gradle create mode 100644 dataforge-io/build.gradle rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/{meta => values}/Value.kt (80%) create mode 100644 dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt create mode 100644 dataforge-tables/build.gradle create mode 100644 dataforge-workspace/build.gradle diff --git a/build.gradle b/build.gradle index 76235d81..38a7fe77 100644 --- a/build.gradle +++ b/build.gradle @@ -20,9 +20,7 @@ allprojects { repositories { jcenter() - maven { url = "http://dl.bintray.com/kotlin/kotlin-eap" } maven { url = "https://kotlin.bintray.com/kotlinx" } - //maven { url 'https://jitpack.io' } } group = 'hep.dataforge' diff --git a/dataforge-context/build.gradle b/dataforge-context/build.gradle new file mode 100644 index 00000000..c7228860 --- /dev/null +++ b/dataforge-context/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'kotlin-multiplatform' +} + +repositories { + jcenter() +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + //fromPreset(presets.js, 'js') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.iosX64, 'ios') + } + sourceSets { + commonMain { + dependencies { + api project(":dataforge-meta") + api "org.jetbrains.kotlin:kotlin-reflect" + api "io.github.microutils:kotlin-logging-common:1.6.10" + } + } + jvmMain{ + dependencies{ + api "io.github.microutils:kotlin-logging:1.6.10" + } + } + } +} \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt new file mode 100644 index 00000000..f88e7cf3 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt @@ -0,0 +1,106 @@ +package hep.dataforge.context + +import hep.dataforge.meta.* +import hep.dataforge.names.Name +import hep.dataforge.names.toName +import hep.dataforge.provider.Provider +import hep.dataforge.provider.provideAll +import hep.dataforge.values.Value +import mu.KLogger +import mu.KotlinLogging +import kotlin.reflect.KClass + +interface Context : Named, MetaRepr, Provider { + + val parent: Context? + + /** + * Context properties. Working as substitutes for environment variables + */ + val properties: Meta + + /** + * Context logger + */ + val logger: KLogger + + val plugins: PluginManager + + /** + * Defines if context is used in any kind of active computations. Active context properties and plugins could not be changed + */ + val isActive: Boolean + + /** + * Provide services for given type + */ + fun services(type: KClass): Sequence + + override val defaultTarget: String get() = Plugin.PLUGIN_TARGET + + override fun provideTop(target: String, name: Name): Any? { + return when (target) { + Plugin.PLUGIN_TARGET -> plugins[PluginTag.fromString(name.toString())] + Value.VALUE_TARGET -> properties[name]?.value + else -> null + } + } + + override fun listTop(target: String): Sequence { + return when (target) { + Plugin.PLUGIN_TARGET -> plugins.asSequence().map { it.name.toName() } + Value.VALUE_TARGET -> properties.asValueSequence().map { it.first } + else -> emptySequence() + } + } + + /** + * Mark context as active and used by [activator] + */ + fun activate(activator: Any) + + /** + * Mark context unused by [activator] + */ + fun deactivate(activator: Any) + + /** + * Detach all plugins and terminate context + */ + fun close() +} + +/** + * A sequences of all objects provided by plugins with given target and type + */ +inline fun Context.list(target: String): Sequence { + return plugins.asSequence().flatMap { provideAll(target) }.mapNotNull { it as? T } +} + +/** + * A global root context + */ +expect object Global : Context + +/** + * The interface for something that encapsulated in context + * + * @author Alexander Nozik + * @version $Id: $Id + */ +interface ContextAware { + /** + * Get context for this object + * + * @return + */ + val context: Context + + val logger: KLogger + get() = if (this is Named) { + KotlinLogging.logger(context.name + "." + (this as Named).name) + } else { + context.logger + } + +} \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Named.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Named.kt new file mode 100644 index 00000000..1ac31702 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Named.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package hep.dataforge.context + +/** + * Any object that have name + * + * @author Alexander Nozik + */ +interface Named { + + /** + * The name of this object instance + * + * @return + */ + val name: String + + companion object { + const val ANONYMOUS = "" + + /** + * Get the name of given object. If object is Named its name is used, + * otherwise, use Object.toString + * + * @param obj + * @return + */ + fun nameOf(obj: Any): String { + return if (obj is Named) { + obj.name + } else { + obj.toString() + } + } + } +} + +/** + * Check if this object has an empty name and therefore is anonymous. + * @return + */ +val Named.isAnonymous: Boolean + get() = this.name == Named.ANONYMOUS diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt new file mode 100644 index 00000000..56b2a63d --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt @@ -0,0 +1,78 @@ +package hep.dataforge.context + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaRepr +import hep.dataforge.meta.Metoid +import hep.dataforge.meta.buildMeta +import hep.dataforge.provider.Provider + +/** + * The interface to define a Context plugin. A plugin stores all runtime features of a context. + * The plugin is by default configurable and a Provider (both features could be ignored). + * The plugin must in most cases have an empty constructor in order to be able to load it from library. + * + * + * The plugin lifecycle is the following: + * + * + * create - configure - attach - detach - destroy + * + * + * Configuration of attached plugin is possible for a context which is not in a runtime mode, but it is not recommended. + * + * @author Alexander Nozik + */ +interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr { + + /** + * Get tag for this plugin + * + * @return + */ + val tag: PluginTag + + /** + * The name of this plugin ignoring version and group + * + * @return + */ + override val name: String + get() = tag.name + + /** + * Plugin dependencies which are required to attach this plugin. Plugin + * dependencies must be initialized and enabled in the Context before this + * plugin is enabled. + * + * @return + */ + fun dependsOn(): List + + /** + * Start this plugin and attach registration info to the context. This method + * should be called only via PluginManager to avoid dependency issues. + * + * @param context + */ + fun attach(context: Context) + + /** + * Stop this plugin and remove registration info from context and other + * plugins. This method should be called only via PluginManager to avoid + * dependency issues. + */ + fun detach() + + override fun toMeta(): Meta = buildMeta { + "context" to context.name + "type" to this::class.qualifiedName + "tag" to tag + "meta" to meta + } + + companion object { + + const val PLUGIN_TARGET = "plugin" + } + +} \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt new file mode 100644 index 00000000..d50893e2 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt @@ -0,0 +1,145 @@ +package hep.dataforge.context + +import hep.dataforge.meta.EmptyMeta +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.buildMeta +import kotlin.reflect.KClass + +/** + * The manager for plugin system. Should monitor plugin dependencies and locks. + * + * @property context A context for this plugin manager + * @author Alexander Nozik + */ +class PluginManager(override val context: Context) : ContextAware, Iterable { + + /** + * A set of loaded plugins + */ + private val plugins = HashSet() + + private val parent: PluginManager? = context.parent?.plugins + + + fun sequence(recursive: Boolean): Sequence { + return if (recursive && parent != null) { + plugins.asSequence() + parent.sequence(true) + } else { + plugins.asSequence() + } + } + + /** + * Get for existing plugin + */ + fun get(recursive: Boolean = true, predicate: (Plugin) -> Boolean): Plugin? = sequence(recursive).find(predicate) + + + /** + * Find a loaded plugin via its tag + * + * @param tag + * @return + */ + operator fun get(tag: PluginTag, recursive: Boolean = true): Plugin? = get(recursive) { tag.matches(it.tag) } + + + /** + * Find a loaded plugin via its class + * + * @param tag + * @param type + * @param + * @return + */ + @Suppress("UNCHECKED_CAST") + operator fun get(type: KClass, recursive: Boolean = true): T? = get(recursive) { type.isInstance(it) } as T? + + inline fun get(recursive: Boolean = true): T? = get(T::class, recursive) + + + /** + * Load given plugin into this manager and return loaded instance. + * Throw error if plugin of the same class already exists in manager + * + * @param plugin + * @return + */ + fun load(plugin: T): T { + if (context.isActive) error("Can't load plugin into active context") + + if (get(plugin::class, false) != null) { + throw RuntimeException("Plugin of type ${plugin::class} already exists in ${context.name}") + } else { + loadDependencies(plugin) + + logger.info { "Loading plugin ${plugin.name} into ${context.name}" } + plugin.attach(context) + plugins.add(plugin) + return plugin + } + } + + private fun loadDependencies(plugin: Plugin) { + for (tag in plugin.dependsOn()) { + load(tag) + } + } + + fun remove(plugin: Plugin) { + if (context.isActive) error("Can't remove plugin from active context") + + if (plugins.contains(plugin)) { + logger.info { "Removing plugin ${plugin.name} from ${context.name}" } + plugin.detach() + plugins.remove(plugin) + } + } + + /** + * Get plugin instance via plugin reolver and load it. + * + * @param tag + * @return + */ + fun load(tag: PluginTag, meta: Meta = EmptyMeta): Plugin { + val loaded = get(tag, false) + return when { + loaded == null -> load(PluginRepository.fetch(tag, meta)) + loaded.meta == meta -> loaded // if meta is the same, return existing plugin + else -> throw RuntimeException("Can't load plugin with tag $tag. Plugin with this tag and different configuration already exists in context.") + } + } + + /** + * Load plugin by its class and meta. Ignore if plugin with this meta is already loaded. + */ + fun load(type: KClass, meta: Meta = EmptyMeta): T { + val loaded = get(type, false) + return when { + loaded == null -> { + val plugin = PluginRepository.list().first { it.type == type }.build(meta) + if (type.isInstance(plugin)) { + @Suppress("UNCHECKED_CAST") + load(plugin as T) + } else { + error("Corrupt type information in plugin repository") + } + } + loaded.meta == meta -> loaded // if meta is the same, return existing plugin + else -> throw RuntimeException("Can't load plugin with type $type. Plugin with this type and different configuration already exists in context.") + } + } + + inline fun load(noinline metaBuilder: MetaBuilder.() -> Unit = {}): T { + return load(T::class, buildMeta(metaBuilder)) + } + + fun load(name: String, meta: Meta = EmptyMeta): Plugin { + return load(PluginTag.fromString(name), meta) + } + + override fun iterator(): Iterator = plugins.iterator() + +} \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt new file mode 100644 index 00000000..3ac99da4 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt @@ -0,0 +1,25 @@ +package hep.dataforge.context + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +interface PluginFactory { + val tag: PluginTag + val type: KClass + fun build(meta: Meta): Plugin +} + + +object PluginRepository { + + /** + * List plugins available in the repository + */ + fun list(): Sequence = Global.services(PluginFactory::class) + + /** + * Fetch specific plugin and instantiate it with given meta + */ + fun fetch(tag: PluginTag, meta: Meta): Plugin = PluginRepository.list().find { it.tag.matches(tag) }?.build(meta) + ?: error("Plugin with tag $tag not found in the repository") +} \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt new file mode 100644 index 00000000..6196a8cb --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt @@ -0,0 +1,62 @@ +package hep.dataforge.context + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaRepr +import hep.dataforge.meta.buildMeta + +/** + * The tag which contains information about name, group and version of some + * object. It also could contain any complex rule to define version ranges + * + * @author Alexander Nozik + */ +data class PluginTag( + val name: String, + val group: String = "", + val version: String = "" +) : MetaRepr { + + /** + * Check if given tag is compatible (in range) of this tag + * + * @param otherTag + * @return + */ + fun matches(otherTag: PluginTag): Boolean { + return matchesName(otherTag) && matchesGroup(otherTag) + } + + private fun matchesGroup(otherTag: PluginTag): Boolean { + return this.group.isEmpty() || this.group == otherTag.group + } + + private fun matchesName(otherTag: PluginTag): Boolean { + return this.name == otherTag.name + } + + override fun toString(): String = listOf(group, name, version).joinToString(separator = ":") + + override fun toMeta(): Meta = buildMeta { + "name" to name + "group" to group + "version" to version + } + + companion object { + + /** + * Build new PluginTag from standard string representation + * + * @param tag + * @return + */ + fun fromString(tag: String): PluginTag { + val sepIndex = tag.indexOf(":") + return if (sepIndex >= 0) { + PluginTag(group = tag.substring(0, sepIndex), name = tag.substring(sepIndex + 1)) + } else { + PluginTag(tag) + } + } + } +} \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt new file mode 100644 index 00000000..06c36ab0 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Path.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2015 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package hep.dataforge.provider + +import hep.dataforge.names.Name +import hep.dataforge.names.toName + +/** + * + * + * Path interface. + * + * @author Alexander Nozik + * @version $Id: $Id + */ +inline class Path(val tokens: List) : Iterable { + + val head: PathToken? get() = tokens.firstOrNull() + + val length: Int get() = tokens.size + + /** + * Returns non-empty optional containing the chain without first segment in case of chain path. + * + * @return + */ + val tail: Path? get() = if (tokens.isEmpty()) null else Path(tokens.drop(1)) + + override fun iterator(): Iterator = tokens.iterator() + + companion object { + const val PATH_SEGMENT_SEPARATOR = "/" + + 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) + } + } +} + +operator fun Path.plus(path: Path) = Path(this.tokens + path.tokens) + +data class PathToken(val name: Name, val target: String? = null) { + override fun toString(): String = if (target == null) { + name.toString() + } else { + "$target$TARGET_SEPARATOR$name" + } + + companion object { + const val TARGET_SEPARATOR = "::" + fun parse(token: String): PathToken { + val target = token.substringBefore(TARGET_SEPARATOR, "") + val name = token.substringAfter(TARGET_SEPARATOR).toName() + if (target.contains("[")) TODO("target separators in queries are not supported") + return PathToken(name, target) + } + } +} + +fun PathToken.toPath() = Path(listOf(this)) diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt new file mode 100644 index 00000000..7d37d609 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/provider/Provider.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2015 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package hep.dataforge.provider + +import hep.dataforge.names.Name +import hep.dataforge.names.toName + +/** + * A marker utility interface for providers. + * + * @author Alexander Nozik + */ +interface Provider { + + /** + * Default target for this provider + * + * @return + */ + val defaultTarget: String get() = "" + + /** + * Default target for next chain segment + * + * @return + */ + val defaultChainTarget: String get() = "" + + + /** + * Provide a top level element for this [Provider] or return null if element is not present + */ + fun provideTop(target: String, name: Name): Any? + + /** + * [Sequence] of available names with given target. Only top level names are listed, no chain path. + * + * @param target + * @return + */ + fun listTop(target: String): Sequence +} + +fun Provider.provide(path: Path, targetOverride: String? = null): Any? { + if (path.length == 0) throw IllegalArgumentException("Can't provide by empty path") + val first = path.first() + val top = provideTop(targetOverride ?: first.target ?: defaultTarget, first.name) + return when (path.length) { + 1 -> top + else -> { + when (top) { + null -> null + is Provider -> top.provide(path.tail!!, targetOverride = defaultChainTarget) + else -> throw IllegalStateException("Chain path not supported: child is not a provider") + } + } + } +} + +/** + * Type checked provide + */ +inline fun Provider.provide(path: String): T? { + return provide(Path.parse(path)) as? T +} + +inline fun Provider.provide(target: String, name: String): T? { + return provide(PathToken(name.toName(), target).toPath()) as? T +} + +/** + * [Sequence] of all elements with given target + */ +fun Provider.provideAll(target: String): Sequence { + return listTop(target).map { it -> provideTop(target, it) ?: error("The element $it is declared but not provided") } +} diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt new file mode 100644 index 00000000..7564b5fb --- /dev/null +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2018 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package hep.dataforge.context + +import java.util.* +import kotlin.collections.HashMap + + +private fun Properties.asMeta(): Meta { + return buildMeta { + this@asMeta.forEach { key, value -> + set(key.toString().toName(), value) + } + } +} + +/** + * A singleton global context. Automatic root for the whole context hierarchy. Also stores the registry for active contexts. + * + * @author Alexander Nozik + */ +actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread().contextClassLoader) { + + /** + * Closing all contexts + * + * @throws Exception + */ + @Throws(Exception::class) + override fun close() { + logger.info("Shutting down GLOBAL") + for (ctx in contextRegistry.values) { + ctx.close() + } + super.close() + } + + private val contextRegistry = HashMap() + + /** + * Get previously builder context o builder a new one + * + * @param name + * @return + */ + @Synchronized + fun getContext(name: String): Context { + return contextRegistry.getOrPut(name) { JVMContext(name) } + } + + /** + * Close all contexts and terminate framework + */ + @JvmStatic + fun terminate() { + try { + close() + } catch (e: Exception) { + logger.error("Exception while terminating DataForge framework", e) + } + + } +} diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt new file mode 100644 index 00000000..448eb63e --- /dev/null +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2018 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package hep.dataforge.context + +import mu.KLogger +import mu.KotlinLogging +import java.lang.ref.WeakReference +import java.util.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import kotlin.collections.HashSet +import kotlin.reflect.KClass +import kotlin.reflect.full.cast + +/** + * The local environment for anything being done in DataForge framework. Contexts are organized into tree structure with [Global] at the top. + * Each context has a set of named [Value] properties which are taken from parent context in case they are not found in local context. + * Context implements [ValueProvider] interface and therefore could be uses as a value source for substitutions etc. + * Context contains [PluginManager] which could be used any number of configurable named plugins. + * @author Alexander Nozik + */ +open class JVMContext( + final override val name: String, + final override val parent: JVMContext? = Global, + classLoader: ClassLoader? = null, + properties: Meta = EmptyMeta +) : Context, AutoCloseable { + + private val _properties = Config().apply { update(properties) } + override val properties: Meta + get() = if (parent == null) { + _properties + } else { + Laminate(_properties, parent.properties) + } + + override val plugins: PluginManager by lazy { PluginManager(this) } + override val logger: KLogger = KotlinLogging.logger(name) + + /** + * A class loader for this context. Parent class loader is used by default + */ + open val classLoader: ClassLoader = classLoader ?: parent?.classLoader ?: Global.classLoader + + /** + * A property showing that dispatch thread is started in the context + */ + private var started = false + + /** + * A dispatch thread executor for current context + * + * @return + */ + val dispatcher: ExecutorService by lazy { + logger.info("Initializing dispatch thread executor in {}", name) + Executors.newSingleThreadExecutor { r -> + Thread(r).apply { + priority = 8 // slightly higher priority + isDaemon = true + name = this@JVMContext.name + "_dispatch" + }.also { started = true } + } + } + + private val serviceCache: MutableMap, ServiceLoader<*>> = HashMap() + + override fun services(type: KClass): Sequence { + return serviceCache.getOrPut(type.java) { ServiceLoader.load(type.java, classLoader) }.asSequence().map { type.cast(it) } + } + + /** + * Get identity for this context + * + * @return + */ + override fun toMeta(): Meta { + return buildMeta { + "parent" to parent?.name + "properties" to properties.seal() + "plugins" to plugins.map { it.toMeta() } + } + } + + /** + * Free up resources associated with this context + * + * @throws Exception + */ + override fun close() { + if (isActive) error("Can't close active context") + //detach all plugins + plugins.forEach { it.detach() } + + if (started) { + dispatcher.shutdown() + } + } + + private val activators = HashSet>() + + override val isActive: Boolean = activators.all { it.get() == null } + + override fun activate(activator: Any) { + activators.add(WeakReference(activator)) + } + + override fun deactivate(activator: Any) { + activators.removeAll { it.get() == activator } + } +} + diff --git a/dataforge-data/build.gradle b/dataforge-data/build.gradle new file mode 100644 index 00000000..512fff84 --- /dev/null +++ b/dataforge-data/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'kotlin-multiplatform' +} + +repositories { + jcenter() +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + //fromPreset(presets.js, 'js') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.iosX64, 'ios') + } + sourceSets { + commonMain { + dependencies { + api project(":dataforge-context") + } + } + } +} \ No newline at end of file diff --git a/dataforge-io/build.gradle b/dataforge-io/build.gradle new file mode 100644 index 00000000..512fff84 --- /dev/null +++ b/dataforge-io/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'kotlin-multiplatform' +} + +repositories { + jcenter() +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + //fromPreset(presets.js, 'js') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.iosX64, 'ios') + } + sourceSets { + commonMain { + dependencies { + api project(":dataforge-context") + } + } + } +} \ No newline at end of file diff --git a/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/MetaFormat.kt b/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/MetaFormat.kt index 552f9532..4f78661b 100644 --- a/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/MetaFormat.kt +++ b/dataforge-meta-io/src/commonMain/kotlin/hep/dataforge/meta/io/MetaFormat.kt @@ -1,6 +1,7 @@ package hep.dataforge.meta.io import hep.dataforge.meta.* +import hep.dataforge.values.* import kotlinx.io.core.* /** @@ -20,20 +21,12 @@ fun MetaFormat.stringify(meta: Meta): String { return builder.build().readText() } +fun Meta.asString() = JSONMetaFormat.stringify(this) + fun MetaFormat.parse(str: String): Meta{ return read(ByteReadPacket(str.toByteArray())) } -///** -// * Resolve format by its name. Null if not provided -// */ -//expect fun resolveFormat(name: String): MetaFormat? -// -///** -// * Resolve format by its binary key. Null if not provided -// */ -//expect fun resolveFormat(key: Short): MetaFormat? - internal expect fun writeJson(meta: Meta, out: Output) internal expect fun readJson(input: Input, length: Int = -1): Meta @@ -155,7 +148,7 @@ object BinaryMetaFormat : MetaFormat { (1..length).forEach { _ -> val name = readString() val item = readMetaItem() - set(name, item) + setItem(name, item) } } MetaItem.NodeItem(meta) @@ -163,6 +156,5 @@ object BinaryMetaFormat : MetaFormat { else -> error("Unknown serialization key character: $keyChar") } } - } diff --git a/dataforge-meta-io/src/commonTest/kotlin/hep/dataforge/meta/io/MetaFormatTest.kt b/dataforge-meta-io/src/commonTest/kotlin/hep/dataforge/meta/io/MetaFormatTest.kt index 1a4e9060..72851d90 100644 --- a/dataforge-meta-io/src/commonTest/kotlin/hep/dataforge/meta/io/MetaFormatTest.kt +++ b/dataforge-meta-io/src/commonTest/kotlin/hep/dataforge/meta/io/MetaFormatTest.kt @@ -19,4 +19,18 @@ class MetaFormatTest{ assertEquals(meta,result) } + @Test + fun testJsonMetaFormat(){ + val meta = buildMeta { + "a" to 22 + "node" to { + "b" to "DDD" + "c" to 11.1 + } + } + val string = JSONMetaFormat.stringify(meta) + val result = JSONMetaFormat.parse(string) + assertEquals(meta,result) + } + } \ No newline at end of file diff --git a/dataforge-meta-io/src/jsMain/kotlin/hep/dataforge/meta/io/JSMeta.kt b/dataforge-meta-io/src/jsMain/kotlin/hep/dataforge/meta/io/JSMeta.kt index 3efa97ee..e024c319 100644 --- a/dataforge-meta-io/src/jsMain/kotlin/hep/dataforge/meta/io/JSMeta.kt +++ b/dataforge-meta-io/src/jsMain/kotlin/hep/dataforge/meta/io/JSMeta.kt @@ -2,8 +2,8 @@ package hep.dataforge.meta.io import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaItem -import hep.dataforge.meta.Value import hep.dataforge.names.NameToken +import hep.dataforge.values.Value /** * Represent any js object as meta @@ -16,7 +16,7 @@ class JSMeta(val obj: Any) : Meta { private fun isList(obj: Any): Boolean = js("Array").isArray(obj) as Boolean - private fun isPrimitive(obj: Any?): Boolean = js("obj !== Object(obj)") as Boolean + private fun isPrimitive(@Suppress("UNUSED_PARAMETER") obj: Any?): Boolean = js("obj !== Object(obj)") as Boolean private fun convert(obj: Any?): MetaItem { return when (obj) { diff --git a/dataforge-meta-io/src/jvmMain/kotlin/hep/dataforge/meta/io/JSONMetaFormat.kt b/dataforge-meta-io/src/jvmMain/kotlin/hep/dataforge/meta/io/JSONMetaFormat.kt index 15314218..ae597b53 100644 --- a/dataforge-meta-io/src/jvmMain/kotlin/hep/dataforge/meta/io/JSONMetaFormat.kt +++ b/dataforge-meta-io/src/jvmMain/kotlin/hep/dataforge/meta/io/JSONMetaFormat.kt @@ -5,10 +5,12 @@ import com.github.cliftonlabs.json_simple.JsonObject import com.github.cliftonlabs.json_simple.Jsoner import hep.dataforge.meta.* import hep.dataforge.names.toName +import hep.dataforge.values.* import kotlinx.io.core.* import java.io.ByteArrayInputStream import java.io.InputStreamReader import java.io.Reader +import java.nio.ByteBuffer import java.text.ParseException internal actual fun writeJson(meta: Meta, out: Output) { @@ -31,7 +33,7 @@ private fun Value.toJson(): Any { } } -private fun Meta.toJson(): JsonObject { +fun Meta.toJson(): JsonObject { val builder = JsonObject() items.forEach { name, item -> when (item) { @@ -60,9 +62,11 @@ internal actual fun readJson(input: Input, length: Int): Meta { } override fun read(cbuf: CharArray, off: Int, len: Int): Int { - val block = input.readText(Charsets.UTF_8, len).toCharArray() - System.arraycopy(block, 0, cbuf, off, block.size) - return block.size + val buffer = ByteBuffer.allocate(len) + val res = input.readAvailable(buffer) + val chars = String(buffer.array()).toCharArray() + System.arraycopy(chars, 0, cbuf, off, chars.size) + return res } } @@ -103,13 +107,13 @@ private fun MetaBuilder.appendValue(key: String, value: Any?) { this[key] = value.toListValue() } else { val list = value.map { - when(it){ + when (it) { is JsonObject -> it.toMeta() is JsonArray -> it.toListValue().toMeta() else -> Value.of(it).toMeta() } } - setIndexed(key.toName(),list) + setIndexed(key.toName(), list) } } is Number -> this[key] = NumberValue(value) 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 1caef790..4a5d23fe 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt @@ -1,6 +1,7 @@ package hep.dataforge.meta import hep.dataforge.names.Name +import hep.dataforge.names.toName //TODO add validator to configuration @@ -20,7 +21,7 @@ open class Config : MutableMetaNode() { fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder -> this.items.mapValues { entry -> val item = entry.value - builder[entry.key] = when (item) { + builder[entry.key.toName()] = when (item) { is MetaItem.ValueItem -> MetaItem.ValueItem(item.value) is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig()) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt index bfb5eaae..f63a271b 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt @@ -1,5 +1,7 @@ package hep.dataforge.meta +import hep.dataforge.values.Null +import hep.dataforge.values.Value import kotlin.jvm.JvmName import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty 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 295f825b..5d8f13c5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt @@ -7,7 +7,17 @@ import hep.dataforge.names.NameToken * * */ -class Laminate(val layers: List) : Meta { +class Laminate(layers: List) : Meta { + + val layers: List = layers.flatMap { + if(it is Laminate){ + it.layers + } else{ + listOf(it) + } + } + + constructor(vararg layers: Meta): this(layers.asList()) override val items: Map> get() = layers.map { it.items.keys }.flatten().associateWith { key -> 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 e12251b1..12dc3e2f 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -5,7 +5,11 @@ import hep.dataforge.meta.MetaItem.NodeItem import hep.dataforge.meta.MetaItem.ValueItem import hep.dataforge.names.Name import hep.dataforge.names.NameToken +import hep.dataforge.names.plus import hep.dataforge.names.toName +import hep.dataforge.values.Value +import hep.dataforge.values.boolean + /** * A member of the meta tree. Could be represented as one of following: @@ -17,6 +21,14 @@ sealed class MetaItem { data class NodeItem(val node: M) : MetaItem() } +/** + * 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. + */ +interface MetaRepr { + fun toMeta(): Meta +} + /** * Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities: * * [MetaItem.ValueItem] (leaf) @@ -24,9 +36,11 @@ sealed class MetaItem { * * * Same name siblings are supported via elements with the same [Name] but different queries */ -interface Meta { +interface Meta : MetaRepr { val items: Map> + override fun toMeta(): Meta = this + companion object { /** * A key for single value node @@ -58,17 +72,32 @@ operator fun Meta.get(key: String): MetaItem? = get(key.toName()) /** * Get all items matching given name. */ -fun Meta.getByName(name: Name): Map> { +fun Meta.getAll(name: Name): Map> { if (name.length == 0) error("Can't use empty name for that") val (body, query) = name.last()!! val regex = query.toRegex() return (this[name.cutLast()] as? NodeItem<*>)?.node?.items - ?.filter { it.key.body == body && (query.isEmpty()|| regex.matches(it.key.query)) } + ?.filter { it.key.body == body && (query.isEmpty() || regex.matches(it.key.query)) } ?.mapKeys { it.key.query } ?: emptyMap() } +/** + * Transform meta to sequence of [Name]-[Value] pairs + */ +fun Meta.asValueSequence(): Sequence> { + return items.asSequence().flatMap { entry -> + val item = entry.value + when (item) { + is ValueItem -> sequenceOf(entry.key.toName() to item.value) + is NodeItem -> item.node.asValueSequence().map { pair -> (entry.key.toName() + pair.first) to pair.second } + } + } +} + +operator fun Meta.iterator(): Iterator> = asValueSequence().iterator() + /** * A meta node that ensures that all of its descendants has at least the same type */ 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 eb071ee7..61310d67 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt @@ -1,6 +1,8 @@ package hep.dataforge.meta import hep.dataforge.names.Name +import hep.dataforge.names.toName +import hep.dataforge.values.Value /** * DSL builder for meta. Is not intended to store mutable state @@ -36,7 +38,7 @@ fun Meta.builder(): MetaBuilder { return MetaBuilder().also { builder -> items.mapValues { entry -> val item = entry.value - builder[entry.key] = when (item) { + builder[entry.key.toName()] = when (item) { is MetaItem.ValueItem -> MetaItem.ValueItem(item.value) is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder()) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaNode.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaNode.kt index be51180e..48729efb 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaNode.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaNode.kt @@ -4,6 +4,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.plus import hep.dataforge.names.toName +import hep.dataforge.values.Value class MetaListener(val owner: Any? = null, val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit) { operator fun invoke(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) = action(name, oldItem, newItem) @@ -77,7 +78,7 @@ abstract class MutableMetaNode> : MetaNode(), MutableM override operator fun set(name: Name, item: MetaItem?) { when (name.length) { - 0 -> error("Can't set meta item for empty name") + 0 -> error("Can't setValue meta item for empty name") 1 -> { val token = name.first()!! replaceItem(token, get(name), item) @@ -98,24 +99,27 @@ abstract class MutableMetaNode> : MetaNode(), MutableM fun > M.remove(name: Name) = set(name, null) fun > M.remove(name: String) = remove(name.toName()) -operator fun > M.set(name: Name, value: Value) = set(name, MetaItem.ValueItem(value)) -operator fun > M.set(name: Name, meta: Meta) = set(name, MetaItem.NodeItem(wrap(name, meta))) -operator fun > M.set(name: String, item: MetaItem) = set(name.toName(), item) -operator fun > M.set(name: String, value: Value) = set(name.toName(), MetaItem.ValueItem(value)) -operator fun > M.set(name: String, meta: Meta) = set(name.toName(), meta) -operator fun > M.set(token: NameToken, item: MetaItem?) = set(token.toName(), item) +fun > M.setValue(name: Name, value: Value) = set(name, MetaItem.ValueItem(value)) +fun > M.setItem(name: String, item: MetaItem) = set(name.toName(), item) +fun > M.setValue(name: String, value: Value) = set(name.toName(), MetaItem.ValueItem(value)) +fun > M.setItem(token: NameToken, item: MetaItem?) = set(token.toName(), item) + +fun > M.setNode(name: Name, node: Meta) = set(name, MetaItem.NodeItem(wrap(name, node))) +fun > M.setNode(name: String, node: Meta) = setNode(name.toName(), node) /** * Universal set method */ -operator fun > M.set(key: String, value: Any?) { +operator fun > M.set(name: Name, value: Any?) { when (value) { - null -> remove(key) - is Meta -> set(key, value) - else -> set(key, Value.of(value)) + null -> remove(name) + is Meta -> setNode(name, value) + else -> setValue(name, Value.of(value)) } } +operator fun > M.set(key: String, value: Any?) = set(key.toName(), value) + /** * Update existing mutable node with another node. The rules are following: * * value replaces anything @@ -126,9 +130,9 @@ fun > M.update(meta: Meta) { meta.items.forEach { entry -> val value = entry.value when (value) { - is MetaItem.ValueItem -> this[entry.key.toName()] = value.value + is MetaItem.ValueItem -> setValue(entry.key.toName(),value.value) is MetaItem.NodeItem -> (this[entry.key.toName()] as? MetaItem.NodeItem)?.node?.update(value.node) - ?: run { this[entry.key.toName()] = value.node } + ?: run { setNode(entry.key.toName(),value.node)} } } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styleable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styleable.kt index 915ddc89..5b4af6d2 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styleable.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styleable.kt @@ -27,8 +27,8 @@ class StyledConfig(val config: Config, style: Meta = EmptyMeta) : Config() { override fun set(name: Name, item: MetaItem?) { when (item) { null -> config.remove(name) - is MetaItem.ValueItem -> config[name] = item.value - is MetaItem.NodeItem -> config[name] = item.node + is MetaItem.ValueItem -> config.setValue(name, item.value) + is MetaItem.NodeItem -> config.setNode(name, item.node) } } 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 1eae4182..57395b1b 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -6,7 +6,7 @@ package hep.dataforge.names * The name is a dot separated list of strings like `token1.token2.token3`. * Each token could contain additional query in square brackets. */ -class Name internal constructor(val tokens: List) { +inline class Name constructor(val tokens: List) { val length get() = tokens.size @@ -35,19 +35,6 @@ class Name internal constructor(val tokens: List) { override fun toString(): String = tokens.joinToString(separator = NAME_SEPARATOR) { it.toString() } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Name) return false - - if (tokens != other.tokens) return false - - return true - } - - override fun hashCode(): Int { - return tokens.hashCode() - } - companion object { const val NAME_SEPARATOR = "." } @@ -80,7 +67,7 @@ fun String.toName(): Name { var bracketCount: Int = 0 fun queryOn() = bracketCount > 0 - this@toName.asSequence().forEach { + asSequence().forEach { if (queryOn()) { when (it) { '[' -> bracketCount++ diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Value.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt similarity index 80% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Value.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt index 53c5a8f3..53cc1996 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Value.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt @@ -1,4 +1,4 @@ -package hep.dataforge.meta +package hep.dataforge.values /** @@ -44,19 +44,22 @@ interface Value { get() = listOf(this) companion object { + const val VALUE_TARGET = "value" + /** * Convert object to value */ fun of(value: Any?): Value { return when (value) { null -> Null + is Value -> value true -> True false -> False is Number -> NumberValue(value) is Iterable<*> -> ListValue(value.map { of(it) }) is Enum<*> -> EnumValue(value) is CharSequence -> StringValue(value.toString()) - else -> throw IllegalArgumentException("Unrecognized type of the object converted to Value") + else -> throw IllegalArgumentException("Unrecognized type of the object (${value::class}) converted to Value") } } } @@ -70,6 +73,8 @@ object Null : Value { 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() } /** @@ -86,6 +91,8 @@ object True : Value { override val type: ValueType get() = ValueType.BOOLEAN override val number: Number get() = 1.0 override val string: String get() = "+" + + override fun toString(): String = value.toString() } /** @@ -106,12 +113,21 @@ class NumberValue(override val number: Number) : Value { override val string: String get() = number.toString() override fun equals(other: Any?): Boolean { - return this.number == (other as? Value)?.number + if (other !is Value) return false + return when (number) { + is Short -> number == other.number.toShort() + is Long -> number == other.number.toLong() + is Byte -> number == other.number.toByte() + is Int -> number == other.number.toInt() + is Float -> number == other.number.toFloat() + is Double -> number == other.number.toDouble() + else -> number.toString() == other.number.toString() + } } override fun hashCode(): Int = number.hashCode() - + override fun toString(): String = value.toString() } class StringValue(override val string: String) : Value { @@ -123,7 +139,9 @@ class StringValue(override val string: String) : Value { return this.string == (other as? Value)?.string } - override fun hashCode(): Int = string.hashCode() + override fun hashCode(): Int = string.hashCode() + + override fun toString(): String = value.toString() } class EnumValue>(override val value: E) : Value { @@ -136,6 +154,8 @@ class EnumValue>(override val value: E) : Value { } override fun hashCode(): Int = value.hashCode() + + override fun toString(): String = value.toString() } class ListValue(override val list: List) : Value { @@ -149,6 +169,8 @@ class ListValue(override val list: List) : Value { 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 fun toString(): String = value.toString() } /** @@ -201,8 +223,8 @@ fun String.parseValue(): Value { return StringValue(this) } -class LazyParsedValue(override val string: String): Value{ - private val parsedValue by lazy { string.parseValue() } +class LazyParsedValue(override val string: String) : Value { + private val parsedValue by lazy { string.parseValue() } override val value: Any? get() = parsedValue.value @@ -210,4 +232,6 @@ class LazyParsedValue(override val string: String): Value{ get() = parsedValue.type override val number: Number get() = parsedValue.number + + override fun toString(): String = value.toString() } \ No newline at end of file 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 26e10354..8cda9003 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.values.asValue import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt new file mode 100644 index 00000000..7f83b0b2 --- /dev/null +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt @@ -0,0 +1,33 @@ +package hep.dataforge.meta + +import hep.dataforge.values.NumberValue +import hep.dataforge.values.True +import hep.dataforge.values.Value +import kotlin.test.Test +import kotlin.test.assertEquals + +class MetaTest { + @Test + fun valueEqualityTest() { + assertEquals(NumberValue(22), NumberValue(22)) + assertEquals(NumberValue(22.0), NumberValue(22)) + assertEquals(True, Value.of(true)) + } + + @Test + fun metaEqualityTest() { + val meta1 = buildMeta { + "a" to 22 + "b" to { + "c" to "ddd" + } + } + val meta2 = buildMeta { + "b" to { + "c" to "ddd" + } + "a" to 22 + }.seal() + assertEquals(meta1, meta2) + } +} \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/names/NameTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/names/NameTest.kt index 764d23c2..52fb478d 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/names/NameTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/names/NameTest.kt @@ -9,4 +9,11 @@ class NameTest{ val name = "token1.token2.token3".toName() assertEquals("token2", name[1].toString()) } + + @Test + fun equalityTest(){ + val name1 = "token1.token2[2].token3".toName() + val name2 = "token1".toName() + "token2[2].token3" + assertEquals(name1,name2) + } } \ No newline at end of file diff --git a/dataforge-tables/build.gradle b/dataforge-tables/build.gradle new file mode 100644 index 00000000..512fff84 --- /dev/null +++ b/dataforge-tables/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'kotlin-multiplatform' +} + +repositories { + jcenter() +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + //fromPreset(presets.js, 'js') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.iosX64, 'ios') + } + sourceSets { + commonMain { + dependencies { + api project(":dataforge-context") + } + } + } +} \ No newline at end of file diff --git a/dataforge-workspace/build.gradle b/dataforge-workspace/build.gradle new file mode 100644 index 00000000..512fff84 --- /dev/null +++ b/dataforge-workspace/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'kotlin-multiplatform' +} + +repositories { + jcenter() +} + +kotlin { + targets { + fromPreset(presets.jvm, 'jvm') + //fromPreset(presets.js, 'js') + // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 + // For Linux, preset should be changed to e.g. presets.linuxX64 + // For MacOS, preset should be changed to e.g. presets.macosX64 + //fromPreset(presets.iosX64, 'ios') + } + sourceSets { + commonMain { + dependencies { + api project(":dataforge-context") + } + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 71f46856..22a42916 100644 --- a/settings.gradle +++ b/settings.gradle @@ -27,4 +27,12 @@ rootProject.name = 'dataforge-core' include ":dataforge-meta" include ":dataforge-meta-io" + +include ":dataforge-context" +include ":dataforge-data" +include ":dataforge-io" + +include ":dataforge-tables" + +include ":dataforge-workspace" //include ":dataforge-envelope" \ No newline at end of file