Basic design for workspaces

This commit is contained in:
Alexander Nozik 2019-01-31 21:21:02 +03:00
parent 012ee93ab2
commit a4a7163e0d
17 changed files with 345 additions and 307 deletions

View File

@ -1,16 +1,16 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin { abstract class AbstractPlugin : Plugin {
private var _context: Context? = null private var _context: Context? = null
override val context: Context override val context: Context
get() = _context ?: error("Plugin $tag is not attached") get() = _context ?: error("Plugin $tag is not attached")
override val config = Config()
override fun attach(context: Context) { override fun attach(context: Context) {
this._context = context this._context = context
} }
@ -19,6 +19,8 @@ abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin {
this._context = null this._context = null
} }
//TODO make configuration activation-safe
override fun provideTop(target: String, name: Name): Any? = null override fun provideTop(target: String, name: Name): Any? = null
override fun listTop(target: String): Sequence<Name> = emptySequence() override fun listTop(target: String): Sequence<Name> = emptySequence()

View File

@ -4,12 +4,14 @@ import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
import hep.dataforge.provider.provideAll
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import mu.KLogger import mu.KLogger
import mu.KotlinLogging import mu.KotlinLogging
import kotlin.coroutines.CoroutineContext 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. * 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. * Since plugins could contain mutable state, context has two states: active and inactive. No changes are allowed to active context.
* @author Alexander Nozik * @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 * 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 * Context logger
*/ */
val logger: KLogger val logger: KLogger = KotlinLogging.logger(name)
/** /**
* A [PluginManager] for current context * A [PluginManager] for current context
*/ */
val plugins: PluginManager val plugins: PluginManager by lazy { PluginManager(this) }
private val activators = HashSet<Any>()
/** /**
* Defines if context is used in any kind of active computations. Active context properties and plugins could not be changed * 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 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] * Mark context as active and used by [activator]
*/ */
fun activate(activator: Any) fun activate(activator: Any) {
activators.add(activator)
}
/** /**
* Mark context unused by [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 override val coroutineContext: CoroutineContext
get() = Dispatchers.Default get() = EmptyCoroutineContext
/** /**
* Detach all plugins and terminate context * 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 { override fun toMeta(): Meta = buildMeta {
"parent" to parent?.name "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<Any> =
plugins.asSequence().flatMap { it.provideAll(target) }
@JvmName("typedMembers")
inline fun <reified T : Any> Context.members(target: String) =
members(target).filterIsInstance<T>()
/** /**
* A global root context. Closing [Global] terminates the framework. * A global root context. Closing [Global] terminates the framework.
*/ */
expect object Global : Context { object Global : Context("GLOBAL", null) {
fun getContext(name: String): Context /**
* 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<String, Context>()
/**
* Get previously builder context o builder a new one
*
* @param name
* @return
*/
fun getContext(name: String): Context {
return contextRegistry.getOrPut(name) { Context(name) }
}
} }

View File

@ -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<Plugin>()
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)
}
}
}
}

View File

