Immutable plugin manager
This commit is contained in:
parent
474597777c
commit
73b3bbe7fc
@ -5,14 +5,17 @@
|
|||||||
- Experimental `listOfSpec` delegate.
|
- Experimental `listOfSpec` delegate.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- **API breaking** Config is deprecated, use `ObservableMeta` instead.
|
||||||
- **API breaking** Descriptor no has a member property `defaultValue` instead of `defaultItem()` extension. It caches default value state on the first call. It is done because computing default on each call is too expensive.
|
- **API breaking** Descriptor no has a member property `defaultValue` instead of `defaultItem()` extension. It caches default value state on the first call. It is done because computing default on each call is too expensive.
|
||||||
- Kotlin 1.5.10
|
- Kotlin 1.5.10
|
||||||
- Build tools 0.10.0
|
- Build tools 0.10.0
|
||||||
- Relaxed type restriction on `MetaConverter`. Now nullables are available.
|
- Relaxed type restriction on `MetaConverter`. Now nullables are available.
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
- Direct use of `Config`
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
- Public PluginManager mutability
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Proper json array index treatment
|
- Proper json array index treatment
|
||||||
|
@ -4,7 +4,7 @@ plugins {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.4.4-dev-1"
|
version = "0.5.0-dev-1"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
@ -26,6 +26,7 @@ import kotlin.jvm.Synchronized
|
|||||||
public open class Context internal constructor(
|
public open class Context internal constructor(
|
||||||
final override val name: Name,
|
final override val name: Name,
|
||||||
public val parent: Context?,
|
public val parent: Context?,
|
||||||
|
plugins: Set<Plugin>,
|
||||||
meta: Meta,
|
meta: Meta,
|
||||||
) : Named, MetaRepr, Provider, CoroutineScope {
|
) : Named, MetaRepr, Provider, CoroutineScope {
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ public open class Context internal constructor(
|
|||||||
/**
|
/**
|
||||||
* A [PluginManager] for current context
|
* A [PluginManager] for current context
|
||||||
*/
|
*/
|
||||||
public val plugins: PluginManager by lazy { PluginManager(this) }
|
public val plugins: PluginManager by lazy { PluginManager(this, plugins) }
|
||||||
|
|
||||||
override val defaultTarget: String get() = Plugin.TARGET
|
override val defaultTarget: String get() = Plugin.TARGET
|
||||||
|
|
||||||
|
@ -64,12 +64,32 @@ public class ContextBuilder internal constructor(
|
|||||||
|
|
||||||
public fun build(): Context {
|
public fun build(): Context {
|
||||||
val contextName = name ?: "@auto[${hashCode().toUInt().toString(16)}]".toName()
|
val contextName = name ?: "@auto[${hashCode().toUInt().toString(16)}]".toName()
|
||||||
return Context(contextName, parent, meta.seal()).apply {
|
val plugins = HashMap<PluginTag, Plugin>()
|
||||||
|
|
||||||
|
fun addPlugin(factory: PluginFactory<*>, meta: Meta) {
|
||||||
|
val existing = plugins[factory.tag]
|
||||||
|
// Add if does not exist
|
||||||
|
if (existing == null) {
|
||||||
|
//TODO bypass if parent already has plugin with given meta?
|
||||||
|
val plugin = factory(meta, parent)
|
||||||
|
|
||||||
|
for ((depFactory, deoMeta) in plugin.dependsOn()) {
|
||||||
|
addPlugin(depFactory, deoMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.logger.info { "Loading plugin ${plugin.name} into $contextName" }
|
||||||
|
plugins[plugin.tag] = plugin
|
||||||
|
} else if (existing.meta != meta) {
|
||||||
|
error("Plugin with tag ${factory.tag} and meta $meta already exists in $contextName")
|
||||||
|
}
|
||||||
|
//bypass if exists with the same meta
|
||||||
|
}
|
||||||
|
|
||||||
factories.forEach { (factory, meta) ->
|
factories.forEach { (factory, meta) ->
|
||||||
@Suppress("DEPRECATION")
|
addPlugin(factory, meta)
|
||||||
plugins.fetch(factory, meta)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Context(contextName, parent, plugins.values.toSet(), meta.seal())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ internal expect val globalLoggerFactory: PluginFactory<out LogManager>
|
|||||||
* A global root context. Closing [Global] terminates the framework.
|
* A global root context. Closing [Global] terminates the framework.
|
||||||
*/
|
*/
|
||||||
@ThreadLocal
|
@ThreadLocal
|
||||||
private object GlobalContext : Context("GLOBAL".asName(), null, Meta.EMPTY) {
|
private object GlobalContext : Context("GLOBAL".asName(), null, emptySet(), Meta.EMPTY) {
|
||||||
override val coroutineContext: CoroutineContext = Job() + CoroutineName("GlobalContext")
|
override val coroutineContext: CoroutineContext = Job() + CoroutineName("GlobalContext")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
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.meta.MetaBuilder
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
|
||||||
@ -11,12 +10,10 @@ import kotlin.reflect.KClass
|
|||||||
* @property context A context for this plugin manager
|
* @property context A context for this plugin manager
|
||||||
* @author Alexander Nozik
|
* @author Alexander Nozik
|
||||||
*/
|
*/
|
||||||
public class PluginManager(override val context: Context) : ContextAware, Iterable<Plugin> {
|
public class PluginManager internal constructor(
|
||||||
|
override val context: Context,
|
||||||
/**
|
private val plugins: Set<Plugin>
|
||||||
* A set of loaded plugins
|
) : ContextAware, Iterable<Plugin> {
|
||||||
*/
|
|
||||||
private val plugins: HashSet<Plugin> = HashSet()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
plugins.forEach { it.attach(context) }
|
plugins.forEach { it.attach(context) }
|
||||||
@ -76,64 +73,7 @@ public class PluginManager(override val context: Context) : ContextAware, Iterab
|
|||||||
public inline operator fun <reified T : Plugin> get(factory: PluginFactory<T>, recursive: Boolean = true): T? =
|
public inline operator fun <reified T : Plugin> get(factory: PluginFactory<T>, recursive: Boolean = true): T? =
|
||||||
get(factory.type, factory.tag, recursive)
|
get(factory.type, factory.tag, recursive)
|
||||||
|
|
||||||
/**
|
|
||||||
* Load given plugin into this manager and return loaded instance.
|
|
||||||
* Throw error if plugin of the same type and tag already exists in manager.
|
|
||||||
*
|
|
||||||
* @param plugin
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private fun <T : Plugin> load(plugin: T): T {
|
|
||||||
if (get(plugin::class, plugin.tag, recursive = false) != null) {
|
|
||||||
error("Plugin with tag ${plugin.tag} already exists in ${context.name}")
|
|
||||||
} else {
|
|
||||||
for ((factory, meta) in plugin.dependsOn()) {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
fetch(factory, meta, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info { "Loading plugin ${plugin.name} into ${context.name}" }
|
|
||||||
plugin.attach(context)
|
|
||||||
plugins.add(plugin)
|
|
||||||
return plugin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a plugin from [PluginManager]
|
|
||||||
*/
|
|
||||||
@Deprecated("Use immutable contexts instead")
|
|
||||||
public fun remove(plugin: Plugin) {
|
|
||||||
if (plugins.contains(plugin)) {
|
|
||||||
Global.logger.info { "Removing plugin ${plugin.name} from ${context.name}" }
|
|
||||||
plugin.detach()
|
|
||||||
plugins.remove(plugin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an existing plugin with given meta or load new one using provided factory
|
|
||||||
*/
|
|
||||||
@Deprecated("Use immutable contexts instead")
|
|
||||||
public fun <T : Plugin> fetch(factory: PluginFactory<T>, meta: Meta = Meta.EMPTY, recursive: Boolean = true): T {
|
|
||||||
val loaded = get(factory.type, factory.tag, recursive)
|
|
||||||
return when {
|
|
||||||
loaded == null -> load(factory(meta, context))
|
|
||||||
loaded.meta == meta -> loaded // if meta is the same, return existing plugin
|
|
||||||
else -> throw RuntimeException("Can't load plugin with tag ${factory.tag}. Plugin with this tag and different configuration already exists in context.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Use immutable contexts instead")
|
|
||||||
public fun <T : Plugin> fetch(
|
|
||||||
factory: PluginFactory<T>,
|
|
||||||
recursive: Boolean = true,
|
|
||||||
metaBuilder: MetaBuilder.() -> Unit,
|
|
||||||
): T = fetch(factory, Meta(metaBuilder), recursive)
|
|
||||||
|
|
||||||
override fun iterator(): Iterator<Plugin> = plugins.iterator()
|
override fun iterator(): Iterator<Plugin> = plugins.iterator()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.NameToken
|
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import space.kscience.dataforge.names.plus
|
|
||||||
import kotlin.collections.set
|
|
||||||
import kotlin.jvm.Synchronized
|
|
||||||
|
|
||||||
//TODO add validator to configuration
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutable meta representing object state
|
|
||||||
*/
|
|
||||||
@Serializable(Config.Companion::class)
|
|
||||||
public class Config : AbstractMutableMeta<Config>(), ItemPropertyProvider {
|
|
||||||
|
|
||||||
private val listeners = HashSet<ItemListener>()
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun itemChanged(name: Name, oldItem: MetaItem?, newItem: MetaItem?) {
|
|
||||||
listeners.forEach { it.action(name, oldItem, newItem) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
|
|
||||||
listeners.add(ItemListener(owner, action))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all listeners belonging to given owner
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
override fun removeListener(owner: Any?) {
|
|
||||||
listeners.removeAll { it.owner === owner }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun replaceItem(key: NameToken, oldItem: TypedMetaItem<Config>?, newItem: TypedMetaItem<Config>?) {
|
|
||||||
if (newItem == null) {
|
|
||||||
children.remove(key)
|
|
||||||
if (oldItem != null && oldItem is MetaItemNode<Config>) {
|
|
||||||
oldItem.node.removeListener(this)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
children[key] = newItem
|
|
||||||
if (newItem is MetaItemNode) {
|
|
||||||
newItem.node.onChange(this) { name, oldChild, newChild ->
|
|
||||||
itemChanged(key + name, oldChild, newChild)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemChanged(key.asName(), oldItem, newItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach configuration node instead of creating one
|
|
||||||
*/
|
|
||||||
override fun wrapNode(meta: Meta): Config = meta.asConfig()
|
|
||||||
|
|
||||||
override fun empty(): Config = Config()
|
|
||||||
|
|
||||||
public companion object ConfigSerializer : KSerializer<Config> {
|
|
||||||
|
|
||||||
public fun empty(): Config = Config()
|
|
||||||
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Config {
|
|
||||||
return MetaSerializer.deserialize(decoder).asConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: Config) {
|
|
||||||
MetaSerializer.serialize(encoder, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun Config.get(token: NameToken): TypedMetaItem<Config>? = items[token]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a mutable copy of this [Meta]. The copy is created event if initial [Meta] is a [Config].
|
|
||||||
* Listeners are not preserved
|
|
||||||
*/
|
|
||||||
public fun Meta.toConfig(): Config = Config().also { builder ->
|
|
||||||
this.items.mapValues { entry ->
|
|
||||||
val item = entry.value
|
|
||||||
builder[entry.key.asName()] = when (item) {
|
|
||||||
is MetaItemValue -> item.value
|
|
||||||
is MetaItemNode -> MetaItemNode(item.node.asConfig())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a copy of this config, optionally applying the given [block].
|
|
||||||
* The listeners of the original Config are not retained.
|
|
||||||
*/
|
|
||||||
public inline fun Config.copy(block: Config.() -> Unit = {}): Config = toConfig().apply(block)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return this [Meta] as [Config] if it is [Config] and create a new copy otherwise
|
|
||||||
*/
|
|
||||||
public fun Meta.asConfig(): Config = this as? Config ?: toConfig()
|
|
@ -5,21 +5,21 @@ import space.kscience.dataforge.names.Name
|
|||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A container that holds a [Config].
|
* A container that holds a [ObservableMeta].
|
||||||
*/
|
*/
|
||||||
public interface Configurable {
|
public interface Configurable {
|
||||||
/**
|
/**
|
||||||
* Backing config
|
* Backing config
|
||||||
*/
|
*/
|
||||||
public val config: Config
|
public val config: ObservableMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
|
public fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
|
||||||
|
|
||||||
@DFBuilder
|
@DFBuilder
|
||||||
public inline fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
|
public inline fun <T : Configurable> T.configure(action: ObservableMeta.() -> Unit): T = apply { config.apply(action) }
|
||||||
|
|
||||||
/* Node delegates */
|
/* Node delegates */
|
||||||
|
|
||||||
public fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> = config.node(key)
|
public fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, ObservableMeta?> = config.node(key)
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
package space.kscience.dataforge.meta
|
package space.kscience.dataforge.meta
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.*
|
||||||
import space.kscience.dataforge.names.asName
|
import kotlin.js.JsName
|
||||||
import space.kscience.dataforge.names.startsWith
|
import kotlin.jvm.Synchronized
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
|
|
||||||
@ -12,13 +17,105 @@ internal data class ItemListener(
|
|||||||
val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit,
|
val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
public interface ObservableItemProvider : ItemProvider {
|
* An item provider that could be observed and mutated
|
||||||
|
*/
|
||||||
|
public interface ObservableItemProvider : ItemProvider, MutableItemProvider {
|
||||||
|
/**
|
||||||
|
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
||||||
|
*/
|
||||||
public fun onChange(owner: Any?, action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit)
|
public fun onChange(owner: Any?, action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all listeners belonging to given owner
|
||||||
|
*/
|
||||||
public fun removeListener(owner: Any?)
|
public fun removeListener(owner: Any?)
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ItemPropertyProvider: ObservableItemProvider, MutableItemProvider
|
private open class ObservableItemProviderWrapper(
|
||||||
|
open val itemProvider: MutableItemProvider,
|
||||||
|
open val parent: Pair<ObservableItemProviderWrapper, Name>? = null
|
||||||
|
) : ObservableItemProvider {
|
||||||
|
|
||||||
|
override fun getItem(name: Name): MetaItem? = itemProvider.getItem(name)
|
||||||
|
|
||||||
|
private val listeners = HashSet<ItemListener>()
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun itemChanged(name: Name, oldItem: MetaItem?, newItem: MetaItem?) {
|
||||||
|
listeners.forEach { it.action(name, oldItem, newItem) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setItem(name: Name, item: MetaItem?) {
|
||||||
|
val oldItem = getItem(name)
|
||||||
|
itemProvider.setItem(name, item)
|
||||||
|
itemChanged(name, oldItem, item)
|
||||||
|
|
||||||
|
//Recursively send update to parent listeners
|
||||||
|
parent?.let { (parentNode, token) ->
|
||||||
|
parentNode.itemChanged(token + name, oldItem, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
|
||||||
|
listeners.add(ItemListener(owner, action))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun removeListener(owner: Any?) {
|
||||||
|
listeners.removeAll { it.owner === owner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun MutableItemProvider.asObservable(): ObservableItemProvider =
|
||||||
|
(this as? ObservableItemProvider) ?: ObservableItemProviderWrapper(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Meta instance that could be both mutated and observed.
|
||||||
|
*/
|
||||||
|
@Serializable(ObservableMetaSerializer::class)
|
||||||
|
public interface ObservableMeta : ObservableItemProvider, MutableMeta<ObservableMeta>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper class that creates observable meta node from regular meta node
|
||||||
|
*/
|
||||||
|
private class ObservableMetaWrapper<M : MutableMeta<M>>(
|
||||||
|
override val itemProvider: M,
|
||||||
|
override val parent: Pair<ObservableMetaWrapper<M>, Name>? = null
|
||||||
|
) : ObservableItemProviderWrapper(itemProvider, parent), ObservableMeta {
|
||||||
|
override fun equals(other: Any?): Boolean = (itemProvider == other)
|
||||||
|
|
||||||
|
override fun hashCode(): Int = itemProvider.hashCode()
|
||||||
|
|
||||||
|
override fun toString(): String = itemProvider.toString()
|
||||||
|
|
||||||
|
private fun wrapItem(name: Name, item: TypedMetaItem<M>): TypedMetaItem<ObservableMeta> {
|
||||||
|
return when (item) {
|
||||||
|
is MetaItemValue -> item
|
||||||
|
is MetaItemNode<M> -> ObservableMetaWrapper(item.node, this to name).asMetaItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(name: Name): TypedMetaItem<ObservableMeta>? = itemProvider[name]?.let {
|
||||||
|
wrapItem(name, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val items: Map<NameToken, TypedMetaItem<ObservableMeta>>
|
||||||
|
get() = itemProvider.items.mapValues { (token, childItem: TypedMetaItem<M>) ->
|
||||||
|
wrapItem(token.asName(), childItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this meta is observable return itself. Otherwise return an observable wrapper. The changes of initial meta are
|
||||||
|
* reflected on wrapper but are **NOT** observed.
|
||||||
|
*/
|
||||||
|
public fun <M : MutableMeta<M>> M.asObservable(): ObservableMeta =
|
||||||
|
(this as? ObservableMeta) ?: ObservableMetaWrapper(this)
|
||||||
|
|
||||||
|
@JsName("buildObservableMeta")
|
||||||
|
public fun ObservableMeta(): ObservableMeta = MetaBuilder().asObservable()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the value of the property in a [callBack].
|
* Use the value of the property in a [callBack].
|
||||||
@ -40,3 +137,25 @@ public fun <O : ObservableItemProvider, T> O.useProperty(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public object ObservableMetaSerializer : KSerializer<ObservableMeta> {
|
||||||
|
public fun empty(): ObservableMeta = ObservableMeta()
|
||||||
|
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): ObservableMeta {
|
||||||
|
return MetaSerializer.deserialize(decoder).toMutableMeta().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: ObservableMeta) {
|
||||||
|
MetaSerializer.serialize(encoder, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public operator fun ObservableMeta.get(token: NameToken): TypedMetaItem<ObservableMeta>? = items[token]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a copy of this config, optionally applying the given [block].
|
||||||
|
* The listeners of the original Config are not retained.
|
||||||
|
*/
|
||||||
|
public inline fun ObservableMeta.copy(block: ObservableMeta.() -> Unit = {}): ObservableMeta =
|
||||||
|
asObservable().apply(block)
|
@ -13,16 +13,11 @@ import kotlin.jvm.Synchronized
|
|||||||
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
|
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
|
||||||
* Default item provider and [NodeDescriptor] are optional
|
* Default item provider and [NodeDescriptor] are optional
|
||||||
*/
|
*/
|
||||||
public open class Scheme() : Described, MetaRepr, ItemPropertyProvider {
|
public open class Scheme(
|
||||||
|
private var items: ObservableItemProvider = ObservableMeta(),
|
||||||
private var items: MutableItemProvider = Config()
|
final override var descriptor: NodeDescriptor? = null,
|
||||||
|
|
||||||
private val listeners = HashSet<ItemListener>()
|
|
||||||
|
|
||||||
private var default: ItemProvider? = null
|
private var default: ItemProvider? = null
|
||||||
|
) : Described, MetaRepr, ObservableItemProvider, MutableItemProvider {
|
||||||
final override var descriptor: NodeDescriptor? = null
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener to this scheme changes. If the inner provider is observable, then listening will be delegated to it.
|
* Add a listener to this scheme changes. If the inner provider is observable, then listening will be delegated to it.
|
||||||
@ -30,8 +25,7 @@ public open class Scheme() : Described, MetaRepr, ItemPropertyProvider {
|
|||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
|
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
|
||||||
(items as? ObservableItemProvider)?.onChange(owner, action)
|
items.onChange(owner, action)
|
||||||
?: run { listeners.add(ItemListener(owner, action)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,8 +33,7 @@ public open class Scheme() : Described, MetaRepr, ItemPropertyProvider {
|
|||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun removeListener(owner: Any?) {
|
override fun removeListener(owner: Any?) {
|
||||||
(items as? ObservableItemProvider)?.removeListener(owner)
|
items.removeListener(owner)
|
||||||
?: listeners.removeAll { it.owner === owner }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun wrap(
|
internal fun wrap(
|
||||||
@ -51,11 +44,11 @@ public open class Scheme() : Described, MetaRepr, ItemPropertyProvider {
|
|||||||
//use properties in the init block as default
|
//use properties in the init block as default
|
||||||
this.default = this.items.withDefault(default)
|
this.default = this.items.withDefault(default)
|
||||||
//reset values, defaults are already saved
|
//reset values, defaults are already saved
|
||||||
this.items = items
|
this.items = items.asObservable()
|
||||||
this.descriptor = descriptor
|
this.descriptor = descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDefaultItem(name: Name): MetaItem? {
|
protected open fun getDefaultItem(name: Name): MetaItem? {
|
||||||
return default?.get(name) ?: descriptor?.get(name)?.defaultValue
|
return default?.get(name) ?: descriptor?.get(name)?.defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +72,6 @@ public open class Scheme() : Described, MetaRepr, ItemPropertyProvider {
|
|||||||
val oldItem = items[name]
|
val oldItem = items[name]
|
||||||
if (validateItem(name, item)) {
|
if (validateItem(name, item)) {
|
||||||
items[name] = item
|
items[name] = item
|
||||||
listeners.forEach { it.action(name, oldItem, item) }
|
|
||||||
} else {
|
} else {
|
||||||
error("Validation failed for property $name with value $item")
|
error("Validation failed for property $name with value $item")
|
||||||
}
|
}
|
||||||
@ -105,7 +97,7 @@ public open class Scheme() : Described, MetaRepr, ItemPropertyProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toMeta(): Laminate = Laminate(items[Name.EMPTY].node, defaultLayer)
|
override fun toMeta(): Laminate = Laminate(items.rootNode, defaultLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,7 +137,7 @@ public open class SchemeSpec<out T : Scheme>(
|
|||||||
|
|
||||||
override fun empty(): T = builder()
|
override fun empty(): T = builder()
|
||||||
|
|
||||||
override fun read(items: ItemProvider): T = wrap(Config(), items, descriptor)
|
override fun read(items: ItemProvider): T = wrap(ObservableMeta(), items, descriptor)
|
||||||
|
|
||||||
override fun write(target: MutableItemProvider, defaultProvider: ItemProvider): T =
|
override fun write(target: MutableItemProvider, defaultProvider: ItemProvider): T =
|
||||||
wrap(target, defaultProvider, descriptor)
|
wrap(target, defaultProvider, descriptor)
|
||||||
|
@ -52,7 +52,7 @@ public sealed interface ItemDescriptor: MetaRepr {
|
|||||||
* The builder for [ItemDescriptor]
|
* The builder for [ItemDescriptor]
|
||||||
*/
|
*/
|
||||||
@DFBuilder
|
@DFBuilder
|
||||||
public sealed class ItemDescriptorBuilder(final override val config: Config) : Configurable, ItemDescriptor {
|
public sealed class ItemDescriptorBuilder(final override val config: ObservableMeta) : Configurable, ItemDescriptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if same name siblings with this name are allowed
|
* True if same name siblings with this name are allowed
|
||||||
@ -75,7 +75,7 @@ public sealed class ItemDescriptorBuilder(final override val config: Config) : C
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
override var attributes: Config? by config.node()
|
override var attributes: ObservableMeta? by config.node()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An index field by which this node is identified in case of same name siblings construct
|
* An index field by which this node is identified in case of same name siblings construct
|
||||||
@ -94,8 +94,8 @@ public sealed class ItemDescriptorBuilder(final override val config: Config) : C
|
|||||||
/**
|
/**
|
||||||
* Configure attributes of the descriptor, creating an attributes node if needed.
|
* Configure attributes of the descriptor, creating an attributes node if needed.
|
||||||
*/
|
*/
|
||||||
public inline fun ItemDescriptorBuilder.attributes(block: Config.() -> Unit) {
|
public inline fun ItemDescriptorBuilder.attributes(block: ObservableMeta.() -> Unit) {
|
||||||
(attributes ?: Config().also { this.attributes = it }).apply(block)
|
(attributes ?: ObservableMeta().also { this.attributes = it }).apply(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,7 +41,7 @@ public sealed interface NodeDescriptor : ItemDescriptor {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public val default: Config?
|
public val default: Meta?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The map of children item descriptors (both nodes and values)
|
* The map of children item descriptors (both nodes and values)
|
||||||
@ -74,7 +74,7 @@ public sealed interface NodeDescriptor : ItemDescriptor {
|
|||||||
|
|
||||||
|
|
||||||
@DFBuilder
|
@DFBuilder
|
||||||
public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBuilder(config), NodeDescriptor {
|
public class NodeDescriptorBuilder(config: ObservableMeta = ObservableMeta()) : ItemDescriptorBuilder(config), NodeDescriptor {
|
||||||
init {
|
init {
|
||||||
config[IS_NODE_KEY] = true
|
config[IS_NODE_KEY] = true
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBu
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
override var default: Config? by config.node()
|
override var default: ObservableMeta? by config.node()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The map of children item descriptors (both nodes and values)
|
* The map of children item descriptors (both nodes and values)
|
||||||
@ -101,9 +101,9 @@ public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBu
|
|||||||
if (name == null) error("Child item index should not be null")
|
if (name == null) error("Child item index should not be null")
|
||||||
val node = item.node ?: error("Node descriptor must be a node")
|
val node = item.node ?: error("Node descriptor must be a node")
|
||||||
if (node[IS_NODE_KEY].boolean == true) {
|
if (node[IS_NODE_KEY].boolean == true) {
|
||||||
name to NodeDescriptorBuilder(node as Config)
|
name to NodeDescriptorBuilder(node as ObservableMeta)
|
||||||
} else {
|
} else {
|
||||||
name to ValueDescriptorBuilder(node as Config)
|
name to ValueDescriptorBuilder(node as ObservableMeta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBu
|
|||||||
}.associate { (name, item) ->
|
}.associate { (name, item) ->
|
||||||
if (name == null) error("Child node index should not be null")
|
if (name == null) error("Child node index should not be null")
|
||||||
val node = item.node ?: error("Node descriptor must be a node")
|
val node = item.node ?: error("Node descriptor must be a node")
|
||||||
name to NodeDescriptorBuilder(node as Config)
|
name to NodeDescriptorBuilder(node as ObservableMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,7 +129,7 @@ public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBu
|
|||||||
}.associate { (name, item) ->
|
}.associate { (name, item) ->
|
||||||
if (name == null) error("Child value index should not be null")
|
if (name == null) error("Child value index should not be null")
|
||||||
val node = item.node ?: error("Node descriptor must be a node")
|
val node = item.node ?: error("Node descriptor must be a node")
|
||||||
name to ValueDescriptorBuilder(node as Config)
|
name to ValueDescriptorBuilder(node as ObservableMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildNode(name: Name): NodeDescriptorBuilder {
|
private fun buildNode(name: Name): NodeDescriptorBuilder {
|
||||||
@ -137,7 +137,7 @@ public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBu
|
|||||||
0 -> this
|
0 -> this
|
||||||
1 -> {
|
1 -> {
|
||||||
val token = NameToken(ITEM_KEY.toString(), name.toString())
|
val token = NameToken(ITEM_KEY.toString(), name.toString())
|
||||||
val config: Config = config[token].node ?: Config().also {
|
val config: ObservableMeta = config[token].node ?: ObservableMeta().also {
|
||||||
it[IS_NODE_KEY] = true
|
it[IS_NODE_KEY] = true
|
||||||
config[token] = it
|
config[token] = it
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import space.kscience.dataforge.misc.DFBuilder
|
|||||||
import space.kscience.dataforge.values.*
|
import space.kscience.dataforge.values.*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A descriptor for meta value
|
* A descriptor for meta value
|
||||||
*
|
*
|
||||||
@ -37,6 +36,7 @@ public sealed interface ValueDescriptor: ItemDescriptor{
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public val type: List<ValueType>?
|
public val type: List<ValueType>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if given value is allowed for here. The type should be allowed and
|
* Check if given value is allowed for here. The type should be allowed and
|
||||||
* if it is value should be within allowed values
|
* if it is value should be within allowed values
|
||||||
@ -61,7 +61,9 @@ public sealed interface ValueDescriptor: ItemDescriptor{
|
|||||||
* A builder fir [ValueDescriptor]
|
* A builder fir [ValueDescriptor]
|
||||||
*/
|
*/
|
||||||
@DFBuilder
|
@DFBuilder
|
||||||
public class ValueDescriptorBuilder(config: Config = Config()) : ItemDescriptorBuilder(config), ValueDescriptor {
|
public class ValueDescriptorBuilder(
|
||||||
|
config: ObservableMeta = ObservableMeta()
|
||||||
|
) : ItemDescriptorBuilder(config), ValueDescriptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the value is required
|
* True if the value is required
|
||||||
|
@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
|
|||||||
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
|
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
|
||||||
*/
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public fun generate(source: Config): ObservableItemProvider = Config().apply {
|
public fun generate(source: ObservableMeta): ObservableItemProvider = ObservableMeta().apply {
|
||||||
transformations.forEach { rule ->
|
transformations.forEach { rule ->
|
||||||
rule.selectItems(source).forEach { name ->
|
rule.selectItems(source).forEach { name ->
|
||||||
rule.transformItem(name, source[name], this)
|
rule.transformItem(name, source[name], this)
|
||||||
@ -131,7 +131,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
|
|||||||
/**
|
/**
|
||||||
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
|
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
|
||||||
*/
|
*/
|
||||||
public fun <M : MutableMeta<M>> bind(source: Config, target: M) {
|
public fun <M : MutableMeta<M>> bind(source: ObservableMeta, target: M) {
|
||||||
source.onChange(target) { name, _, newItem ->
|
source.onChange(target) { name, _, newItem ->
|
||||||
transformations.forEach { t ->
|
transformations.forEach { t ->
|
||||||
if (t.matches(name, newItem)) {
|
if (t.matches(name, newItem)) {
|
||||||
|
@ -6,7 +6,7 @@ import kotlin.test.assertEquals
|
|||||||
class ConfigTest {
|
class ConfigTest {
|
||||||
@Test
|
@Test
|
||||||
fun testIndexedWrite(){
|
fun testIndexedWrite(){
|
||||||
val config = Config()
|
val config = MetaBuilder()
|
||||||
config["a[1].b"] = 1
|
config["a[1].b"] = 1
|
||||||
assertEquals(null, config["a.b"].int)
|
assertEquals(null, config["a.b"].int)
|
||||||
assertEquals(1, config["a[1].b"].int)
|
assertEquals(1, config["a[1].b"].int)
|
||||||
|
@ -14,7 +14,7 @@ class MutableMetaTest{
|
|||||||
"b" put 22
|
"b" put 22
|
||||||
"c" put "StringValue"
|
"c" put "StringValue"
|
||||||
}
|
}
|
||||||
}.asConfig()
|
}.asObservable()
|
||||||
|
|
||||||
meta.remove("aNode.c")
|
meta.remove("aNode.c")
|
||||||
assertEquals(meta["aNode.c"], null)
|
assertEquals(meta["aNode.c"], null)
|
||||||
|
@ -8,7 +8,7 @@ import kotlin.test.assertEquals
|
|||||||
class SchemeTest {
|
class SchemeTest {
|
||||||
@Test
|
@Test
|
||||||
fun testSchemeWrappingBeforeEdit(){
|
fun testSchemeWrappingBeforeEdit(){
|
||||||
val config = Config()
|
val config = MetaBuilder()
|
||||||
val scheme = TestScheme.wrap(config)
|
val scheme = TestScheme.wrap(config)
|
||||||
scheme.a = 29
|
scheme.a = 29
|
||||||
assertEquals(29, config["a"].int)
|
assertEquals(29, config["a"].int)
|
||||||
@ -18,7 +18,7 @@ class SchemeTest {
|
|||||||
fun testSchemeWrappingAfterEdit(){
|
fun testSchemeWrappingAfterEdit(){
|
||||||
val scheme = TestScheme.empty()
|
val scheme = TestScheme.empty()
|
||||||
scheme.a = 29
|
scheme.a = 29
|
||||||
val config = Config()
|
val config = MetaBuilder()
|
||||||
scheme.retarget(config)
|
scheme.retarget(config)
|
||||||
assertEquals(29, scheme.a)
|
assertEquals(29, scheme.a)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ internal class TestScheme : Scheme() {
|
|||||||
override fun empty(): TestScheme = TestScheme()
|
override fun empty(): TestScheme = TestScheme()
|
||||||
|
|
||||||
override fun read(items: ItemProvider): TestScheme =
|
override fun read(items: ItemProvider): TestScheme =
|
||||||
wrap(Config(), items)
|
wrap(MetaBuilder(), items)
|
||||||
|
|
||||||
override fun write(target: MutableItemProvider, defaultProvider: ItemProvider): TestScheme =
|
override fun write(target: MutableItemProvider, defaultProvider: ItemProvider): TestScheme =
|
||||||
wrap(target, defaultProvider)
|
wrap(target, defaultProvider)
|
||||||
@ -58,7 +58,7 @@ class SpecificationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChildModification() {
|
fun testChildModification() {
|
||||||
val config = Config()
|
val config = MetaBuilder()
|
||||||
val child = config.getChild("child")
|
val child = config.getChild("child")
|
||||||
val scheme = TestScheme.write(child)
|
val scheme = TestScheme.write(child)
|
||||||
scheme.a = 22
|
scheme.a = 22
|
||||||
@ -69,7 +69,7 @@ class SpecificationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testChildUpdate() {
|
fun testChildUpdate() {
|
||||||
val config = Config()
|
val config = MetaBuilder()
|
||||||
val child = config.getChild("child")
|
val child = config.getChild("child")
|
||||||
child.update(TestScheme) {
|
child.update(TestScheme) {
|
||||||
a = 22
|
a = 22
|
||||||
|
@ -51,7 +51,7 @@ public object Builders {
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun buildWorkspace(file: File): Workspace = buildWorkspace(file.toScriptSource())
|
public fun buildWorkspace(scriptFile: File): Workspace = buildWorkspace(scriptFile.toScriptSource())
|
||||||
|
|
||||||
public fun buildWorkspace(string: String): Workspace = buildWorkspace(string.toScriptSource())
|
public fun buildWorkspace(scriptString: String): Workspace = buildWorkspace(scriptString.toScriptSource())
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ package space.kscience.dataforge.scripting
|
|||||||
import space.kscience.dataforge.context.Global
|
import space.kscience.dataforge.context.Global
|
||||||
import space.kscience.dataforge.meta.get
|
import space.kscience.dataforge.meta.get
|
||||||
import space.kscience.dataforge.meta.int
|
import space.kscience.dataforge.meta.int
|
||||||
import space.kscience.dataforge.workspace.WorkspaceBuilder
|
import space.kscience.dataforge.workspace.Workspace
|
||||||
import space.kscience.dataforge.workspace.target
|
import space.kscience.dataforge.workspace.target
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -12,7 +12,7 @@ import kotlin.test.assertEquals
|
|||||||
class BuildersKtTest {
|
class BuildersKtTest {
|
||||||
@Test
|
@Test
|
||||||
fun checkBuilder() {
|
fun checkBuilder() {
|
||||||
val workspace = WorkspaceBuilder(Global).apply {
|
Workspace(Global){
|
||||||
println("I am working")
|
println("I am working")
|
||||||
|
|
||||||
context { name("test") }
|
context { name("test") }
|
||||||
|
@ -91,6 +91,5 @@ public inline fun WorkspaceBuilder.target(name: String, metaBuilder: MetaBuilder
|
|||||||
target(name, Meta(metaBuilder))
|
target(name, Meta(metaBuilder))
|
||||||
|
|
||||||
@DFBuilder
|
@DFBuilder
|
||||||
public fun Workspace(parentContext: Context = Global, builder: WorkspaceBuilder.() -> Unit): Workspace {
|
public fun Workspace(parentContext: Context = Global, builder: WorkspaceBuilder.() -> Unit): Workspace =
|
||||||
return WorkspaceBuilder(parentContext).apply(builder).build()
|
WorkspaceBuilder(parentContext).apply(builder).build()
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user