Context module
This commit is contained in:
parent
dd05897759
commit
d3ce88eb3f
@ -20,9 +20,7 @@ allprojects {
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url = "http://dl.bintray.com/kotlin/kotlin-eap" }
|
||||
maven { url = "https://kotlin.bintray.com/kotlinx" }
|
||||
//maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
group = 'hep.dataforge'
|
||||
|
32
dataforge-context/build.gradle
Normal file
32
dataforge-context/build.gradle
Normal file
@ -0,0 +1,32 @@
|
||||
plugins {
|
||||
id 'kotlin-multiplatform'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm')
|
||||
//fromPreset(presets.js, 'js')
|
||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||
//fromPreset(presets.iosX64, 'ios')
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":dataforge-meta")
|
||||
api "org.jetbrains.kotlin:kotlin-reflect"
|
||||
api "io.github.microutils:kotlin-logging-common:1.6.10"
|
||||
}
|
||||
}
|
||||
jvmMain{
|
||||
dependencies{
|
||||
api "io.github.microutils:kotlin-logging:1.6.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package hep.dataforge.context
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Provider
|
||||
import hep.dataforge.provider.provideAll
|
||||
import hep.dataforge.values.Value
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface Context : Named, MetaRepr, Provider {
|
||||
|
||||
val parent: Context?
|
||||
|
||||
/**
|
||||
* Context properties. Working as substitutes for environment variables
|
||||
*/
|
||||
val properties: Meta
|
||||
|
||||
/**
|
||||
* Context logger
|
||||
*/
|
||||
val logger: KLogger
|
||||
|
||||
val plugins: PluginManager
|
||||
|
||||
/**
|
||||
* Defines if context is used in any kind of active computations. Active context properties and plugins could not be changed
|
||||
*/
|
||||
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 fun provideTop(target: String, name: Name): Any? {
|
||||
return when (target) {
|
||||
Plugin.PLUGIN_TARGET -> plugins[PluginTag.fromString(name.toString())]
|
||||
Value.VALUE_TARGET -> properties[name]?.value
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun listTop(target: String): Sequence<Name> {
|
||||
return when (target) {
|
||||
Plugin.PLUGIN_TARGET -> plugins.asSequence().map { it.name.toName() }
|
||||
Value.VALUE_TARGET -> properties.asValueSequence().map { it.first }
|
||||
else -> emptySequence()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark context as active and used by [activator]
|
||||
*/
|
||||
fun activate(activator: Any)
|
||||
|
||||
/**
|
||||
* Mark context unused by [activator]
|
||||
*/
|
||||
fun deactivate(activator: Any)
|
||||
|
||||
/**
|
||||
* Detach all plugins and terminate context
|
||||
*/
|
||||
fun close()
|
||||
}
|
||||
|
||||
/**
|
||||
* A sequences of all objects provided by plugins with given target and type
|
||||
*/
|
||||
inline fun <reified T : Any> Context.list(target: String): Sequence<T> {
|
||||
return plugins.asSequence().flatMap { provideAll(target) }.mapNotNull { it as? T }
|
||||
}
|
||||
|
||||
/**
|
||||
* A global root context
|
||||
*/
|
||||
expect object Global : Context
|
||||
|
||||
/**
|
||||
* The interface for something that encapsulated in context
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
interface ContextAware {
|
||||
/**
|
||||
* Get context for this object
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val context: Context
|
||||
|
||||
val logger: KLogger
|
||||
get() = if (this is Named) {
|
||||
KotlinLogging.logger(context.name + "." + (this as Named).name)
|
||||
} else {
|
||||
context.logger
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.context
|
||||
|
||||
/**
|
||||
* Any object that have name
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
interface Named {
|
||||
|
||||
/**
|
||||
* The name of this object instance
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val name: String
|
||||
|
||||
companion object {
|
||||
const val ANONYMOUS = ""
|
||||
|
||||
/**
|
||||
* Get the name of given object. If object is Named its name is used,
|
||||
* otherwise, use Object.toString
|
||||
*
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
fun nameOf(obj: Any): String {
|
||||
return if (obj is Named) {
|
||||
obj.name
|
||||
} else {
|
||||
obj.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this object has an empty name and therefore is anonymous.
|
||||
* @return
|
||||
*/
|
||||
val Named.isAnonymous: Boolean
|
||||
get() = this.name == Named.ANONYMOUS
|
@ -0,0 +1,78 @@
|
||||
package hep.dataforge.context
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaRepr
|
||||
import hep.dataforge.meta.Metoid
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.provider.Provider
|
||||
|
||||
/**
|
||||
* The interface to define a Context plugin. A plugin stores all runtime features of a context.
|
||||
* The plugin is by default configurable and a Provider (both features could be ignored).
|
||||
* The plugin must in most cases have an empty constructor in order to be able to load it from library.
|
||||
*
|
||||
*
|
||||
* The plugin lifecycle is the following:
|
||||
*
|
||||
*
|
||||
* create - configure - attach - detach - destroy
|
||||
*
|
||||
*
|
||||
* Configuration of attached plugin is possible for a context which is not in a runtime mode, but it is not recommended.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
interface Plugin : Named, Metoid, ContextAware, Provider, MetaRepr {
|
||||
|
||||
/**
|
||||
* Get tag for this plugin
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val tag: PluginTag
|
||||
|
||||
/**
|
||||
* The name of this plugin ignoring version and group
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override val name: String
|
||||
get() = tag.name
|
||||
|
||||
/**
|
||||
* Plugin dependencies which are required to attach this plugin. Plugin
|
||||
* dependencies must be initialized and enabled in the Context before this
|
||||
* plugin is enabled.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
fun dependsOn(): List<PluginTag>
|
||||
|
||||
/**
|
||||
* Start this plugin and attach registration info to the context. This method
|
||||
* should be called only via PluginManager to avoid dependency issues.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
fun attach(context: Context)
|
||||
|
||||
/**
|
||||
* Stop this plugin and remove registration info from context and other
|
||||
* plugins. This method should be called only via PluginManager to avoid
|
||||
* dependency issues.
|
||||
*/
|
||||
fun detach()
|
||||
|
||||
override fun toMeta(): Meta = buildMeta {
|
||||
"context" to context.name
|
||||
"type" to this::class.qualifiedName
|
||||
"tag" to tag
|
||||
"meta" to meta
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val PLUGIN_TARGET = "plugin"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package hep.dataforge.context
|
||||
|
||||
import hep.dataforge.meta.EmptyMeta
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* The manager for plugin system. Should monitor plugin dependencies and locks.
|
||||
*
|
||||
* @property context A context for this plugin manager
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
class PluginManager(override val context: Context) : ContextAware, Iterable<Plugin> {
|
||||
|
||||
/**
|
||||
* A set of loaded plugins
|
||||
*/
|
||||
private val plugins = HashSet<Plugin>()
|
||||
|
||||
private val parent: PluginManager? = context.parent?.plugins
|
||||
|
||||
|
||||
fun sequence(recursive: Boolean): Sequence<Plugin> {
|
||||
return if (recursive && parent != null) {
|
||||
plugins.asSequence() + parent.sequence(true)
|
||||
} else {
|
||||
plugins.asSequence()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get for existing plugin
|
||||
*/
|
||||
fun get(recursive: Boolean = true, predicate: (Plugin) -> Boolean): Plugin? = sequence(recursive).find(predicate)
|
||||
|
||||
|
||||
/**
|
||||
* Find a loaded plugin via its tag
|
||||
*
|
||||
* @param tag
|
||||
* @return
|
||||
*/
|
||||
operator fun get(tag: PluginTag, recursive: Boolean = true): Plugin? = get(recursive) { tag.matches(it.tag) }
|
||||
|
||||
|
||||
/**
|
||||
* Find a loaded plugin via its class
|
||||
*
|
||||
* @param tag
|
||||
* @param type
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
operator fun <T : Plugin> get(type: KClass<T>, recursive: Boolean = true): T? = get(recursive) { type.isInstance(it) } as T?
|
||||
|
||||
inline fun <reified T : Plugin> get(recursive: Boolean = true): T? = get(T::class, recursive)
|
||||
|
||||
|
||||
/**
|
||||
* Load given plugin into this manager and return loaded instance.
|
||||
* Throw error if plugin of the same class already exists in manager
|
||||
*
|
||||
* @param plugin
|
||||
* @return
|
||||
*/
|
||||
fun <T : Plugin> load(plugin: T): T {
|
||||
if (context.isActive) error("Can't load plugin into active context")
|
||||
|
||||
if (get(plugin::class, false) != null) {
|
||||
throw RuntimeException("Plugin of type ${plugin::class} already exists in ${context.name}")
|
||||
} else {
|
||||
loadDependencies(plugin)
|
||||
|
||||
logger.info { "Loading plugin ${plugin.name} into ${context.name}" }
|
||||
plugin.attach(context)
|
||||
plugins.add(plugin)
|
||||
return plugin
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadDependencies(plugin: Plugin) {
|
||||
for (tag in plugin.dependsOn()) {
|
||||
load(tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(plugin: Plugin) {
|
||||
if (context.isActive) error("Can't remove plugin from active context")
|
||||
|
||||
if (plugins.contains(plugin)) {
|
||||
logger.info { "Removing plugin ${plugin.name} from ${context.name}" }
|
||||
plugin.detach()
|
||||
plugins.remove(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin instance via plugin reolver and load it.
|
||||
*
|
||||
* @param tag
|
||||
* @return
|
||||
*/
|
||||
fun load(tag: PluginTag, meta: Meta = EmptyMeta): Plugin {
|
||||
val loaded = get(tag, false)
|
||||
return when {
|
||||
loaded == null -> load(PluginRepository.fetch(tag, meta))
|
||||
loaded.meta == meta -> loaded // if meta is the same, return existing plugin
|
||||
else -> throw RuntimeException("Can't load plugin with tag $tag. Plugin with this tag and different configuration already exists in context.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin by its class and meta. Ignore if plugin with this meta is already loaded.
|
||||
*/
|
||||
fun <T : Plugin> load(type: KClass<T>, meta: Meta = EmptyMeta): T {
|
||||
val loaded = get(type, false)
|
||||
return when {
|
||||
loaded == null -> {
|
||||
val plugin = PluginRepository.list().first { it.type == type }.build(meta)
|
||||
if (type.isInstance(plugin)) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
load(plugin as T)
|
||||
} else {
|
||||
error("Corrupt type information in plugin repository")
|
||||
}
|
||||
}
|
||||
loaded.meta == meta -> loaded // if meta is the same, return existing plugin
|
||||
else -> throw RuntimeException("Can't load plugin with type $type. Plugin with this type and different configuration already exists in context.")
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Plugin> load(noinline metaBuilder: MetaBuilder.() -> Unit = {}): T {
|
||||
return load(T::class, buildMeta(metaBuilder))
|
||||
}
|
||||
|
||||
fun load(name: String, meta: Meta = EmptyMeta): Plugin {
|
||||
return load(PluginTag.fromString(name), meta)
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<Plugin> = plugins.iterator()
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package hep.dataforge.context
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface PluginFactory {
|
||||
val tag: PluginTag
|
||||
val type: KClass<Plugin>
|
||||
fun build(meta: Meta): Plugin
|
||||
}
|
||||
|
||||
|
||||
object PluginRepository {
|
||||
|
||||
/**
|
||||
* List plugins available in the repository
|
||||
*/
|
||||
fun list(): Sequence<PluginFactory> = Global.services(PluginFactory::class)
|
||||
|
||||
/**
|
||||
* 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)
|
||||
?: error("Plugin with tag $tag not found in the repository")
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package hep.dataforge.context
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaRepr
|
||||
import hep.dataforge.meta.buildMeta
|
||||
|
||||
/**
|
||||
* The tag which contains information about name, group and version of some
|
||||
* object. It also could contain any complex rule to define version ranges
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
data class PluginTag(
|
||||
val name: String,
|
||||
val group: String = "",
|
||||
val version: String = ""
|
||||
) : MetaRepr {
|
||||
|
||||
/**
|
||||
* Check if given tag is compatible (in range) of this tag
|
||||
*
|
||||
* @param otherTag
|
||||
* @return
|
||||
*/
|
||||
fun matches(otherTag: PluginTag): Boolean {
|
||||
return matchesName(otherTag) && matchesGroup(otherTag)
|
||||
}
|
||||
|
||||
private fun matchesGroup(otherTag: PluginTag): Boolean {
|
||||
return this.group.isEmpty() || this.group == otherTag.group
|
||||
}
|
||||
|
||||
private fun matchesName(otherTag: PluginTag): Boolean {
|
||||
return this.name == otherTag.name
|
||||
}
|
||||
|
||||
override fun toString(): String = listOf(group, name, version).joinToString(separator = ":")
|
||||
|
||||
override fun toMeta(): Meta = buildMeta {
|
||||
"name" to name
|
||||
"group" to group
|
||||
"version" to version
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Build new PluginTag from standard string representation
|
||||
*
|
||||
* @param tag
|
||||
* @return
|
||||
*/
|
||||
fun fromString(tag: String): PluginTag {
|
||||
val sepIndex = tag.indexOf(":")
|
||||
return if (sepIndex >= 0) {
|
||||
PluginTag(group = tag.substring(0, sepIndex), name = tag.substring(sepIndex + 1))
|
||||
} else {
|
||||
PluginTag(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.provider
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Path interface.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
* @version $Id: $Id
|
||||
*/
|
||||
inline class Path(val tokens: List<PathToken>) : Iterable<PathToken> {
|
||||
|
||||
val head: PathToken? get() = tokens.firstOrNull()
|
||||
|
||||
val length: Int get() = tokens.size
|
||||
|
||||
/**
|
||||
* Returns non-empty optional containing the chain without first segment in case of chain path.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val tail: Path? get() = if (tokens.isEmpty()) null else Path(tokens.drop(1))
|
||||
|
||||
override fun iterator(): Iterator<PathToken> = tokens.iterator()
|
||||
|
||||
companion object {
|
||||
const val PATH_SEGMENT_SEPARATOR = "/"
|
||||
|
||||
fun parse(path: String): Path {
|
||||
val head = path.substringBefore(PATH_SEGMENT_SEPARATOR)
|
||||
val tail = path.substringAfter(PATH_SEGMENT_SEPARATOR)
|
||||
return PathToken.parse(head).toPath() + parse(tail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun Path.plus(path: Path) = Path(this.tokens + path.tokens)
|
||||
|
||||
data class PathToken(val name: Name, val target: String? = null) {
|
||||
override fun toString(): String = if (target == null) {
|
||||
name.toString()
|
||||
} else {
|
||||
"$target$TARGET_SEPARATOR$name"
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TARGET_SEPARATOR = "::"
|
||||
fun parse(token: String): PathToken {
|
||||
val target = token.substringBefore(TARGET_SEPARATOR, "")
|
||||
val name = token.substringAfter(TARGET_SEPARATOR).toName()
|
||||
if (target.contains("[")) TODO("target separators in queries are not supported")
|
||||
return PathToken(name, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PathToken.toPath() = Path(listOf(this))
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2015 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.provider
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
|
||||
/**
|
||||
* A marker utility interface for providers.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
interface Provider {
|
||||
|
||||
/**
|
||||
* Default target for this provider
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val defaultTarget: String get() = ""
|
||||
|
||||
/**
|
||||
* Default target for next chain segment
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val defaultChainTarget: String get() = ""
|
||||
|
||||
|
||||
/**
|
||||
* Provide a top level element for this [Provider] or return null if element is not present
|
||||
*/
|
||||
fun provideTop(target: String, name: Name): Any?
|
||||
|
||||
/**
|
||||
* [Sequence] of available names with given target. Only top level names are listed, no chain path.
|
||||
*
|
||||
* @param target
|
||||
* @return
|
||||
*/
|
||||
fun listTop(target: String): Sequence<Name>
|
||||
}
|
||||
|
||||
fun Provider.provide(path: Path, targetOverride: String? = null): Any? {
|
||||
if (path.length == 0) throw IllegalArgumentException("Can't provide by empty path")
|
||||
val first = path.first()
|
||||
val top = provideTop(targetOverride ?: first.target ?: defaultTarget, first.name)
|
||||
return when (path.length) {
|
||||
1 -> top
|
||||
else -> {
|
||||
when (top) {
|
||||
null -> null
|
||||
is Provider -> top.provide(path.tail!!, targetOverride = defaultChainTarget)
|
||||
else -> throw IllegalStateException("Chain path not supported: child is not a provider")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type checked provide
|
||||
*/
|
||||
inline fun <reified T : Any> Provider.provide(path: String): T? {
|
||||
return provide(Path.parse(path)) as? T
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> Provider.provide(target: String, name: String): T? {
|
||||
return provide(PathToken(name.toName(), target).toPath()) as? T
|
||||
}
|
||||
|
||||
/**
|
||||
* [Sequence] of all elements with given target
|
||||
*/
|
||||
fun Provider.provideAll(target: String): Sequence<Any> {
|
||||
return listTop(target).map { it -> provideTop(target, it) ?: error("The element $it is declared but not provided") }
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.context
|
||||
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
|
||||
private fun Properties.asMeta(): Meta {
|
||||
return buildMeta {
|
||||
this@asMeta.forEach { key, value ->
|
||||
set(key.toString().toName(), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A singleton global context. Automatic root for the whole context hierarchy. Also stores the registry for active contexts.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
actual object Global : Context, JVMContext("GLOBAL", null, Thread.currentThread().contextClassLoader) {
|
||||
|
||||
/**
|
||||
* Closing all contexts
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
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
|
||||
fun getContext(name: String): Context {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2018 Alexander Nozik.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package hep.dataforge.context
|
||||
|
||||
import mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.cast
|
||||
|
||||
/**
|
||||
* The local environment for anything being done in DataForge framework. Contexts are organized into tree structure with [Global] at the top.
|
||||
* Each context has a set of named [Value] properties which are taken from parent context in case they are not found in local context.
|
||||
* Context implements [ValueProvider] interface and therefore could be uses as a value source for substitutions etc.
|
||||
* Context contains [PluginManager] which could be used any number of configurable named plugins.
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
open class JVMContext(
|
||||
final override val name: String,
|
||||
final override val parent: JVMContext? = Global,
|
||||
classLoader: ClassLoader? = null,
|
||||
properties: Meta = EmptyMeta
|
||||
) : Context, AutoCloseable {
|
||||
|
||||
private val _properties = Config().apply { update(properties) }
|
||||
override val properties: Meta
|
||||
get() = if (parent == null) {
|
||||
_properties
|
||||
} else {
|
||||
Laminate(_properties, parent.properties)
|
||||
}
|
||||
|
||||
override val plugins: PluginManager by lazy { PluginManager(this) }
|
||||
override val logger: KLogger = KotlinLogging.logger(name)
|
||||
|
||||
/**
|
||||
* A class loader for this context. Parent class loader is used by default
|
||||
*/
|
||||
open val classLoader: ClassLoader = classLoader ?: parent?.classLoader ?: Global.classLoader
|
||||
|
||||
/**
|
||||
* A property showing that dispatch thread is started in the context
|
||||
*/
|
||||
private var started = false
|
||||
|
||||
/**
|
||||
* A dispatch thread executor for current context
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
val dispatcher: ExecutorService by lazy {
|
||||
logger.info("Initializing dispatch thread executor in {}", name)
|
||||
Executors.newSingleThreadExecutor { r ->
|
||||
Thread(r).apply {
|
||||
priority = 8 // slightly higher priority
|
||||
isDaemon = true
|
||||
name = this@JVMContext.name + "_dispatch"
|
||||
}.also { started = true }
|
||||
}
|
||||
}
|
||||
|
||||
private val serviceCache: MutableMap<Class<*>, ServiceLoader<*>> = HashMap()
|
||||
|
||||
override fun <T : Any> services(type: KClass<T>): Sequence<T> {
|
||||
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
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
override fun close() {
|
||||
if (isActive) error("Can't close active context")
|
||||
//detach all plugins
|
||||
plugins.forEach { it.detach() }
|
||||
|
||||
if (started) {
|
||||
dispatcher.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
private val activators = HashSet<WeakReference<Any>>()
|
||||
|
||||
override val isActive: Boolean = activators.all { it.get() == null }
|
||||
|
||||
override fun activate(activator: Any) {
|
||||
activators.add(WeakReference(activator))
|
||||
}
|
||||
|
||||
override fun deactivate(activator: Any) {
|
||||
activators.removeAll { it.get() == activator }
|
||||
}
|
||||
}
|
||||
|
25
dataforge-data/build.gradle
Normal file
25
dataforge-data/build.gradle
Normal file
@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id 'kotlin-multiplatform'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm')
|
||||
//fromPreset(presets.js, 'js')
|
||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||
//fromPreset(presets.iosX64, 'ios')
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":dataforge-context")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
dataforge-io/build.gradle
Normal file
25
dataforge-io/build.gradle
Normal file
@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id 'kotlin-multiplatform'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm')
|
||||
//fromPreset(presets.js, 'js')
|
||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||
//fromPreset(presets.iosX64, 'ios')
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":dataforge-context")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package hep.dataforge.meta.io
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.*
|
||||
import kotlinx.io.core.*
|
||||
|
||||
/**
|
||||
@ -20,20 +21,12 @@ fun MetaFormat.stringify(meta: Meta): String {
|
||||
return builder.build().readText()
|
||||
}
|
||||
|
||||
fun Meta.asString() = JSONMetaFormat.stringify(this)
|
||||
|
||||
fun MetaFormat.parse(str: String): Meta{
|
||||
return read(ByteReadPacket(str.toByteArray()))
|
||||
}
|
||||
|
||||
///**
|
||||
// * Resolve format by its name. Null if not provided
|
||||
// */
|
||||
//expect fun resolveFormat(name: String): MetaFormat?
|
||||
//
|
||||
///**
|
||||
// * Resolve format by its binary key. Null if not provided
|
||||
// */
|
||||
//expect fun resolveFormat(key: Short): MetaFormat?
|
||||
|
||||
internal expect fun writeJson(meta: Meta, out: Output)
|
||||
internal expect fun readJson(input: Input, length: Int = -1): Meta
|
||||
|
||||
@ -155,7 +148,7 @@ object BinaryMetaFormat : MetaFormat {
|
||||
(1..length).forEach { _ ->
|
||||
val name = readString()
|
||||
val item = readMetaItem()
|
||||
set(name, item)
|
||||
setItem(name, item)
|
||||
}
|
||||
}
|
||||
MetaItem.NodeItem(meta)
|
||||
@ -163,6 +156,5 @@ object BinaryMetaFormat : MetaFormat {
|
||||
else -> error("Unknown serialization key character: $keyChar")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -19,4 +19,18 @@ class MetaFormatTest{
|
||||
assertEquals(meta,result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJsonMetaFormat(){
|
||||
val meta = buildMeta {
|
||||
"a" to 22
|
||||
"node" to {
|
||||
"b" to "DDD"
|
||||
"c" to 11.1
|
||||
}
|
||||
}
|
||||
val string = JSONMetaFormat.stringify(meta)
|
||||
val result = JSONMetaFormat.parse(string)
|
||||
assertEquals(meta,result)
|
||||
}
|
||||
|
||||
}
|
@ -2,8 +2,8 @@ package hep.dataforge.meta.io
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.Value
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.values.Value
|
||||
|
||||
/**
|
||||
* Represent any js object as meta
|
||||
@ -16,7 +16,7 @@ class JSMeta(val obj: Any) : Meta {
|
||||
|
||||
private fun isList(obj: Any): Boolean = js("Array").isArray(obj) as Boolean
|
||||
|
||||
private fun isPrimitive(obj: Any?): Boolean = js("obj !== Object(obj)") as Boolean
|
||||
private fun isPrimitive(@Suppress("UNUSED_PARAMETER") obj: Any?): Boolean = js("obj !== Object(obj)") as Boolean
|
||||
|
||||
private fun convert(obj: Any?): MetaItem<out Meta> {
|
||||
return when (obj) {
|
||||
|
@ -5,10 +5,12 @@ import com.github.cliftonlabs.json_simple.JsonObject
|
||||
import com.github.cliftonlabs.json_simple.Jsoner
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.values.*
|
||||
import kotlinx.io.core.*
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.io.Reader
|
||||
import java.nio.ByteBuffer
|
||||
import java.text.ParseException
|
||||
|
||||
internal actual fun writeJson(meta: Meta, out: Output) {
|
||||
@ -31,7 +33,7 @@ private fun Value.toJson(): Any {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Meta.toJson(): JsonObject {
|
||||
fun Meta.toJson(): JsonObject {
|
||||
val builder = JsonObject()
|
||||
items.forEach { name, item ->
|
||||
when (item) {
|
||||
@ -60,9 +62,11 @@ internal actual fun readJson(input: Input, length: Int): Meta {
|
||||
}
|
||||
|
||||
override fun read(cbuf: CharArray, off: Int, len: Int): Int {
|
||||
val block = input.readText(Charsets.UTF_8, len).toCharArray()
|
||||
System.arraycopy(block, 0, cbuf, off, block.size)
|
||||
return block.size
|
||||
val buffer = ByteBuffer.allocate(len)
|
||||
val res = input.readAvailable(buffer)
|
||||
val chars = String(buffer.array()).toCharArray()
|
||||
System.arraycopy(chars, 0, cbuf, off, chars.size)
|
||||
return res
|
||||
}
|
||||
|
||||
}
|
||||
@ -103,13 +107,13 @@ private fun MetaBuilder.appendValue(key: String, value: Any?) {
|
||||
this[key] = value.toListValue()
|
||||
} else {
|
||||
val list = value.map<Any, Meta> {
|
||||
when(it){
|
||||
when (it) {
|
||||
is JsonObject -> it.toMeta()
|
||||
is JsonArray -> it.toListValue().toMeta()
|
||||
else -> Value.of(it).toMeta()
|
||||
}
|
||||
}
|
||||
setIndexed(key.toName(),list)
|
||||
setIndexed(key.toName(), list)
|
||||
}
|
||||
}
|
||||
is Number -> this[key] = NumberValue(value)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package hep.dataforge.meta
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
|
||||
//TODO add validator to configuration
|
||||
|
||||
@ -20,7 +21,7 @@ open class Config : MutableMetaNode<Config>() {
|
||||
fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
|
||||
this.items.mapValues { entry ->
|
||||
val item = entry.value
|
||||
builder[entry.key] = when (item) {
|
||||
builder[entry.key.toName()] = when (item) {
|
||||
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
|
||||
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package hep.dataforge.meta
|
||||
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
|
@ -7,7 +7,17 @@ import hep.dataforge.names.NameToken
|
||||
*
|
||||
*
|
||||
*/
|
||||
class Laminate(val layers: List<Meta>) : Meta {
|
||||
class Laminate(layers: List<Meta>) : Meta {
|
||||
|
||||
val layers: List<Meta> = layers.flatMap {
|
||||
if(it is Laminate){
|
||||
it.layers
|
||||
} else{
|
||||
listOf(it)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(vararg layers: Meta): this(layers.asList())
|
||||
|
||||
override val items: Map<NameToken, MetaItem<out Meta>>
|
||||
get() = layers.map { it.items.keys }.flatten().associateWith { key ->
|
||||
|
@ -5,7 +5,11 @@ import hep.dataforge.meta.MetaItem.NodeItem
|
||||
import hep.dataforge.meta.MetaItem.ValueItem
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.boolean
|
||||
|
||||
|
||||
/**
|
||||
* A member of the meta tree. Could be represented as one of following:
|
||||
@ -17,6 +21,14 @@ sealed class MetaItem<M : Meta> {
|
||||
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>()
|
||||
}
|
||||
|
||||
/**
|
||||
* The object that could be represented as [Meta]. Meta provided by [toMeta] method should fully represent object state.
|
||||
* Meaning that two states with the same meta are equal.
|
||||
*/
|
||||
interface MetaRepr {
|
||||
fun toMeta(): Meta
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities:
|
||||
* * [MetaItem.ValueItem] (leaf)
|
||||
@ -24,9 +36,11 @@ sealed class MetaItem<M : Meta> {
|
||||
*
|
||||
* * Same name siblings are supported via elements with the same [Name] but different queries
|
||||
*/
|
||||
interface Meta {
|
||||
interface Meta : MetaRepr {
|
||||
val items: Map<NameToken, MetaItem<out Meta>>
|
||||
|
||||
override fun toMeta(): Meta = this
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* A key for single value node
|
||||
@ -58,17 +72,32 @@ operator fun Meta.get(key: String): MetaItem<out Meta>? = get(key.toName())
|
||||
/**
|
||||
* Get all items matching given name.
|
||||
*/
|
||||
fun Meta.getByName(name: Name): Map<String, MetaItem<out Meta>> {
|
||||
fun Meta.getAll(name: Name): Map<String, MetaItem<out Meta>> {
|
||||
if (name.length == 0) error("Can't use empty name for that")
|
||||
val (body, query) = name.last()!!
|
||||
val regex = query.toRegex()
|
||||
return (this[name.cutLast()] as? NodeItem<*>)?.node?.items
|
||||
?.filter { it.key.body == body && (query.isEmpty()|| regex.matches(it.key.query)) }
|
||||
?.filter { it.key.body == body && (query.isEmpty() || regex.matches(it.key.query)) }
|
||||
?.mapKeys { it.key.query }
|
||||
?: emptyMap()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform meta to sequence of [Name]-[Value] pairs
|
||||
*/
|
||||
fun Meta.asValueSequence(): Sequence<Pair<Name, Value>> {
|
||||
return items.asSequence().flatMap { entry ->
|
||||
val item = entry.value
|
||||
when (item) {
|
||||
is ValueItem -> sequenceOf(entry.key.toName() to item.value)
|
||||
is NodeItem -> item.node.asValueSequence().map { pair -> (entry.key.toName() + pair.first) to pair.second }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun Meta.iterator(): Iterator<Pair<Name, Value>> = asValueSequence().iterator()
|
||||
|
||||
/**
|
||||
* A meta node that ensures that all of its descendants has at least the same type
|
||||
*/
|
||||
|
@ -1,6 +1,8 @@
|
||||
package hep.dataforge.meta
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.values.Value
|
||||
|
||||
/**
|
||||
* DSL builder for meta. Is not intended to store mutable state
|
||||
@ -36,7 +38,7 @@ fun Meta.builder(): MetaBuilder {
|
||||
return MetaBuilder().also { builder ->
|
||||
items.mapValues { entry ->
|
||||
val item = entry.value
|
||||
builder[entry.key] = when (item) {
|
||||
builder[entry.key.toName()] = when (item) {
|
||||
is MetaItem.ValueItem -> MetaItem.ValueItem<MetaBuilder>(item.value)
|
||||
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder())
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.values.Value
|
||||
|
||||
class MetaListener(val owner: Any? = null, val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit) {
|
||||
operator fun invoke(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) = action(name, oldItem, newItem)
|
||||
@ -77,7 +78,7 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableM
|
||||
|
||||
override operator fun set(name: Name, item: MetaItem<M>?) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't set meta item for empty name")
|
||||
0 -> error("Can't setValue meta item for empty name")
|
||||
1 -> {
|
||||
val token = name.first()!!
|
||||
replaceItem(token, get(name), item)
|
||||
@ -98,24 +99,27 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableM
|
||||
fun <M : MutableMeta<M>> M.remove(name: Name) = set(name, null)
|
||||
fun <M : MutableMeta<M>> M.remove(name: String) = remove(name.toName())
|
||||
|
||||
operator fun <M : MutableMeta<M>> M.set(name: Name, value: Value) = set(name, MetaItem.ValueItem(value))
|
||||
operator fun <M : MutableMetaNode<M>> M.set(name: Name, meta: Meta) = set(name, MetaItem.NodeItem(wrap(name, meta)))
|
||||
operator fun <M : MutableMeta<M>> M.set(name: String, item: MetaItem<M>) = set(name.toName(), item)
|
||||
operator fun <M : MutableMeta<M>> M.set(name: String, value: Value) = set(name.toName(), MetaItem.ValueItem(value))
|
||||
operator fun <M : MutableMetaNode<M>> M.set(name: String, meta: Meta) = set(name.toName(), meta)
|
||||
operator fun <M : MutableMeta<M>> M.set(token: NameToken, item: MetaItem<M>?) = set(token.toName(), item)
|
||||
fun <M : MutableMeta<M>> M.setValue(name: Name, value: Value) = set(name, MetaItem.ValueItem(value))
|
||||
fun <M : MutableMeta<M>> M.setItem(name: String, item: MetaItem<M>) = set(name.toName(), item)
|
||||
fun <M : MutableMeta<M>> M.setValue(name: String, value: Value) = set(name.toName(), MetaItem.ValueItem(value))
|
||||
fun <M : MutableMeta<M>> M.setItem(token: NameToken, item: MetaItem<M>?) = set(token.toName(), item)
|
||||
|
||||
fun <M : MutableMetaNode<M>> M.setNode(name: Name, node: Meta) = set(name, MetaItem.NodeItem(wrap(name, node)))
|
||||
fun <M : MutableMetaNode<M>> M.setNode(name: String, node: Meta) = setNode(name.toName(), node)
|
||||
|
||||
/**
|
||||
* Universal set method
|
||||
*/
|
||||
operator fun <M : MutableMeta<M>> M.set(key: String, value: Any?) {
|
||||
operator fun <M : MutableMetaNode<M>> M.set(name: Name, value: Any?) {
|
||||
when (value) {
|
||||
null -> remove(key)
|
||||
is Meta -> set(key, value)
|
||||
else -> set(key, Value.of(value))
|
||||
null -> remove(name)
|
||||
is Meta -> setNode(name, value)
|
||||
else -> setValue(name, Value.of(value))
|
||||
}
|
||||
}
|
||||
|
||||
operator fun <M : MutableMetaNode<M>> M.set(key: String, value: Any?) = set(key.toName(), value)
|
||||
|
||||
/**
|
||||
* Update existing mutable node with another node. The rules are following:
|
||||
* * value replaces anything
|
||||
@ -126,9 +130,9 @@ fun <M : MutableMetaNode<M>> M.update(meta: Meta) {
|
||||
meta.items.forEach { entry ->
|
||||
val value = entry.value
|
||||
when (value) {
|
||||
is MetaItem.ValueItem -> this[entry.key.toName()] = value.value
|
||||
is MetaItem.ValueItem -> setValue(entry.key.toName(),value.value)
|
||||
is MetaItem.NodeItem -> (this[entry.key.toName()] as? MetaItem.NodeItem)?.node?.update(value.node)
|
||||
?: run { this[entry.key.toName()] = value.node }
|
||||
?: run { setNode(entry.key.toName(),value.node)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ class StyledConfig(val config: Config, style: Meta = EmptyMeta) : Config() {
|
||||
override fun set(name: Name, item: MetaItem<Config>?) {
|
||||
when (item) {
|
||||
null -> config.remove(name)
|
||||
is MetaItem.ValueItem -> config[name] = item.value
|
||||
is MetaItem.NodeItem -> config[name] = item.node
|
||||
is MetaItem.ValueItem -> config.setValue(name, item.value)
|
||||
is MetaItem.NodeItem -> config.setNode(name, item.node)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ package hep.dataforge.names
|
||||
* The name is a dot separated list of strings like `token1.token2.token3`.
|
||||
* Each token could contain additional query in square brackets.
|
||||
*/
|
||||
class Name internal constructor(val tokens: List<NameToken>) {
|
||||
inline class Name constructor(val tokens: List<NameToken>) {
|
||||
|
||||
val length
|
||||
get() = tokens.size
|
||||
@ -35,19 +35,6 @@ class Name internal constructor(val tokens: List<NameToken>) {
|
||||
|
||||
override fun toString(): String = tokens.joinToString(separator = NAME_SEPARATOR) { it.toString() }
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Name) return false
|
||||
|
||||
if (tokens != other.tokens) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return tokens.hashCode()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NAME_SEPARATOR = "."
|
||||
}
|
||||
@ -80,7 +67,7 @@ fun String.toName(): Name {
|
||||
var bracketCount: Int = 0
|
||||
fun queryOn() = bracketCount > 0
|
||||
|
||||
this@toName.asSequence().forEach {
|
||||
asSequence().forEach {
|
||||
if (queryOn()) {
|
||||
when (it) {
|
||||
'[' -> bracketCount++
|
||||
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.meta
|
||||
package hep.dataforge.values
|
||||
|
||||
|
||||
/**
|
||||
@ -44,19 +44,22 @@ interface Value {
|
||||
get() = listOf(this)
|
||||
|
||||
companion object {
|
||||
const val VALUE_TARGET = "value"
|
||||
|
||||
/**
|
||||
* Convert object to value
|
||||
*/
|
||||
fun of(value: Any?): Value {
|
||||
return when (value) {
|
||||
null -> Null
|
||||
is Value -> value
|
||||
true -> True
|
||||
false -> False
|
||||
is Number -> NumberValue(value)
|
||||
is Iterable<*> -> ListValue(value.map { of(it) })
|
||||
is Enum<*> -> EnumValue(value)
|
||||
is CharSequence -> StringValue(value.toString())
|
||||
else -> throw IllegalArgumentException("Unrecognized type of the object converted to Value")
|
||||
else -> throw IllegalArgumentException("Unrecognized type of the object (${value::class}) converted to Value")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,6 +73,8 @@ object Null : Value {
|
||||
override val type: ValueType get() = ValueType.NULL
|
||||
override val number: Number get() = Double.NaN
|
||||
override val string: String get() = "@null"
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,6 +91,8 @@ object True : Value {
|
||||
override val type: ValueType get() = ValueType.BOOLEAN
|
||||
override val number: Number get() = 1.0
|
||||
override val string: String get() = "+"
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,12 +113,21 @@ class NumberValue(override val number: Number) : Value {
|
||||
override val string: String get() = number.toString()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this.number == (other as? Value)?.number
|
||||
if (other !is Value) return false
|
||||
return when (number) {
|
||||
is Short -> number == other.number.toShort()
|
||||
is Long -> number == other.number.toLong()
|
||||
is Byte -> number == other.number.toByte()
|
||||
is Int -> number == other.number.toInt()
|
||||
is Float -> number == other.number.toFloat()
|
||||
is Double -> number == other.number.toDouble()
|
||||
else -> number.toString() == other.number.toString()
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = number.hashCode()
|
||||
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
class StringValue(override val string: String) : Value {
|
||||
@ -123,7 +139,9 @@ class StringValue(override val string: String) : Value {
|
||||
return this.string == (other as? Value)?.string
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = string.hashCode()
|
||||
override fun hashCode(): Int = string.hashCode()
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
class EnumValue<E : Enum<*>>(override val value: E) : Value {
|
||||
@ -136,6 +154,8 @@ class EnumValue<E : Enum<*>>(override val value: E) : Value {
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = value.hashCode()
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
class ListValue(override val list: List<Value>) : Value {
|
||||
@ -149,6 +169,8 @@ class ListValue(override val list: List<Value>) : Value {
|
||||
override val type: ValueType get() = list.first().type
|
||||
override val number: Number get() = list.first().number
|
||||
override val string: String get() = list.first().string
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,8 +223,8 @@ fun String.parseValue(): Value {
|
||||
return StringValue(this)
|
||||
}
|
||||
|
||||
class LazyParsedValue(override val string: String): Value{
|
||||
private val parsedValue by lazy { string.parseValue() }
|
||||
class LazyParsedValue(override val string: String) : Value {
|
||||
private val parsedValue by lazy { string.parseValue() }
|
||||
|
||||
override val value: Any?
|
||||
get() = parsedValue.value
|
||||
@ -210,4 +232,6 @@ class LazyParsedValue(override val string: String): Value{
|
||||
get() = parsedValue.type
|
||||
override val number: Number
|
||||
get() = parsedValue.number
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package hep.dataforge.meta
|
||||
|
||||
import hep.dataforge.values.asValue
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
package hep.dataforge.meta
|
||||
|
||||
import hep.dataforge.values.NumberValue
|
||||
import hep.dataforge.values.True
|
||||
import hep.dataforge.values.Value
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class MetaTest {
|
||||
@Test
|
||||
fun valueEqualityTest() {
|
||||
assertEquals(NumberValue(22), NumberValue(22))
|
||||
assertEquals(NumberValue(22.0), NumberValue(22))
|
||||
assertEquals(True, Value.of(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun metaEqualityTest() {
|
||||
val meta1 = buildMeta {
|
||||
"a" to 22
|
||||
"b" to {
|
||||
"c" to "ddd"
|
||||
}
|
||||
}
|
||||
val meta2 = buildMeta {
|
||||
"b" to {
|
||||
"c" to "ddd"
|
||||
}
|
||||
"a" to 22
|
||||
}.seal()
|
||||
assertEquals<Meta>(meta1, meta2)
|
||||
}
|
||||
}
|
@ -9,4 +9,11 @@ class NameTest{
|
||||
val name = "token1.token2.token3".toName()
|
||||
assertEquals("token2", name[1].toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun equalityTest(){
|
||||
val name1 = "token1.token2[2].token3".toName()
|
||||
val name2 = "token1".toName() + "token2[2].token3"
|
||||
assertEquals(name1,name2)
|
||||
}
|
||||
}
|
25
dataforge-tables/build.gradle
Normal file
25
dataforge-tables/build.gradle
Normal file
@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id 'kotlin-multiplatform'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm')
|
||||
//fromPreset(presets.js, 'js')
|
||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||
//fromPreset(presets.iosX64, 'ios')
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":dataforge-context")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
dataforge-workspace/build.gradle
Normal file
25
dataforge-workspace/build.gradle
Normal file
@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id 'kotlin-multiplatform'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
targets {
|
||||
fromPreset(presets.jvm, 'jvm')
|
||||
//fromPreset(presets.js, 'js')
|
||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
||||
//fromPreset(presets.iosX64, 'ios')
|
||||
}
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":dataforge-context")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -27,4 +27,12 @@ rootProject.name = 'dataforge-core'
|
||||
|
||||
include ":dataforge-meta"
|
||||
include ":dataforge-meta-io"
|
||||
|
||||
include ":dataforge-context"
|
||||
include ":dataforge-data"
|
||||
include ":dataforge-io"
|
||||
|
||||
include ":dataforge-tables"
|
||||
|
||||
include ":dataforge-workspace"
|
||||
//include ":dataforge-envelope"
|
Loading…
Reference in New Issue
Block a user