Context module
This commit is contained in:
parent
dd05897759
commit
d3ce88eb3f
@ -20,9 +20,7 @@ allprojects {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url = "http://dl.bintray.com/kotlin/kotlin-eap" }
|
|
||||||
maven { url = "https://kotlin.bintray.com/kotlinx" }
|
maven { url = "https://kotlin.bintray.com/kotlinx" }
|
||||||
//maven { url 'https://jitpack.io' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'hep.dataforge'
|
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
|
package hep.dataforge.meta.io
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.values.*
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,20 +21,12 @@ fun MetaFormat.stringify(meta: Meta): String {
|
|||||||
return builder.build().readText()
|
return builder.build().readText()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Meta.asString() = JSONMetaFormat.stringify(this)
|
||||||
|
|
||||||
fun MetaFormat.parse(str: String): Meta{
|
fun MetaFormat.parse(str: String): Meta{
|
||||||
return read(ByteReadPacket(str.toByteArray()))
|
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 writeJson(meta: Meta, out: Output)
|
||||||
internal expect fun readJson(input: Input, length: Int = -1): Meta
|
internal expect fun readJson(input: Input, length: Int = -1): Meta
|
||||||
|
|
||||||
@ -155,7 +148,7 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
(1..length).forEach { _ ->
|
(1..length).forEach { _ ->
|
||||||
val name = readString()
|
val name = readString()
|
||||||
val item = readMetaItem()
|
val item = readMetaItem()
|
||||||
set(name, item)
|
setItem(name, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MetaItem.NodeItem(meta)
|
MetaItem.NodeItem(meta)
|
||||||
@ -163,6 +156,5 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
else -> error("Unknown serialization key character: $keyChar")
|
else -> error("Unknown serialization key character: $keyChar")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,4 +19,18 @@ class MetaFormatTest{
|
|||||||
assertEquals(meta,result)
|
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.Meta
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.meta.Value
|
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.values.Value
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent any js object as meta
|
* 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 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> {
|
private fun convert(obj: Any?): MetaItem<out Meta> {
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
|
@ -5,10 +5,12 @@ import com.github.cliftonlabs.json_simple.JsonObject
|
|||||||
import com.github.cliftonlabs.json_simple.Jsoner
|
import com.github.cliftonlabs.json_simple.Jsoner
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.values.*
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
|
|
||||||
internal actual fun writeJson(meta: Meta, out: Output) {
|
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()
|
val builder = JsonObject()
|
||||||
items.forEach { name, item ->
|
items.forEach { name, item ->
|
||||||
when (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 {
|
override fun read(cbuf: CharArray, off: Int, len: Int): Int {
|
||||||
val block = input.readText(Charsets.UTF_8, len).toCharArray()
|
val buffer = ByteBuffer.allocate(len)
|
||||||
System.arraycopy(block, 0, cbuf, off, block.size)
|
val res = input.readAvailable(buffer)
|
||||||
return block.size
|
val chars = String(buffer.array()).toCharArray()
|
||||||
|
System.arraycopy(chars, 0, cbuf, off, chars.size)
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
//TODO add validator to configuration
|
//TODO add validator to configuration
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ open class Config : MutableMetaNode<Config>() {
|
|||||||
fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
|
fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
|
||||||
this.items.mapValues { entry ->
|
this.items.mapValues { entry ->
|
||||||
val item = entry.value
|
val item = entry.value
|
||||||
builder[entry.key] = when (item) {
|
builder[entry.key.toName()] = when (item) {
|
||||||
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
|
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
|
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.values.Null
|
||||||
|
import hep.dataforge.values.Value
|
||||||
import kotlin.jvm.JvmName
|
import kotlin.jvm.JvmName
|
||||||
import kotlin.properties.ReadOnlyProperty
|
import kotlin.properties.ReadOnlyProperty
|
||||||
import kotlin.properties.ReadWriteProperty
|
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>>
|
override val items: Map<NameToken, MetaItem<out Meta>>
|
||||||
get() = layers.map { it.items.keys }.flatten().associateWith { key ->
|
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.meta.MetaItem.ValueItem
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
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:
|
* 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>()
|
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:
|
* Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities:
|
||||||
* * [MetaItem.ValueItem] (leaf)
|
* * [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
|
* * 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>>
|
val items: Map<NameToken, MetaItem<out Meta>>
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = this
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* A key for single value node
|
* A key for single value node
|
||||||
@ -58,7 +72,7 @@ operator fun Meta.get(key: String): MetaItem<out Meta>? = get(key.toName())
|
|||||||
/**
|
/**
|
||||||
* Get all items matching given name.
|
* 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")
|
if (name.length == 0) error("Can't use empty name for that")
|
||||||
val (body, query) = name.last()!!
|
val (body, query) = name.last()!!
|
||||||
val regex = query.toRegex()
|
val regex = query.toRegex()
|
||||||
@ -69,6 +83,21 @@ fun Meta.getByName(name: Name): Map<String, MetaItem<out Meta>> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* A meta node that ensures that all of its descendants has at least the same type
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
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
|
* DSL builder for meta. Is not intended to store mutable state
|
||||||
@ -36,7 +38,7 @@ fun Meta.builder(): MetaBuilder {
|
|||||||
return MetaBuilder().also { builder ->
|
return MetaBuilder().also { builder ->
|
||||||
items.mapValues { entry ->
|
items.mapValues { entry ->
|
||||||
val item = entry.value
|
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.ValueItem -> MetaItem.ValueItem<MetaBuilder>(item.value)
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder())
|
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.NameToken
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
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) {
|
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)
|
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>?) {
|
override operator fun set(name: Name, item: MetaItem<M>?) {
|
||||||
when (name.length) {
|
when (name.length) {
|
||||||
0 -> error("Can't set meta item for empty name")
|
0 -> error("Can't setValue meta item for empty name")
|
||||||
1 -> {
|
1 -> {
|
||||||
val token = name.first()!!
|
val token = name.first()!!
|
||||||
replaceItem(token, get(name), item)
|
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: Name) = set(name, null)
|
||||||
fun <M : MutableMeta<M>> M.remove(name: String) = remove(name.toName())
|
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))
|
fun <M : MutableMeta<M>> M.setValue(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)))
|
fun <M : MutableMeta<M>> M.setItem(name: String, item: MetaItem<M>) = set(name.toName(), item)
|
||||||
operator fun <M : MutableMeta<M>> M.set(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))
|
||||||
operator fun <M : MutableMeta<M>> M.set(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)
|
||||||
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 : 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
|
* 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) {
|
when (value) {
|
||||||
null -> remove(key)
|
null -> remove(name)
|
||||||
is Meta -> set(key, value)
|
is Meta -> setNode(name, value)
|
||||||
else -> set(key, Value.of(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:
|
* Update existing mutable node with another node. The rules are following:
|
||||||
* * value replaces anything
|
* * value replaces anything
|
||||||
@ -126,9 +130,9 @@ fun <M : MutableMetaNode<M>> M.update(meta: Meta) {
|
|||||||
meta.items.forEach { entry ->
|
meta.items.forEach { entry ->
|
||||||
val value = entry.value
|
val value = entry.value
|
||||||
when (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)
|
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>?) {
|
override fun set(name: Name, item: MetaItem<Config>?) {
|
||||||
when (item) {
|
when (item) {
|
||||||
null -> config.remove(name)
|
null -> config.remove(name)
|
||||||
is MetaItem.ValueItem -> config[name] = item.value
|
is MetaItem.ValueItem -> config.setValue(name, item.value)
|
||||||
is MetaItem.NodeItem -> config[name] = item.node
|
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`.
|
* The name is a dot separated list of strings like `token1.token2.token3`.
|
||||||
* Each token could contain additional query in square brackets.
|
* 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
|
val length
|
||||||
get() = tokens.size
|
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 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 {
|
companion object {
|
||||||
const val NAME_SEPARATOR = "."
|
const val NAME_SEPARATOR = "."
|
||||||
}
|
}
|
||||||
@ -80,7 +67,7 @@ fun String.toName(): Name {
|
|||||||
var bracketCount: Int = 0
|
var bracketCount: Int = 0
|
||||||
fun queryOn() = bracketCount > 0
|
fun queryOn() = bracketCount > 0
|
||||||
|
|
||||||
this@toName.asSequence().forEach {
|
asSequence().forEach {
|
||||||
if (queryOn()) {
|
if (queryOn()) {
|
||||||
when (it) {
|
when (it) {
|
||||||
'[' -> bracketCount++
|
'[' -> bracketCount++
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.values
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,19 +44,22 @@ interface Value {
|
|||||||
get() = listOf(this)
|
get() = listOf(this)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val VALUE_TARGET = "value"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert object to value
|
* Convert object to value
|
||||||
*/
|
*/
|
||||||
fun of(value: Any?): Value {
|
fun of(value: Any?): Value {
|
||||||
return when (value) {
|
return when (value) {
|
||||||
null -> Null
|
null -> Null
|
||||||
|
is Value -> value
|
||||||
true -> True
|
true -> True
|
||||||
false -> False
|
false -> False
|
||||||
is Number -> NumberValue(value)
|
is Number -> NumberValue(value)
|
||||||
is Iterable<*> -> ListValue(value.map { of(it) })
|
is Iterable<*> -> ListValue(value.map { of(it) })
|
||||||
is Enum<*> -> EnumValue(value)
|
is Enum<*> -> EnumValue(value)
|
||||||
is CharSequence -> StringValue(value.toString())
|
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 type: ValueType get() = ValueType.NULL
|
||||||
override val number: Number get() = Double.NaN
|
override val number: Number get() = Double.NaN
|
||||||
override val string: String get() = "@null"
|
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 type: ValueType get() = ValueType.BOOLEAN
|
||||||
override val number: Number get() = 1.0
|
override val number: Number get() = 1.0
|
||||||
override val string: String get() = "+"
|
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 val string: String get() = number.toString()
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
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 hashCode(): Int = number.hashCode()
|
||||||
|
|
||||||
|
override fun toString(): String = value.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
class StringValue(override val string: String) : Value {
|
class StringValue(override val string: String) : Value {
|
||||||
@ -124,6 +140,8 @@ class StringValue(override val string: String) : Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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 hashCode(): Int = value.hashCode()
|
||||||
|
|
||||||
|
override fun toString(): String = value.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListValue(override val list: List<Value>) : Value {
|
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 type: ValueType get() = list.first().type
|
||||||
override val number: Number get() = list.first().number
|
override val number: Number get() = list.first().number
|
||||||
override val string: String get() = list.first().string
|
override val string: String get() = list.first().string
|
||||||
|
|
||||||
|
override fun toString(): String = value.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -210,4 +232,6 @@ class LazyParsedValue(override val string: String): Value{
|
|||||||
get() = parsedValue.type
|
get() = parsedValue.type
|
||||||
override val number: Number
|
override val number: Number
|
||||||
get() = parsedValue.number
|
get() = parsedValue.number
|
||||||
|
|
||||||
|
override fun toString(): String = value.toString()
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.values.asValue
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
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()
|
val name = "token1.token2.token3".toName()
|
||||||
assertEquals("token2", name[1].toString())
|
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"
|
||||||
include ":dataforge-meta-io"
|
include ":dataforge-meta-io"
|
||||||
|
|
||||||
|
include ":dataforge-context"
|
||||||
|
include ":dataforge-data"
|
||||||
|
include ":dataforge-io"
|
||||||
|
|
||||||
|
include ":dataforge-tables"
|
||||||
|
|
||||||
|
include ":dataforge-workspace"
|
||||||
//include ":dataforge-envelope"
|
//include ":dataforge-envelope"
|
Loading…
Reference in New Issue
Block a user