@ -1,8 +1,8 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.Configurable
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr import hep.dataforge.meta.MetaRepr
import hep.dataforge.meta.Metoid
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
@ -22,7 +22,7 @@ import hep.dataforge.provider.Provider
* *
* @author Alexander Nozik * @author Alexander Nozik
*/ */
interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr { interface Plugin : Named, ContextAware, Provider, MetaRepr, Configurable {
/** /**
* Get tag for this plugin * Get tag for this plugin
@ -67,7 +67,7 @@ interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr {
"context" to context.name "context" to context.name
"type" to this::class.simpleName "type" to this::class.simpleName
"tag" to tag "tag" to tag
"meta" to meta "meta" to config
} }
companion object { companion object {

View File

@ -1,9 +1,6 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.*
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -115,8 +112,8 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
fun load(tag: PluginTag, meta: Meta = EmptyMeta): Plugin { fun load(tag: PluginTag, meta: Meta = EmptyMeta): Plugin {
val loaded = get(tag, false) val loaded = get(tag, false)
return when { return when {
loaded == null -> load(PluginRepository.fetch(tag, meta)) loaded == null -> load(PluginRepository.fetch(tag)).configure(meta)
loaded.meta == meta -> 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 tag $tag. Plugin with this tag and different configuration already exists in context.") 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<Plug
error("Corrupt type information in plugin repository") error("Corrupt type information in plugin repository")
} }
} }
loaded.meta == meta -> 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.") else -> throw RuntimeException("Can't load plugin with type $type. Plugin with this type and different configuration already exists in context.")
} }
} }

View File

@ -1,14 +1,17 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.configure
import kotlin.reflect.KClass import kotlin.reflect.KClass
interface PluginFactory { interface PluginFactory {
val tag: PluginTag val tag: PluginTag
val type: KClass<out Plugin> val type: KClass<out Plugin>
fun build(meta: Meta): Plugin fun build(): Plugin
} }
fun PluginFactory.build(meta: Meta) = build().configure(meta)
expect object PluginRepository { expect object PluginRepository {
@ -24,6 +27,22 @@ expect object PluginRepository {
/** /**
* Fetch specific plugin and instantiate it with given meta * Fetch specific plugin and instantiate it with given meta
*/ */
fun PluginRepository.fetch(tag: PluginTag, meta: Meta): Plugin = fun PluginRepository.fetch(tag: PluginTag): Plugin =
PluginRepository.list().find { it.tag.matches(tag) }?.build(meta) PluginRepository.list().find { it.tag.matches(tag) }?.build()
?: error("Plugin with tag $tag not found in the repository") ?: error("Plugin with tag $tag not found in the repository")
fun PluginRepository.register(tag: PluginTag, type: KClass<out Plugin>, constructor: () -> Plugin) {
val factory = object : PluginFactory {
override val tag: PluginTag = tag
override val type: KClass<out Plugin> = type
override fun build(): Plugin = constructor()
}
PluginRepository.register(factory)
}
inline fun <reified T : Plugin> PluginRepository.register(tag: PluginTag, noinline constructor: () -> T) =
register(tag, T::class, constructor)
fun PluginRepository.register(plugin: Plugin) = register(plugin.tag, plugin::class) { plugin }

View File

@ -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<String, Context>()
/**
* 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<Any>()
override val isActive: Boolean = !activators.isEmpty()
override fun activate(activator: Any) {
activators.add(activator)
}
override fun deactivate(activator: Any) {
activators.clear()
}
}

View File

@ -1,8 +1,5 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.Meta
import kotlin.reflect.KClass
actual object PluginRepository { actual object PluginRepository {
@ -12,20 +9,6 @@ actual object PluginRepository {
factories.add(factory) factories.add(factory)
} }
fun <T : Plugin> register(tag: PluginTag, type: KClass<out Plugin>, constructor: (Meta) -> T) {
val factory = object : PluginFactory {
override val tag: PluginTag = tag
override val type: KClass<out Plugin> = type
override fun build(meta: Meta): Plugin = constructor(meta)
}
register(factory)
}
inline fun <reified T : Plugin> register(tag: PluginTag, noinline constructor: (Meta) -> T) =
register(tag, T::class, constructor)
/** /**
* List plugins available in the repository * List plugins available in the repository
*/ */

View File

@ -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<Class<*>, ServiceLoader<*>> = HashMap()
fun <T : Any> services(type: KClass<T>): Sequence<T> {
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 <reified T : Any> 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<Class<*>, ServiceLoader<*>> = HashMap()
//
// fun <T : Any> services(type: KClass<T>): Sequence<T> {
// 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<WeakReference<Any>>()
//
// 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 }
// }
//}
//

View File

@ -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<String, Context>()
/**
* 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) }
}
}

View File

@ -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<Class<*>, ServiceLoader<*>> = HashMap()
fun <T : Any> services(type: KClass<T>): Sequence<T> {
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<WeakReference<Any>>()
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 }
}
}

View File

@ -12,6 +12,6 @@ actual object PluginRepository {
* List plugins available in the repository * List plugins available in the repository
*/ */
actual fun list(): Sequence<PluginFactory> = actual fun list(): Sequence<PluginFactory> =
factories.asSequence() + Global.services(PluginFactory::class) factories.asSequence() + Global.services()
} }

View File

@ -1,6 +1,7 @@
package hep.dataforge.provider package hep.dataforge.provider
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.context.members
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
@ -31,7 +32,5 @@ inline fun <reified T : Any> Provider.provideAllByType(): Sequence<T> {
/** /**
* A sequences of all objects provided by plugins with given target and type * A sequences of all objects provided by plugins with given target and type
*/ */
inline fun <reified T : Any> Context.components(): Sequence<T> { inline fun <reified T : Any> Context.members(): Sequence<T> = members<T>(Types[T::class])
return plugins.asSequence().flatMap { it.provideAll(Types[T::class]) }.filterIsInstance<T>()
}

View File

@ -180,3 +180,5 @@ interface Metoid {
} }
fun Value.toMeta() = buildMeta { Meta.VALUE_KEY to this } fun Value.toMeta() = buildMeta { Meta.VALUE_KEY to this }
fun Meta.isEmpty() = this === EmptyMeta || this.items.isEmpty()

View File

@ -31,12 +31,9 @@ class DataDependency(val filter: DataFilter) : Dependency() {
class TaskModelDependency(val name: String, val meta: Meta, val placement: Name = EmptyName) : Dependency() { class TaskModelDependency(val name: String, val meta: Meta, val placement: Name = EmptyName) : Dependency() {
override fun apply(workspace: Workspace): DataNode<Any> { override fun apply(workspace: Workspace): DataNode<Any> {
val model = val task = workspace.tasks[name] ?: error("Task with name ${name} is not found in the workspace")
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")
if (task.isTerminal) TODO("Support terminal task") if (task.isTerminal) TODO("Support terminal task")
val result = task.run(model) val result = with(workspace) { task(meta) }
return if (placement.isEmpty()) { return if (placement.isEmpty()) {
result result
} else { } else {

View File

@ -1,7 +1,8 @@
package hep.dataforge.workspace package hep.dataforge.workspace
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware import hep.dataforge.context.ContextAware
import hep.dataforge.context.Named import hep.dataforge.context.members
import hep.dataforge.data.Data import hep.dataforge.data.Data
import hep.dataforge.data.DataNode import hep.dataforge.data.DataNode
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
@ -12,7 +13,7 @@ import hep.dataforge.provider.Type
@Type(Workspace.TYPE) @Type(Workspace.TYPE)
interface Workspace : ContextAware, Named, Provider { interface Workspace : ContextAware, Provider {
/** /**
* The whole data node for current workspace * The whole data node for current workspace
*/ */
@ -48,8 +49,40 @@ interface Workspace : ContextAware, Named, Provider {
} }
} }
operator fun <R : Any> Task<R>.invoke(config: Meta): DataNode<R> {
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 <R : Any> Task<R>.invoke(targetName: String): DataNode<R> {
val target = targets[targetName] ?: error("A target with name $targetName not found in ${this@Workspace}")
return invoke(target)
}
companion object { companion object {
const val TYPE = "workspace" const val TYPE = "workspace"
} }
} }
class SimpleWorkspace(
override val context: Context,
override val data: DataNode<Any>,
override val targets: Map<String, Meta>,
tasks: Collection<Task<Any>>
) : Workspace {
override val tasks: Map<String, Task<*>> by lazy {
(context.members<Task<*>>(Task.TYPE) + tasks).associate { it.name to it }
}
}

View File

@ -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<Any>()
val targets = HashMap<String, Meta>()
val tasks = HashSet<Task<Any>>()
fun context(action: ContextBuilder.() -> Unit) {
this.context = ContextBuilder().apply(action).build()
}
fun data(action: DataTreeBuilder<Any>.() -> 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
)
}