Workaround for https://youtrack.jetbrains.com/issue/KT-48988. Smart building of child contexts

This commit is contained in:
Alexander Nozik 2021-10-10 14:32:32 +03:00
parent aded38254e
commit 3f54eee578
18 changed files with 72 additions and 58 deletions

View File

@ -4,7 +4,7 @@ plugins {
allprojects { allprojects {
group = "space.kscience" group = "space.kscience"
version = "0.5.1" version = "0.5.2-dev-1"
repositories{ repositories{
mavenCentral() mavenCentral()
} }

View File

@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.provider.Provider import space.kscience.dataforge.provider.Provider
@ -71,16 +72,16 @@ public open class Context internal constructor(
private val childrenContexts = HashMap<Name, Context>() private val childrenContexts = HashMap<Name, Context>()
/** /**
* Build and register a child context * Get and validate existing context or build and register a new child context.
* @param name the relative (tail) name of the new context. If null, uses context hash code as a marker.
*/ */
@OptIn(DFExperimental::class)
@Synchronized @Synchronized
public fun buildContext(name: String? = null, block: ContextBuilder.() -> Unit = {}): Context { public fun buildContext(name: Name? = null, block: ContextBuilder.() -> Unit = {}): Context {
val newContext = ContextBuilder(this) val existing = name?.let { childrenContexts[name] }
.apply { name?.let { name(it) } } return existing?.modify(block)?: ContextBuilder(this, name).apply(block).build().also {
.apply(block) childrenContexts[it.name] = it
.build() }
childrenContexts[newContext.name] = newContext
return newContext
} }
/** /**

View File

@ -20,7 +20,7 @@ import kotlin.collections.set
@DFBuilder @DFBuilder
public class ContextBuilder internal constructor( public class ContextBuilder internal constructor(
private val parent: Context, private val parent: Context,
public var name: Name? = null, public val name: Name? = null,
meta: Meta = Meta.EMPTY, meta: Meta = Meta.EMPTY,
) { ) {
internal val factories = HashMap<PluginFactory<*>, Meta>() internal val factories = HashMap<PluginFactory<*>, Meta>()
@ -30,10 +30,6 @@ public class ContextBuilder internal constructor(
meta.action() meta.action()
} }
public fun name(string: String) {
this.name = Name.parse(string)
}
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
private fun findPluginFactory(tag: PluginTag): PluginFactory<*> = private fun findPluginFactory(tag: PluginTag): PluginFactory<*> =
parent.gatherInSequence<PluginFactory<*>>(PluginFactory.TYPE).values parent.gatherInSequence<PluginFactory<*>>(PluginFactory.TYPE).values
@ -95,19 +91,21 @@ public class ContextBuilder internal constructor(
} }
/** /**
* Check if current context contains all plugins required by the builder and return it it does or forks to a new context * Check if current context contains all plugins required by the builder and return it does or forks to a new context
* if it does not. * if it does not.
*/ */
public fun Context.withEnv(block: ContextBuilder.() -> Unit): Context { @DFExperimental
public fun Context.modify(block: ContextBuilder.() -> Unit): Context {
fun Context.contains(factory: PluginFactory<*>, meta: Meta): Boolean { fun Context.contains(factory: PluginFactory<*>, meta: Meta): Boolean {
val loaded = plugins[factory.tag] ?: return false val loaded = plugins[factory.tag] ?: return false
return loaded.meta == meta return loaded.meta == meta
} }
val builder = ContextBuilder(this, name + "env", properties).apply(block) val builder = ContextBuilder(this, name + "mod", properties).apply(block)
val requiresFork = builder.factories.any { (factory, meta) -> val requiresFork = builder.factories.any { (factory, meta) ->
!contains(factory, meta) !contains(factory, meta)
} || ((properties as Meta) == builder.meta) } || ((properties as Meta) == builder.meta)
return if (requiresFork) builder.build() else this return if (requiresFork) builder.build() else this
} }

View File

@ -3,11 +3,13 @@ package space.kscience.dataforge.context
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.Name.Companion.parse
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.native.concurrent.ThreadLocal import kotlin.native.concurrent.ThreadLocal
internal expect val globalLoggerFactory: PluginFactory<out LogManager> internal expect fun getGlobalLoggerFactory(): PluginFactory<out LogManager>
/** /**
* A global root context. Closing [Global] terminates the framework. * A global root context. Closing [Global] terminates the framework.
@ -20,4 +22,4 @@ private object GlobalContext : Context("GLOBAL".asName(), null, emptySet(), Meta
public val Global: Context get() = GlobalContext public val Global: Context get() = GlobalContext
public fun Context(name: String? = null, block: ContextBuilder.() -> Unit = {}): Context = public fun Context(name: String? = null, block: ContextBuilder.() -> Unit = {}): Context =
Global.buildContext(name, block) Global.buildContext(name?.let(Name::parse), block)

View File

@ -75,7 +75,7 @@ public class DefaultLogManager : AbstractPlugin(), LogManager {
*/ */
public val Context.logger: LogManager public val Context.logger: LogManager
get() = plugins.find(inherit = true) { it is LogManager } as? LogManager get() = plugins.find(inherit = true) { it is LogManager } as? LogManager
?: globalLoggerFactory(context = Global).apply { attach(Global) } ?: getGlobalLoggerFactory()(context = Global).apply { attach(Global) }
/** /**
* The named proxy logger for a context member * The named proxy logger for a context member

View File

@ -1,6 +1,7 @@
package space.kscience.dataforge.context package space.kscience.dataforge.context
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.plus
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -84,7 +85,7 @@ public inline fun <reified T : Plugin> Context.fetch(factory: PluginFactory<T>,
val existing = plugins[factory] val existing = plugins[factory]
return if (existing != null && existing.meta == meta) existing return if (existing != null && existing.meta == meta) existing
else { else {
buildContext { buildContext(name = this@fetch.name + factory.tag.name) {
plugin(factory, meta) plugin(factory, meta)
}.plugins[factory]!! }.plugins[factory]!!
} }

View File

@ -20,8 +20,7 @@ class ContextTest {
@Test @Test
fun testPluginManager() { fun testPluginManager() {
val context = Global.buildContext { val context = Context("test") {
name("test")
plugin(DummyPlugin()) plugin(DummyPlugin())
} }
val members = context.gather<Name>("test") val members = context.gather<Name>("test")

View File

@ -29,4 +29,4 @@ public class ConsoleLogManager : AbstractPlugin(), LogManager {
} }
} }
internal actual val globalLoggerFactory: PluginFactory<out LogManager> = ConsoleLogManager internal actual fun getGlobalLoggerFactory(): PluginFactory<out LogManager> = ConsoleLogManager

View File

@ -31,4 +31,4 @@ public class SlfLogManager : AbstractPlugin(), LogManager {
} }
} }
internal actual val globalLoggerFactory: PluginFactory<out LogManager> = SlfLogManager internal actual fun getGlobalLoggerFactory(): PluginFactory<out LogManager> = SlfLogManager

View File

@ -1,4 +1,4 @@
package space.kscience.dataforge.context package space.kscience.dataforge.context
internal actual val globalLoggerFactory: PluginFactory<out LogManager> = DefaultLogManager internal actual fun getGlobalLoggerFactory(): PluginFactory<out LogManager> = DefaultLogManager

View File

@ -32,7 +32,7 @@ public interface DataSet<out T : Any> {
*/ */
public suspend fun listTop(prefix: Name = Name.EMPTY): List<Name> = public suspend fun listTop(prefix: Name = Name.EMPTY): List<Name> =
flow().map { it.name }.filter { it.startsWith(prefix) && (it.length == prefix.length + 1) }.toList() flow().map { it.name }.filter { it.startsWith(prefix) && (it.length == prefix.length + 1) }.toList()
// By default traverses the whole tree. Could be optimized in descendants // By default, traverses the whole tree. Could be optimized in descendants
public companion object { public companion object {
public val META_KEY: Name = "@meta".asName() public val META_KEY: Name = "@meta".asName()
@ -43,7 +43,7 @@ public interface DataSet<out T : Any> {
public val EMPTY: DataSet<Nothing> = object : DataSet<Nothing> { public val EMPTY: DataSet<Nothing> = object : DataSet<Nothing> {
override val dataType: KType = TYPE_OF_NOTHING override val dataType: KType = TYPE_OF_NOTHING
private val nothing: Nothing get() = error("this is nothing") //private val nothing: Nothing get() = error("this is nothing")
override fun flow(): Flow<NamedData<Nothing>> = emptyFlow() override fun flow(): Flow<NamedData<Nothing>> = emptyFlow()

View File

@ -3,6 +3,7 @@ package space.kscience.dataforge.data
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.matches import space.kscience.dataforge.names.matches
@ -26,40 +27,55 @@ private fun <R : Any> Data<*>.castOrNull(type: KType): Data<R>? =
/** /**
* Select all data matching given type and filters. Does not modify paths * Select all data matching given type and filters. Does not modify paths
*
* @param namePattern a name match patter according to [Name.matches]
* @param filter addition filtering condition based on item name and meta. By default, accepts all
*/ */
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
@PublishedApi public fun <R : Any> DataSet<*>.select(
internal fun <R : Any> DataSet<*>.select(
type: KType, type: KType,
namePattern: Name? = null, namePattern: Name? = null,
filter: (name: Name, meta: Meta) -> Boolean = { _, _ -> true }
): ActiveDataSet<R> = object : ActiveDataSet<R> { ): ActiveDataSet<R> = object : ActiveDataSet<R> {
override val dataType = type override val dataType = type
override fun flow(): Flow<NamedData<R>> = this@select.flow().filter { datum -> private fun checkDatum(name: Name, datum: Data<*>): Boolean = datum.type.isSubtypeOf(type)
datum.type.isSubtypeOf(type) && (namePattern == null || datum.name.matches(namePattern)) && (namePattern == null || name.matches(namePattern))
&& filter(name, datum.meta)
override fun flow(): Flow<NamedData<R>> = this@select.flow().filter {
checkDatum(it.name, it.data)
}.map { }.map {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
it as NamedData<R> it as NamedData<R>
} }
override suspend fun getData(name: Name): Data<R>? = this@select.getData(name)?.castOrNull(type) override suspend fun getData(name: Name): Data<R>? = this@select.getData(name)?.let { datum ->
if (checkDatum(name, datum)) datum.castOrNull(type) else null
}
override val updates: Flow<Name> = this@select.updates.filter { override val updates: Flow<Name> = this@select.updates.filter {
val datum = this@select.getData(it) val datum = this@select.getData(it) ?: return@filter false
datum?.type?.isSubtypeOf(type) ?: false checkDatum(it, datum)
} }
} }
/** /**
* Select a single datum of the appropriate type * Select a single datum of the appropriate type
*/ */
public inline fun <reified R : Any> DataSet<*>.select(namePattern: Name? = null): DataSet<R> = public inline fun <reified R : Any> DataSet<*>.select(
select(typeOf<R>(), namePattern) namePattern: Name? = null,
noinline filter: (name: Name, meta: Meta) -> Boolean = { _, _ -> true }
): DataSet<R> = select(typeOf<R>(), namePattern, filter)
/**
* Select a single datum if it is present and of given [type]
*/
public suspend fun <R : Any> DataSet<*>.selectOne(type: KType, name: Name): NamedData<R>? = public suspend fun <R : Any> DataSet<*>.selectOne(type: KType, name: Name): NamedData<R>? =
getData(name)?.castOrNull<R>(type)?.named(name) getData(name)?.castOrNull<R>(type)?.named(name)
public suspend inline fun <reified R : Any> DataSet<*>.selectOne(name: Name): NamedData<R>? = selectOne(typeOf<R>(), name) public suspend inline fun <reified R : Any> DataSet<*>.selectOne(name: Name): NamedData<R>? =
selectOne(typeOf<R>(), name)
public suspend inline fun <reified R : Any> DataSet<*>.selectOne(name: String): NamedData<R>? = public suspend inline fun <reified R : Any> DataSet<*>.selectOne(name: String): NamedData<R>? =
selectOne(typeOf<R>(), Name.parse(name)) selectOne(typeOf<R>(), Name.parse(name))

View File

@ -10,8 +10,7 @@ import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import kotlin.native.concurrent.ThreadLocal
import kotlin.reflect.KClass import kotlin.reflect.KClass
public class IOPlugin(meta: Meta) : AbstractPlugin(meta) { public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
@ -75,10 +74,11 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
} }
} }
@ThreadLocal public val Context.io: IOPlugin
internal val ioContext = Global.withEnv { get() = if (this == Global) {
name("IO") Global.buildContext("IO".asName()) {
plugin(IOPlugin) plugin(IOPlugin)
} }.fetch(IOPlugin)
} else {
public val Context.io: IOPlugin get() = (if (this == Global) ioContext else this).fetch(IOPlugin) fetch(IOPlugin)
}

View File

@ -161,7 +161,7 @@ public class TaggedEnvelopeFormat(
} }
} }
private val default by lazy { invoke(context = ioContext) } private val default by lazy { invoke() }
override fun readPartial(input: Input): PartialEnvelope = override fun readPartial(input: Input): PartialEnvelope =
default.run { readPartial(input) } default.run { readPartial(input) }

View File

@ -193,11 +193,9 @@ public class TaglessEnvelopeFormat(
override val name: Name = TAGLESS_ENVELOPE_TYPE.asName() override val name: Name = TAGLESS_ENVELOPE_TYPE.asName()
override fun invoke(meta: Meta, context: Context): EnvelopeFormat { override fun invoke(meta: Meta, context: Context): EnvelopeFormat = TaglessEnvelopeFormat(context.io, meta)
return TaglessEnvelopeFormat(context.io, meta)
}
private val default by lazy { invoke(context = ioContext) } private val default by lazy { invoke() }
override fun readPartial(input: Input): PartialEnvelope = override fun readPartial(input: Input): PartialEnvelope =
default.run { readPartial(input) } default.run { readPartial(input) }

View File

@ -15,7 +15,7 @@ class BuildersKtTest {
Workspace(Global){ Workspace(Global){
println("I am working") println("I am working")
context { name("test") } //context { name("test") }
target("testTarget") { target("testTarget") {
"a" put 12 "a" put 12
@ -28,8 +28,6 @@ class BuildersKtTest {
val script = """ val script = """
println("I am working") println("I am working")
context{ name("test") }
target("testTarget"){ target("testTarget"){
"a" put 12 "a" put 12
} }

View File

@ -14,6 +14,7 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import kotlin.properties.PropertyDelegateProvider import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
@ -62,7 +63,7 @@ public class WorkspaceBuilder(private val parentContext: Context = Global) : Tas
* Define a context for the workspace * Define a context for the workspace
*/ */
public fun context(block: ContextBuilder.() -> Unit = {}) { public fun context(block: ContextBuilder.() -> Unit = {}) {
this.context = parentContext.buildContext("workspace", block) this.context = parentContext.buildContext("workspace".asName(), block)
} }
/** /**

View File

@ -4,6 +4,6 @@ org.gradle.parallel=true
kotlin.code.style=official kotlin.code.style=official
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
kotlin.mpp.enableGranularSourceSetsMetadata=true #kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false #kotlin.native.enableDependencyPropagation=false
kotlin.mpp.stability.nowarn=true kotlin.mpp.stability.nowarn=true