From a4a7163e0d38894d69b383588d6d3fe938e03613 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 31 Jan 2019 21:21:02 +0300 Subject: [PATCH] Basic design for workspaces --- .../hep/dataforge/context/AbstractPlugin.kt | 10 +- .../kotlin/hep/dataforge/context/Context.kt | 84 +++++++++++-- .../hep/dataforge/context/ContextBuilder.kt | 37 ++++++ .../kotlin/hep/dataforge/context/Plugin.kt | 6 +- .../hep/dataforge/context/PluginManager.kt | 11 +- .../hep/dataforge/context/PluginRepository.kt | 27 +++- .../kotlin/hep/dataforge/context/JSContext.kt | 75 ----------- .../hep/dataforge/context/PluginRepository.kt | 17 --- .../dataforge/context/ClassLoaderPlugin.kt | 118 ++++++++++++++++++ .../kotlin/hep/dataforge/context/Global.kt | 65 ---------- .../hep/dataforge/context/JVMContext.kt | 107 ---------------- .../hep/dataforge/context/PluginRepository.kt | 2 +- .../kotlin/hep/dataforge/provider/Types.kt | 5 +- .../kotlin/hep/dataforge/meta/Meta.kt | 4 +- .../hep/dataforge/workspace/Dependency.kt | 7 +- .../hep/dataforge/workspace/Workspace.kt | 37 +++++- .../dataforge/workspace/WorkspaceBuilder.kt | 40 ++++++ 17 files changed, 345 insertions(+), 307 deletions(-) create mode 100644 dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt delete mode 100644 dataforge-context/src/jsMain/kotlin/hep/dataforge/context/JSContext.kt create mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/ClassLoaderPlugin.kt delete mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt delete mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt create mode 100644 dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt 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 ac726c1f..8864fae1 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/AbstractPlugin.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/AbstractPlugin.kt @@ -1,16 +1,16 @@ package hep.dataforge.context -import hep.dataforge.meta.EmptyMeta -import hep.dataforge.meta.Meta +import hep.dataforge.meta.Config import hep.dataforge.names.Name -abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin { - +abstract class AbstractPlugin : Plugin { private var _context: Context? = null override val context: Context get() = _context ?: error("Plugin $tag is not attached") + override val config = Config() + override fun attach(context: Context) { this._context = context } @@ -19,6 +19,8 @@ abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin { this._context = null } + //TODO make configuration activation-safe + override fun provideTop(target: String, name: Name): Any? = null override fun listTop(target: String): Sequence = emptySequence() 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 63483f91..d1ff801d 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt @@ -4,12 +4,14 @@ 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 kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import mu.KLogger import mu.KotlinLogging import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.jvm.JvmName /** * The local environment for anything being done in DataForge framework. Contexts are organized into tree structure with [Global] at the top. @@ -23,29 +25,36 @@ import kotlin.coroutines.CoroutineContext * Since plugins could contain mutable state, context has two states: active and inactive. No changes are allowed to active context. * @author Alexander Nozik */ -interface Context : Named, MetaRepr, Provider, CoroutineScope { +open class Context(final override val name: String, val parent: Context? = Global) : Named, MetaRepr, Provider, + CoroutineScope { - val parent: Context? + private val config = Config() /** * Context properties. Working as substitute for environment variables */ - val properties: Meta + val properties: Meta = if (parent == null) { + config + } else { + Laminate(config, parent.properties) + } /** * Context logger */ - val logger: KLogger + val logger: KLogger = KotlinLogging.logger(name) /** * A [PluginManager] for current context */ - val plugins: PluginManager + val plugins: PluginManager by lazy { PluginManager(this) } + + private val activators = HashSet() /** * Defines if context is used in any kind of active computations. Active context properties and plugins could not be changed */ - val isActive: Boolean + val isActive: Boolean = activators.isNotEmpty() override val defaultTarget: String get() = Plugin.PLUGIN_TARGET @@ -68,20 +77,36 @@ interface Context : Named, MetaRepr, Provider, CoroutineScope { /** * Mark context as active and used by [activator] */ - fun activate(activator: Any) + fun activate(activator: Any) { + activators.add(activator) + } /** * Mark context unused by [activator] */ - fun deactivate(activator: Any) + fun deactivate(activator: Any) { + activators.remove(activator) + } + + /** + * Change the properties of the context. If active, throw an exception + */ + fun configure(action: Config.() -> Unit) { + if (isActive) error("Can't configure active context") + config.action() + } override val coroutineContext: CoroutineContext - get() = Dispatchers.Default + get() = EmptyCoroutineContext /** * Detach all plugins and terminate context */ - fun close() + open fun close() { + if (isActive) error("Can't close active context") + //detach all plugins + plugins.forEach { it.detach() } + } override fun toMeta(): Meta = buildMeta { "parent" to parent?.name @@ -90,12 +115,45 @@ interface Context : Named, MetaRepr, Provider, CoroutineScope { } } +/** + * A sequences of all objects provided by plugins with given target and type + */ +fun Context.members(target: String): Sequence = + plugins.asSequence().flatMap { it.provideAll(target) } + +@JvmName("typedMembers") +inline fun Context.members(target: String) = + members(target).filterIsInstance() + /** * A global root context. Closing [Global] terminates the framework. */ -expect object Global : Context { - fun getContext(name: String): Context +object Global : Context("GLOBAL", null) { + /** + * Closing all contexts + * + * @throws Exception + */ + 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 + */ + fun getContext(name: String): Context { + return contextRegistry.getOrPut(name) { Context(name) } + } } diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt new file mode 100644 index 00000000..52079854 --- /dev/null +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt @@ -0,0 +1,37 @@ +package hep.dataforge.context + +import hep.dataforge.meta.Config +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.configure + +/** + * A convenience builder for context + */ +class ContextBuilder(var name: String = "@anonimous", val parent: Context = Global) { + private val plugins = ArrayList() + private var meta = MetaBuilder() + + fun properties(action: MetaBuilder.() -> Unit) { + meta.action() + } + + fun plugin(plugin: Plugin) { + plugins.add(plugin) + } + + fun plugin(tag: PluginTag, action: Config.() -> Unit) { + plugins.add(PluginRepository.fetch(tag).configure(action)) + } + + fun plugin(name: String, group: String = "", version: String = "", action: Config.() -> Unit) { + plugin(PluginTag(name, group, version), action) + } + + fun build(): Context { + return Context(name, parent).apply { + this@ContextBuilder.plugins.forEach { + plugins.load(it) + } + } + } +} \ 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 780855a6..1de7f6ca 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt @@ -1,8 +1,8 @@ package hep.dataforge.context +import hep.dataforge.meta.Configurable 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 @@ -22,7 +22,7 @@ import hep.dataforge.provider.Provider * * @author Alexander Nozik */ -interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr { +interface Plugin : Named, ContextAware, Provider, MetaRepr, Configurable { /** * Get tag for this plugin @@ -67,7 +67,7 @@ interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr { "context" to context.name "type" to this::class.simpleName "tag" to tag - "meta" to meta + "meta" to config } companion object { 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 73b42bd4..1f905093 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt @@ -1,9 +1,6 @@ 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 hep.dataforge.meta.* import kotlin.reflect.KClass /** @@ -115,8 +112,8 @@ class PluginManager(override val context: Context) : ContextAware, Iterable load(PluginRepository.fetch(tag, meta)) - loaded.meta == meta -> loaded // if meta is the same, return existing plugin + loaded == null -> load(PluginRepository.fetch(tag)).configure(meta) + loaded.config == 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.") } } @@ -137,7 +134,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable loaded // if meta is the same, return existing plugin + loaded.config == 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.") } } diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt index 60cdc3f0..43e51a07 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginRepository.kt @@ -1,14 +1,17 @@ package hep.dataforge.context import hep.dataforge.meta.Meta +import hep.dataforge.meta.configure import kotlin.reflect.KClass interface PluginFactory { val tag: PluginTag val type: KClass - fun build(meta: Meta): Plugin + fun build(): Plugin } +fun PluginFactory.build(meta: Meta) = build().configure(meta) + expect object PluginRepository { @@ -24,6 +27,22 @@ expect object PluginRepository { /** * Fetch specific plugin and instantiate it with given meta */ -fun PluginRepository.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 +fun PluginRepository.fetch(tag: PluginTag): Plugin = + PluginRepository.list().find { it.tag.matches(tag) }?.build() + ?: error("Plugin with tag $tag not found in the repository") + +fun PluginRepository.register(tag: PluginTag, type: KClass, constructor: () -> Plugin) { + val factory = object : PluginFactory { + override val tag: PluginTag = tag + override val type: KClass = type + + override fun build(): Plugin = constructor() + + } + PluginRepository.register(factory) +} + +inline fun PluginRepository.register(tag: PluginTag, noinline constructor: () -> T) = + register(tag, T::class, constructor) + +fun PluginRepository.register(plugin: Plugin) = register(plugin.tag, plugin::class) { plugin } \ No newline at end of file diff --git a/dataforge-context/src/jsMain/kotlin/hep/dataforge/context/JSContext.kt b/dataforge-context/src/jsMain/kotlin/hep/dataforge/context/JSContext.kt deleted file mode 100644 index 908f4f16..00000000 --- a/dataforge-context/src/jsMain/kotlin/hep/dataforge/context/JSContext.kt +++ /dev/null @@ -1,75 +0,0 @@ -package hep.dataforge.context - -import hep.dataforge.meta.* -import mu.KLogger -import mu.KotlinLogging -import kotlin.jvm.Synchronized - -actual object Global : Context, JSContext("GLOBAL", null) { - /** - * Closing all contexts - * - * @throws Exception - */ - 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 - actual fun getContext(name: String): Context { - return contextRegistry.getOrPut(name) { JSContext(name) } - } -} - -open class JSContext( - final override val name: String, - final override val parent: JSContext? = Global, - properties: Meta = EmptyMeta -) : Context { - - 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) - - /** - * 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() } - } - - private val activators = HashSet() - - override val isActive: Boolean = !activators.isEmpty() - - override fun activate(activator: Any) { - activators.add(activator) - } - - override fun deactivate(activator: Any) { - activators.clear() - } -} \ No newline at end of file diff --git a/dataforge-context/src/jsMain/kotlin/hep/dataforge/context/PluginRepository.kt b/dataforge-context/src/jsMain/kotlin/hep/dataforge/context/PluginRepository.kt index 830cd233..4cbeb0d1 100644 --- a/dataforge-context/src/jsMain/kotlin/hep/dataforge/context/PluginRepository.kt +++ b/dataforge-context/src/jsMain/kotlin/hep/dataforge/context/PluginRepository.kt @@ -1,8 +1,5 @@ package hep.dataforge.context -import hep.dataforge.meta.Meta -import kotlin.reflect.KClass - actual object PluginRepository { @@ -12,20 +9,6 @@ actual object PluginRepository { factories.add(factory) } - fun register(tag: PluginTag, type: KClass, constructor: (Meta) -> T) { - val factory = object : PluginFactory { - override val tag: PluginTag = tag - override val type: KClass = type - - override fun build(meta: Meta): Plugin = constructor(meta) - - } - register(factory) - } - - inline fun register(tag: PluginTag, noinline constructor: (Meta) -> T) = - register(tag, T::class, constructor) - /** * List plugins available in the repository */ diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/ClassLoaderPlugin.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/ClassLoaderPlugin.kt new file mode 100644 index 00000000..6d401613 --- /dev/null +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/ClassLoaderPlugin.kt @@ -0,0 +1,118 @@ +/* + * 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.reflect.KClass +import kotlin.reflect.full.cast + +class ClassLoaderPlugin(val classLoader: ClassLoader) : AbstractPlugin() { + override val tag: PluginTag = PluginTag("classLoader", PluginTag.DATAFORGE_GROUP) + + private val serviceCache: MutableMap, ServiceLoader<*>> = HashMap() + + fun services(type: KClass): Sequence { + return serviceCache.getOrPut(type.java) { ServiceLoader.load(type.java, classLoader) }.asSequence() + .map { type.cast(it) } + } + + companion object { + val DEFAULT = ClassLoaderPlugin(Global::class.java.classLoader) + } +} + +val Context.classLoaderPlugin get() = this.plugins.get() ?: ClassLoaderPlugin.DEFAULT + +inline fun Context.services() = classLoaderPlugin.services(T::class) + + +//open class JVMContext( +// final override val name: String, +// final override val parent: JVMContext? = Global, +// classLoader: ClassLoader? = null, +// properties: Meta = EmptyMeta +//) : Context, AutoCloseable { +// +// override val properties: Meta = 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() +// +// fun services(type: KClass): Sequence { +// return serviceCache.getOrPut(type.java) { ServiceLoader.load(type.java, classLoader) }.asSequence() +// .map { type.cast(it) } +// } +// +// /** +// * 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-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt deleted file mode 100644 index 4c987afc..00000000 --- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/Global.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 Alexander Nozik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package hep.dataforge.context - -import hep.dataforge.meta.Meta -import hep.dataforge.meta.buildMeta -import hep.dataforge.names.toName -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 - */ - 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 - actual fun getContext(name: String): Context { - return contextRegistry.getOrPut(name) { JVMContext(name) } - } -} diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt deleted file mode 100644 index 223c7d67..00000000 --- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/JVMContext.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2018 Alexander Nozik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package hep.dataforge.context - -import hep.dataforge.meta.* -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 - -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() - - fun services(type: KClass): Sequence { - return serviceCache.getOrPut(type.java) { ServiceLoader.load(type.java, classLoader) }.asSequence() - .map { type.cast(it) } - } - - /** - * 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-context/src/jvmMain/kotlin/hep/dataforge/context/PluginRepository.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/PluginRepository.kt index e6008c34..05b65ad9 100644 --- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/PluginRepository.kt +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/context/PluginRepository.kt @@ -12,6 +12,6 @@ actual object PluginRepository { * List plugins available in the repository */ actual fun list(): Sequence = - factories.asSequence() + Global.services(PluginFactory::class) + factories.asSequence() + Global.services() } \ No newline at end of file diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/Types.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/Types.kt index df283776..15cfe2d1 100644 --- a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/Types.kt +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/provider/Types.kt @@ -1,6 +1,7 @@ package hep.dataforge.provider import hep.dataforge.context.Context +import hep.dataforge.context.members import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation @@ -31,7 +32,5 @@ inline fun Provider.provideAllByType(): Sequence { /** * A sequences of all objects provided by plugins with given target and type */ -inline fun Context.components(): Sequence { - return plugins.asSequence().flatMap { it.provideAll(Types[T::class]) }.filterIsInstance() -} +inline fun Context.members(): Sequence = members(Types[T::class]) 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 bcc777c1..6d00d319 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -179,4 +179,6 @@ interface Metoid { val meta: Meta } -fun Value.toMeta() = buildMeta { Meta.VALUE_KEY to this } \ No newline at end of file +fun Value.toMeta() = buildMeta { Meta.VALUE_KEY to this } + +fun Meta.isEmpty() = this === EmptyMeta || this.items.isEmpty() \ 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 index 0fedc3e9..1f0f64f4 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt @@ -31,12 +31,9 @@ class DataDependency(val filter: DataFilter) : Dependency() { class TaskModelDependency(val name: String, val meta: Meta, val placement: Name = EmptyName) : Dependency() { override fun apply(workspace: Workspace): DataNode { - val model = - workspace.tasks[name]?.build(workspace, meta) ?: error("Task with name $name not found in $workspace") - - val task = workspace.tasks[model.name] ?: error("Task with name ${model.name} is not found in the workspace") + val task = workspace.tasks[name] ?: error("Task with name ${name} is not found in the workspace") if (task.isTerminal) TODO("Support terminal task") - val result = task.run(model) + val result = with(workspace) { task(meta) } return if (placement.isEmpty()) { result } else { 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 a7fbfea7..b0b1cf4d 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt @@ -1,7 +1,8 @@ package hep.dataforge.workspace +import hep.dataforge.context.Context import hep.dataforge.context.ContextAware -import hep.dataforge.context.Named +import hep.dataforge.context.members import hep.dataforge.data.Data import hep.dataforge.data.DataNode import hep.dataforge.meta.Meta @@ -12,7 +13,7 @@ import hep.dataforge.provider.Type @Type(Workspace.TYPE) -interface Workspace : ContextAware, Named, Provider { +interface Workspace : ContextAware, Provider { /** * The whole data node for current workspace */ @@ -48,8 +49,40 @@ interface Workspace : ContextAware, Named, Provider { } } + operator fun Task.invoke(config: Meta): DataNode { + context.activate(this) + try { + val model = build(this@Workspace, config) + validate(model) + return run(model) + } finally { + context.deactivate(this) + } + } + + /** + * Invoke a task in the workspace utilizing caching if possible + */ + operator fun Task.invoke(targetName: String): DataNode { + val target = targets[targetName] ?: error("A target with name $targetName not found in ${this@Workspace}") + return invoke(target) + } + companion object { const val TYPE = "workspace" } } +class SimpleWorkspace( + override val context: Context, + override val data: DataNode, + override val targets: Map, + tasks: Collection> +) : Workspace { + + override val tasks: Map> by lazy { + (context.members>(Task.TYPE) + tasks).associate { it.name to it } + } + +} + diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt new file mode 100644 index 00000000..9aebdcd0 --- /dev/null +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt @@ -0,0 +1,40 @@ +package hep.dataforge.workspace + +import hep.dataforge.context.Context +import hep.dataforge.context.ContextBuilder +import hep.dataforge.data.DataTreeBuilder +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.buildMeta + +/** + * A builder for a workspace + */ +class WorkspaceBuilder(var context: Context) { + val data = DataTreeBuilder() + val targets = HashMap() + val tasks = HashSet>() + + fun context(action: ContextBuilder.() -> Unit) { + this.context = ContextBuilder().apply(action).build() + } + + fun data(action: DataTreeBuilder.() -> Unit) = data.apply(action) + + fun target(name: String, meta: Meta) { + targets[name] = meta + } + + fun target(name: String, action: MetaBuilder.() -> Unit) = target(name, buildMeta(action)) + + fun task(task: Task<*>) { + tasks.add(task) + } + + fun build(): Workspace = SimpleWorkspace( + context, + data.build(), + targets, + tasks + ) +} \ No newline at end of file