From 81660f3ca758814e391c0a73be74d9e9d225a14f Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 14 Sep 2018 22:12:34 +0300 Subject: [PATCH] Optimized generics added jvm module for tests --- dataforge-meta-jvm/build.gradle | 27 ++++++++++++++ .../hep/dataforge/meta/Configuration.kt | 23 ++++++++++++ src/main/kotlin/hep/dataforge/meta/Meta.kt | 37 ++++++++++++++----- .../kotlin/hep/dataforge/meta/MetaBuilder.kt | 10 +++-- .../kotlin/hep/dataforge/meta/MetaUtils.kt | 6 +-- .../{MutableMeta.kt => MutableMetaNode.kt} | 19 ++++++---- src/main/kotlin/hep/dataforge/names/Name.kt | 6 ++- .../hep/dataforge/meta/MetaBuilderTest.kt | 25 +++++++++++++ .../kotlin/hep/dataforge/names/NameTest.kt | 2 +- 9 files changed, 129 insertions(+), 26 deletions(-) create mode 100644 dataforge-meta-jvm/build.gradle create mode 100644 src/main/kotlin/hep/dataforge/meta/Configuration.kt rename src/main/kotlin/hep/dataforge/meta/{MutableMeta.kt => MutableMetaNode.kt} (80%) create mode 100644 src/test/kotlin/hep/dataforge/meta/MetaBuilderTest.kt diff --git a/dataforge-meta-jvm/build.gradle b/dataforge-meta-jvm/build.gradle new file mode 100644 index 00000000..99ac4a6d --- /dev/null +++ b/dataforge-meta-jvm/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'kotlin-platform-jvm' +} + +group 'hep.dataforge' +version '0.1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + expectedBy rootProject + + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + testCompile "junit:junit:4.12" + testCompile "org.jetbrains.kotlin:kotlin-test" + testCompile "org.jetbrains.kotlin:kotlin-test-junit" +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} +sourceCompatibility = "1.8" \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/Configuration.kt b/src/main/kotlin/hep/dataforge/meta/Configuration.kt new file mode 100644 index 00000000..b9e9ea4d --- /dev/null +++ b/src/main/kotlin/hep/dataforge/meta/Configuration.kt @@ -0,0 +1,23 @@ +package hep.dataforge.meta + +//TODO add validator to configuration + +class Configuration: MutableMetaNode() { + /** + * Attach configuration node instead of creating one + */ + override fun wrap(meta: Meta): Configuration { + return meta as? Configuration ?: Configuration().also { builder -> + items.mapValues { entry -> + val item = entry.value + builder[entry.key] = when (item) { + is MetaItem.ValueItem -> MetaItem.ValueItem(item.value) + is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(wrap(item.node)) + is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(item.nodes.map { wrap(it) }) + } + } + } + } + + override fun empty(): Configuration = Configuration() +} \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/Meta.kt b/src/main/kotlin/hep/dataforge/meta/Meta.kt index a322946f..0a662e0b 100644 --- a/src/main/kotlin/hep/dataforge/meta/Meta.kt +++ b/src/main/kotlin/hep/dataforge/meta/Meta.kt @@ -1,7 +1,6 @@ package hep.dataforge.meta import hep.dataforge.names.Name -import hep.dataforge.names.NameToken import hep.dataforge.names.toName /** @@ -10,13 +9,13 @@ import hep.dataforge.names.toName * * a single node * * a list of nodes */ -sealed class MetaItem> { - class ValueItem>(val value: Value) : MetaItem() - class SingleNodeItem>(val node: M) : MetaItem() - class MultiNodeItem>(val nodes: List) : MetaItem() +sealed class MetaItem { + class ValueItem(val value: Value) : MetaItem() + class SingleNodeItem(val node: M) : MetaItem() + class MultiNodeItem(val nodes: List) : MetaItem() } -operator fun > List.get(query: String): M? { +operator fun List.get(query: String): M? { return if (query.isEmpty()) { first() } else { @@ -31,8 +30,26 @@ operator fun > List.get(query: String): M? { * * [MetaItem.SingleNodeItem] single node * * [MetaItem.MultiNodeItem] multi-value node */ -interface Meta> { - val items: Map> +interface Meta { + val items: Map> +} + +operator fun Meta.get(name: Name): MetaItem? { + return when (name.length) { + 0 -> error("Can't resolve element from empty name") + 1 -> items[name.first()!!.body] + else -> name.first()!!.let{ token -> items[token.body]?.nodes?.get(token.query)}?.get(name.cutFirst()) + } +} + +//TODO create Java helper for meta operations +operator fun Meta.get(key: String): MetaItem? = get(key.toName()) + +/** + * A meta node that ensures that all of its descendants has at least the same type + */ +abstract class MetaNode>: Meta{ + abstract override val items: Map> operator fun get(name: Name): MetaItem? { return when (name.length) { @@ -50,7 +67,7 @@ interface Meta> { * * If the argument is possibly mutable node, it is copied on creation */ -class SealedMeta(meta: Meta<*>) : Meta { +class SealedMeta(meta: Meta) : MetaNode() { override val items: Map> = if (meta is SealedMeta) { meta.items } else { @@ -68,4 +85,4 @@ class SealedMeta(meta: Meta<*>) : Meta { /** * Generate sealed node from [this]. If it is already sealed return it as is */ -fun Meta<*>.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(this) \ No newline at end of file +fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(this) \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt b/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt index 8a6cdb9d..c4aa7592 100644 --- a/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt +++ b/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt @@ -3,8 +3,8 @@ package hep.dataforge.meta /** * DSL builder for meta */ -class MetaBuilder : MutableMeta() { - override fun wrap(meta: Meta<*>): MetaBuilder = meta.builder() +class MetaBuilder : MutableMetaNode() { + override fun wrap(meta: Meta): MetaBuilder = meta.builder() override fun empty(): MetaBuilder = MetaBuilder() infix fun String.to(value: Any) { @@ -19,7 +19,7 @@ class MetaBuilder : MutableMeta() { /** * For safety, builder always copies the initial meta even if it is builder itself */ -fun Meta<*>.builder(): MetaBuilder { +fun Meta.builder(): MetaBuilder { return MetaBuilder().also { builder -> items.mapValues { entry -> val item = entry.value @@ -30,4 +30,6 @@ fun Meta<*>.builder(): MetaBuilder { } } } -} \ No newline at end of file +} + +fun buildMeta(builder: MetaBuilder.() -> Unit): Meta = MetaBuilder().apply(builder) \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/MetaUtils.kt b/src/main/kotlin/hep/dataforge/meta/MetaUtils.kt index baafde2a..ef8b8880 100644 --- a/src/main/kotlin/hep/dataforge/meta/MetaUtils.kt +++ b/src/main/kotlin/hep/dataforge/meta/MetaUtils.kt @@ -13,7 +13,7 @@ val MetaItem<*>.double get() = number.toDouble() val MetaItem<*>.int get() = number.toInt() val MetaItem<*>.long get() = number.toLong() -val > MetaItem.node: M +val MetaItem.node: M get() = when (this) { is MetaItem.ValueItem -> error("Trying to interpret value meta item as node item") is MetaItem.SingleNodeItem -> node @@ -24,14 +24,14 @@ val > MetaItem.node: M * Utility method to access item content as list of nodes. * Returns empty list if it is value item. */ -val > MetaItem.nodes: List +val MetaItem.nodes: List get() = when (this) { is MetaItem.ValueItem -> emptyList()//error("Trying to interpret value meta item as node item") is MetaItem.SingleNodeItem -> listOf(node) is MetaItem.MultiNodeItem -> nodes } -fun > MetaItem.indexOf(meta: M): Int { +fun MetaItem.indexOf(meta: M): Int { return when (this) { is MetaItem.ValueItem -> -1 is MetaItem.SingleNodeItem -> if (node == meta) 0 else -1 diff --git a/src/main/kotlin/hep/dataforge/meta/MutableMeta.kt b/src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt similarity index 80% rename from src/main/kotlin/hep/dataforge/meta/MutableMeta.kt rename to src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt index 80c2e6d5..c74d54dc 100644 --- a/src/main/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt @@ -8,10 +8,15 @@ class MetaListener(val owner: Any? = null, val action: (name: Name, oldItem: Met operator fun invoke(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) = action(name, oldItem, newItem) } + +interface MutableMeta>: Meta{ + operator fun set(name: Name, item: MetaItem) +} + /** * A mutable meta node with attachable change listener */ -abstract class MutableMeta> : Meta { +abstract class MutableMetaNode> : MetaNode(), MutableMeta { private val listeners = HashSet() /** @@ -57,14 +62,14 @@ abstract class MutableMeta> : Meta { /** * Transform given meta to node type of this meta tree */ - protected abstract fun wrap(meta: Meta<*>): M + protected abstract fun wrap(meta: Meta): M /** * Create empty node */ protected abstract fun empty(): M - operator fun set(name: Name, item: MetaItem) { + override operator fun set(name: Name, item: MetaItem) { when (name.length) { 0 -> error("Can't set meta item for empty name") 1 -> { @@ -83,11 +88,11 @@ abstract class MutableMeta> : Meta { } operator fun set(name: Name, value: Value) = set(name, MetaItem.ValueItem(value)) - operator fun set(name: Name, meta: Meta<*>) = set(name, MetaItem.SingleNodeItem(wrap(meta))) - operator fun set(name: Name, metas: List>) = set(name, MetaItem.MultiNodeItem(metas.map { wrap(it) })) + operator fun set(name: Name, meta: Meta) = set(name, MetaItem.SingleNodeItem(wrap(meta))) + operator fun set(name: Name, metas: List) = set(name, MetaItem.MultiNodeItem(metas.map { wrap(it) })) operator fun set(name: String, item: MetaItem) = set(name.toName(), item) operator fun set(name: String, value: Value) = set(name.toName(), MetaItem.ValueItem(value)) - operator fun set(name: String, meta: Meta<*>) = set(name.toName(), MetaItem.SingleNodeItem(wrap(meta))) - operator fun set(name: String, metas: List>) = set(name.toName(), MetaItem.MultiNodeItem(metas.map { wrap(it) })) + operator fun set(name: String, meta: Meta) = set(name.toName(), MetaItem.SingleNodeItem(wrap(meta))) + operator fun set(name: String, metas: List) = set(name.toName(), MetaItem.MultiNodeItem(metas.map { wrap(it) })) } \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/names/Name.kt b/src/main/kotlin/hep/dataforge/names/Name.kt index 233582fb..6ac98d8d 100644 --- a/src/main/kotlin/hep/dataforge/names/Name.kt +++ b/src/main/kotlin/hep/dataforge/names/Name.kt @@ -65,7 +65,11 @@ data class NameToken internal constructor(val body: String, val query: String) { if (body.isEmpty()) error("Syntax error: Name token body is empty") } - override fun toString(): String = "$body[$query]" + override fun toString(): String = if (hasQuery()) { + "$body[$query]" + } else { + body + } fun hasQuery() = query.isNotEmpty() } diff --git a/src/test/kotlin/hep/dataforge/meta/MetaBuilderTest.kt b/src/test/kotlin/hep/dataforge/meta/MetaBuilderTest.kt new file mode 100644 index 00000000..26e10354 --- /dev/null +++ b/src/test/kotlin/hep/dataforge/meta/MetaBuilderTest.kt @@ -0,0 +1,25 @@ +package hep.dataforge.meta + +import kotlin.test.Test +import kotlin.test.assertEquals + + +class MetaBuilderTest{ + @Test + fun testBuilder(){ + val meta = buildMeta { + "a" to 22 + "b" to listOf(1,2,3) + this["c"] = "myValue".asValue() + "node" to { + "e" to 12.2 + "childNode" to { + "f" to true + } + } + } + assertEquals(12.2, meta["node.e"]?.double) + assertEquals(true, meta["node.childNode.f"]?.boolean) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/hep/dataforge/names/NameTest.kt b/src/test/kotlin/hep/dataforge/names/NameTest.kt index 3d4a13b9..764d23c2 100644 --- a/src/test/kotlin/hep/dataforge/names/NameTest.kt +++ b/src/test/kotlin/hep/dataforge/names/NameTest.kt @@ -7,6 +7,6 @@ class NameTest{ @Test fun simpleName(){ val name = "token1.token2.token3".toName() - assertEquals("token2", name[2].toString()) + assertEquals("token2", name[1].toString()) } } \ No newline at end of file