From d137a83471e5286742a6d733e26e0c53dc18e7f9 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 11 Jun 2015 23:33:35 +0300 Subject: [PATCH] Simple extensions to delegate readonly property to Map and read-write property to MutableMap without delegate wrappers. Delegation to map with custom key selector by map accessors. --- .../src/kotlin/properties/MapAccessors.kt | 103 +++++++++++++++++ .../properties/delegation/MapAccessorsTest.kt | 107 ++++++++++++++++++ .../delegation/MapDelegationTest.kt | 4 + 3 files changed, 214 insertions(+) create mode 100644 libraries/stdlib/src/kotlin/properties/MapAccessors.kt create mode 100644 libraries/stdlib/test/properties/delegation/MapAccessorsTest.kt diff --git a/libraries/stdlib/src/kotlin/properties/MapAccessors.kt b/libraries/stdlib/src/kotlin/properties/MapAccessors.kt new file mode 100644 index 00000000000..0ada7df5c8c --- /dev/null +++ b/libraries/stdlib/src/kotlin/properties/MapAccessors.kt @@ -0,0 +1,103 @@ +package kotlin.properties + +import kotlin.platform.platformName + +// extensions for Map and MutableMap + +public fun Map.get(thisRef: Any?, property: PropertyMetadata): V { + val value = this.get(property.name) + if (value == null && !containsKey(property.name)) + defaultValueProvider(thisRef, property.name) + return value as V +} + +platformName("getVar") +public fun MutableMap.get(thisRef: Any?, property: PropertyMetadata): V { + val value = this.get(property.name) + if (value == null && !containsKey(property.name)) + defaultValueProvider(thisRef, property.name) + return value as V +} + +public fun MutableMap.set(thisRef: Any?, property: PropertyMetadata, value: V) { + this.put(property.name, value) +} + + + +// Map accessors + +public interface MapAccessor +{ + public val map: Map + + /** Returns `true` if the map contains the specified [key]. */ + public fun containsKey(key: KAccess): Boolean + + /** Returns the value corresponding to the given [key], or the value provided with [default] function if such a key is not present in the map. */ + public fun getOrDefault(key: KAccess): V + + public open fun withDefault(default: (KMap) -> V): MapAccessor +} + +public interface MutableMapAccessor: MapAccessor { + public override val map: MutableMap + /** Associates the specified [value] with the specified [key] in the map. */ + public fun put(key: KAccess, value: V): V? + + public override fun withDefault(default: (KMap) -> V): MutableMapAccessor +} + + +public fun Map.forProperties(): MapAccessor = MapAccessorImpl(this, defaultKeyProvider) + +// todo: inline keyselector into anonymous object +public fun Map.forProperties(keySelector: (PropertyMetadata) -> K): MapAccessor = MapAccessorImpl(this, keySelector) + +public fun MutableMap.forProperties(): MutableMapAccessor = MutableMapAccessorImpl(this, defaultKeyProvider) + +// todo: inline keyselector into anonymous object +public fun MutableMap.forProperties(keySelector: (PropertyMetadata) -> K): MutableMapAccessor = MutableMapAccessorImpl(this, keySelector) + + +// extensions to delegate properties to MapAccessors + +public fun MapAccessor.get(thisRef: Any?, property: PropertyMetadata): V = this.getOrDefault(property) + +platformName("getVar") +public fun MutableMapAccessor.get(thisRef: Any?, property: PropertyMetadata): V = this.getOrDefault(property) as V + +public fun MutableMapAccessor.set(thisRef: Any?, property: PropertyMetadata, value: V) { + this.put(property, value) +} + + + +// MapAccessor implementations + +private open class MapAccessorImpl(public override val map: Map, public val transform: (KAccess) -> KMap, public val default: (KMap) -> V = { k1 -> defaultValueProvider(map, k1) }) +: MapAccessor { + + override fun containsKey(key: KAccess): Boolean = map.containsKey(transform(key)) + + override fun getOrDefault(key: KAccess): V { + val key1 = transform(key) + val value = map.get(key1) + if (value == null && !map.containsKey(key1)) + return default(key1) + else + return value as V + } + + override fun withDefault(default: (KMap) -> V): MapAccessor = MapAccessorImpl(map as Map, transform, default) +} + + +private class MutableMapAccessorImpl(public override val map: MutableMap, transform: (KAccess) -> KMap, default: (KMap) -> V = { k1 -> defaultValueProvider(map, k1) }) +: MapAccessorImpl(map, transform, default), MutableMapAccessor { + + + override fun put(key: KAccess, value: V): V? = map.put(transform(key), value) + + override fun withDefault(default: (KMap) -> V): MutableMapAccessor = MutableMapAccessorImpl(map as MutableMap, transform, default) +} diff --git a/libraries/stdlib/test/properties/delegation/MapAccessorsTest.kt b/libraries/stdlib/test/properties/delegation/MapAccessorsTest.kt new file mode 100644 index 00000000000..7f2beb6d538 --- /dev/null +++ b/libraries/stdlib/test/properties/delegation/MapAccessorsTest.kt @@ -0,0 +1,107 @@ +package test.properties.delegation + +import kotlin.properties.* +import kotlin.test.* +import org.junit.Test as test + +class ValByMapExtensionsTest { + val map: Map = hashMapOf("a" to "all", "b" to "bar", "c" to "code") + val genericMap = mapOf("i" to 1, "x" to 1.0) + + val a: String by map + val b: String by map + val c: Any by map + val d: String? by map + val e: Any by map + val i: Int by genericMap + val x: Double by genericMap + + + test fun doTest() { + assertEquals("all", a) + assertEquals("bar", b) + assertEquals("code", c) + assertEquals(1, i) + assertEquals(1.0, x) + fails { d } + } +} + + + +class VarByMapExtensionsTest { + val map = hashMapOf("a" to "all", "b" to null, "c" to 1, "xProperty" to 1.0) + val map2: MutableMap = hashMapOf("a2" to "all") + + var a: String by map + var b: Any? by map + var c: Int by map + var d: String? by map + var a2: String by map2 + //var x: Int by map2 // prohibited by type system + + test fun doTest() { + assertEquals("all", a) + assertEquals(null, b) + assertEquals(1, c) + c = 2 + assertEquals(2, c) + assertEquals(2, map["c"]) + + + map["c"] = "string" + fails { c } + + map["a"] = null + a // fails { a } // does not fail due to KT-8135 + + fails { d } + map["d"] = null + assertEquals(null, d) + } +} + +class ValByMapAccessorExtensionsTest { + val map: Map = mapOf("a" to "all", "b" to "bar", "c" to "code") + val genericMap = mapOf("i" to 1, "x" to 1.0) + + val mapAccessor = map.forProperties { it.name.take(1) } + val genericMapAccessor = genericMap.forProperties { it.name.take(1) } + + val cProperty by mapAccessor + val xProperty by genericMapAccessor + val zNoDefault by genericMapAccessor + val zProperty by genericMapAccessor.withDefault { 1 } + + test fun doTest() { + assertEquals("code", cProperty) + assertEquals(1.0, xProperty) + assertEquals(1, zProperty) + fails { zNoDefault } + } +} + +class VarByMapAccessorExtensionsTest { + val map = hashMapOf("xProperty" to 1.0) + + val mapAccessor = map.forProperties { it.name + "Property" } + + var x: Double by mapAccessor.withDefault { 2.0 } + var z: Int by mapAccessor + var s: String by mapAccessor + + test fun doTest() { + assertEquals(1.0, x) + + map.remove("xProperty") + assertEquals(2.0, x) + + z = 2 + assertEquals(2, z) + assertEquals(2, map["zProperty"]) + + fails { s } + map["sProperty"] = null + s // fails { s } // does not fail due to KT-8135 + } +} diff --git a/libraries/stdlib/test/properties/delegation/MapDelegationTest.kt b/libraries/stdlib/test/properties/delegation/MapDelegationTest.kt index 537741b8314..70d229539a7 100644 --- a/libraries/stdlib/test/properties/delegation/MapDelegationTest.kt +++ b/libraries/stdlib/test/properties/delegation/MapDelegationTest.kt @@ -3,6 +3,7 @@ package test.properties.delegation import org.junit.Test as test import java.util.HashMap import kotlin.properties.* +import kotlin.test.* class MapDelegationTest(): DelegationTestBase() { @@ -81,6 +82,9 @@ class TestMapVarWithDifferentTypes(): WithBox { if (b != 11) return "fail at 'b'" if (c != B(11)) return "fail at 'c'" if (d != null) return "fail at 'd'" + + map["a"] = null + a // fails { a } // does not fail due to KT-8135 return "OK" } }