Immutable plugin manager

This commit is contained in:
Alexander Nozik 2021-07-08 12:15:34 +03:00
parent 474597777c
commit 73b3bbe7fc
21 changed files with 207 additions and 241 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

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

View File

@ -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")
} }

View File

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

View File

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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)
} }
/** /**

View File

@ -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
} }

View File

@ -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

View File

@ -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)) {

View File

@ -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)

View File

@ -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)

View File

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

View File

@ -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

View File

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

View File

@ -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") }

View File

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