Generalized Contexts for multiplatform

This commit is contained in:
Alexander Nozik 2019-01-11 07:36:02 +03:00
parent 83a56fa0cd
commit ec833dd13c
16 changed files with 242 additions and 59 deletions

View File

@ -1,8 +1,8 @@
buildscript { buildscript {
extra["kotlinVersion"] = "1.3.11" extra["kotlinVersion"] = "1.3.11"
extra["ioVersion"] = "0.1.2-dev-2" extra["ioVersion"] = "0.1.2"
extra["serializationVersion"] = "0.9.1" extra["serializationVersion"] = "0.9.1"
extra["coroutinesVersion"] = "1.0.1" extra["coroutinesVersion"] = "1.1.0"
val kotlinVersion: String by extra val kotlinVersion: String by extra
val ioVersion: String by extra val ioVersion: String by extra

View File

@ -9,7 +9,7 @@ repositories {
kotlin { kotlin {
targets { targets {
fromPreset(presets.jvm, 'jvm') fromPreset(presets.jvm, 'jvm')
//fromPreset(presets.js, 'js') fromPreset(presets.js, 'js')
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
// For Linux, preset should be changed to e.g. presets.linuxX64 // For Linux, preset should be changed to e.g. presets.linuxX64
// For MacOS, preset should be changed to e.g. presets.macosX64 // For MacOS, preset should be changed to e.g. presets.macosX64
@ -21,11 +21,19 @@ kotlin {
api project(":dataforge-meta") api project(":dataforge-meta")
api "org.jetbrains.kotlin:kotlin-reflect" api "org.jetbrains.kotlin:kotlin-reflect"
api "io.github.microutils:kotlin-logging-common:1.6.10" api "io.github.microutils:kotlin-logging-common:1.6.10"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion"
} }
} }
jvmMain{ jvmMain{
dependencies{ dependencies{
api "io.github.microutils:kotlin-logging:1.6.10" api "io.github.microutils:kotlin-logging:1.6.10"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
}
}
jsMain{
dependencies{
api "io.github.microutils:kotlin-logging-js:1.6.10"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion"
} }
} }
} }

View File

@ -0,0 +1,25 @@
package hep.dataforge.context
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin {
private var _context: Context? = null
override val context: Context
get() = _context ?: error("Plugin $tag is not attached")
override fun attach(context: Context) {
this._context = context
}
override fun detach() {
this._context = null
}
override fun provideTop(target: String, name: Name): Any? = null
override fun listTop(target: String): Sequence<Name> = emptySequence()
}

View File

@ -6,8 +6,11 @@ import hep.dataforge.names.toName
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
import hep.dataforge.provider.provideAll import hep.dataforge.provider.provideAll
import hep.dataforge.values.Value import hep.dataforge.values.Value
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.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -22,12 +25,12 @@ import kotlin.reflect.KClass
* 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 { interface Context : Named, MetaRepr, Provider, CoroutineScope {
val parent: Context? val parent: Context?
/** /**
* Context properties. Working as substitutes for environment variables * Context properties. Working as substitute for environment variables
*/ */
val properties: Meta val properties: Meta
@ -46,11 +49,6 @@ interface Context : Named, MetaRepr, Provider {
*/ */
val isActive: Boolean val isActive: Boolean
/**
* Provide services for given type
*/
fun <T : Any> services(type: KClass<T>): Sequence<T>
override val defaultTarget: String get() = Plugin.PLUGIN_TARGET override val defaultTarget: String get() = Plugin.PLUGIN_TARGET
override fun provideTop(target: String, name: Name): Any? { override fun provideTop(target: String, name: Name): Any? {
@ -79,10 +77,19 @@ interface Context : Named, MetaRepr, Provider {
*/ */
fun deactivate(activator: Any) fun deactivate(activator: Any)
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default
/** /**
* Detach all plugins and terminate context * Detach all plugins and terminate context
*/ */
fun close() fun close()
override fun toMeta(): Meta = buildMeta {
"parent" to parent?.name
"properties" to properties.seal()
"plugins" to plugins.map { it.toMeta() }
}
} }
/** /**
@ -95,7 +102,11 @@ inline fun <reified T : Any> Context.list(target: String): Sequence<T> {
/** /**
* A global root context * A global root context
*/ */
expect object Global : Context expect object Global : Context{
fun getContext(name: String): Context
}
/** /**
* The interface for something that encapsulated in context * The interface for something that encapsulated in context

View File

@ -46,7 +46,7 @@ interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr {
* *
* @return * @return
*/ */
fun dependsOn(): List<PluginTag> fun dependsOn(): List<PluginTag> = emptyList()
/** /**
* Start this plugin and attach registration info to the context. This method * Start this plugin and attach registration info to the context. This method

View File

@ -10,16 +10,17 @@ interface PluginFactory {
} }
object PluginRepository { expect object PluginRepository {
/** /**
* List plugins available in the repository * List plugins available in the repository
*/ */
fun list(): Sequence<PluginFactory> = Global.services(PluginFactory::class) fun list(): Sequence<PluginFactory>
/** }
* 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) * Fetch specific plugin and instantiate it with given meta
?: error("Plugin with tag $tag not found in the repository") */
} 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")

View File

@ -0,0 +1,83 @@
package hep.dataforge.context
import hep.dataforge.meta.*
import mu.KLogger
import mu.KotlinLogging
import kotlin.jvm.Synchronized
import kotlin.reflect.KClass
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)
/**
* A property showing that dispatch thread is started in the context
*/
private var started = false
override fun <T : Any> services(type: KClass<T>): Sequence<T> = TODO("Not implemented")
/**
* 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

@ -0,0 +1,11 @@
package hep.dataforge.context
actual object PluginRepository {
/**
* List plugins available in the repository
*/
actual fun list(): Sequence<PluginFactory> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -42,7 +42,6 @@ actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread(
* *
* @throws Exception * @throws Exception
*/ */
@Throws(Exception::class)
override fun close() { override fun close() {
logger.info("Shutting down GLOBAL") logger.info("Shutting down GLOBAL")
for (ctx in contextRegistry.values) { for (ctx in contextRegistry.values) {
@ -60,20 +59,7 @@ actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread(
* @return * @return
*/ */
@Synchronized @Synchronized
fun getContext(name: String): Context { actual fun getContext(name: String): Context {
return contextRegistry.getOrPut(name) { JVMContext(name) } 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)
}
}
} }

View File

@ -16,6 +16,7 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.* import hep.dataforge.meta.*
import kotlinx.coroutines.Dispatchers
import mu.KLogger import mu.KLogger
import mu.KotlinLogging import mu.KotlinLogging
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -23,6 +24,7 @@ import java.util.*
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.collections.HashSet import kotlin.collections.HashSet
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.cast import kotlin.reflect.full.cast
@ -72,23 +74,10 @@ open class JVMContext(
private val serviceCache: MutableMap<Class<*>, ServiceLoader<*>> = HashMap() private val serviceCache: MutableMap<Class<*>, ServiceLoader<*>> = HashMap()
override fun <T : Any> services(type: KClass<T>): Sequence<T> { fun <T : Any> services(type: KClass<T>): Sequence<T> {
return serviceCache.getOrPut(type.java) { ServiceLoader.load(type.java, classLoader) }.asSequence().map { type.cast(it) } 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 * Free up resources associated with this context
* *

View File

@ -0,0 +1,9 @@
package hep.dataforge.context
actual object PluginRepository {
/**
* List plugins available in the repository
*/
actual fun list(): Sequence<PluginFactory> = Global.services(PluginFactory::class)
}

View File

@ -9,7 +9,7 @@ repositories {
kotlin { kotlin {
targets { targets {
fromPreset(presets.jvm, 'jvm') fromPreset(presets.jvm, 'jvm')
fromPreset(presets.js, 'js') //fromPreset(presets.js, 'js')
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
// For Linux, preset should be changed to e.g. presets.linuxX64 // For Linux, preset should be changed to e.g. presets.linuxX64
// For MacOS, preset should be changed to e.g. presets.macosX64 // For MacOS, preset should be changed to e.g. presets.macosX64
@ -28,11 +28,5 @@ kotlin {
} }
} }
jsMain {
dependencies {
}
}
} }
} }

View File

@ -1,12 +1,27 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware import hep.dataforge.context.ContextAware
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.reflect.KClass
/** /**
* A generic way to render any object in the output. * A generic way to render any object in the output.
*
* An object could be rendered either in append or overlay mode. The mode is decided by the [Output]
* based on its configuration and provided meta
*
*/ */
interface Output<in T: Any> : ContextAware { interface Output<in T : Any> : ContextAware {
/**
* Render specific object with configuration.
*
* By convention actual render is called in asynchronous mode, so this method should never
* block execution
*/
fun render(obj: T, meta: Meta = EmptyMeta) fun render(obj: T, meta: Meta = EmptyMeta)
} }

View File

@ -0,0 +1,33 @@
package hep.dataforge.io
import hep.dataforge.context.Plugin
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name
import kotlin.reflect.KClass
/**
* A manager for outputs
*/
interface OutputManager : Plugin {
/**
* Provide an output for given name and stage.
*
* @param stage represents the node or directory for the output. Empty means root node.
* @param name represents the name inside the node.
* @param meta configuration for [Output] (not for rendered object)
*
*/
operator fun get(name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<Any>
/**
* Get an output specialized for giver ntype
*/
fun <T : Any> typed(type: KClass<T>, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<T>
}
inline fun <reified T : Any> OutputManager.typed(name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<T> {
return typed(T::class, name, stage, meta)
}

View File

@ -0,0 +1,16 @@
package hep.dataforge.io
import hep.dataforge.context.Context
import hep.dataforge.meta.Meta
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class TextOutput(override val context: Context, private val output: kotlinx.io.core.Output) : Output<Any> {
override fun render(obj: Any, meta: Meta) {
context.launch(Dispatchers.IO) {
output.append(obj.toString())
output.append('\n')
}
}
}

View File

@ -101,4 +101,6 @@ operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
operator fun Name.plus(other: String): Name = this + other.toName() operator fun Name.plus(other: String): Name = this + other.toName()
fun NameToken.toName() = Name(listOf(this)) fun NameToken.toName() = Name(listOf(this))
val EmptyName = Name(emptyList())