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.
This commit is contained in:
Ilya Gorbunov
2015-06-11 23:33:35 +03:00
parent 2989586582
commit d137a83471
3 changed files with 214 additions and 0 deletions
@@ -0,0 +1,103 @@
package kotlin.properties
import kotlin.platform.platformName
// extensions for Map and MutableMap
public fun <V> Map<in String, *>.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 <V> MutableMap<in String, in V>.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 <V> MutableMap<in String, in V>.set(thisRef: Any?, property: PropertyMetadata, value: V) {
this.put(property.name, value)
}
// Map accessors
public interface MapAccessor<KMap, in KAccess, out V>
{
public val map: Map<in KMap, V>
/** 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<V>(default: (KMap) -> V): MapAccessor<KMap, KAccess, V>
}
public interface MutableMapAccessor<KMap, in KAccess, V>: MapAccessor<KMap, KAccess, V> {
public override val map: MutableMap<in KMap, V>
/** Associates the specified [value] with the specified [key] in the map. */
public fun put(key: KAccess, value: V): V?
public override fun <V> withDefault(default: (KMap) -> V): MutableMapAccessor<KMap, KAccess, V>
}
public fun <V> Map<in String, V>.forProperties(): MapAccessor<String, PropertyMetadata, V> = MapAccessorImpl(this, defaultKeyProvider)
// todo: inline keyselector into anonymous object
public fun <K, V> Map<in K, V>.forProperties(keySelector: (PropertyMetadata) -> K): MapAccessor<K, PropertyMetadata, V> = MapAccessorImpl(this, keySelector)
public fun <V> MutableMap<in String, V>.forProperties(): MutableMapAccessor<String, PropertyMetadata, V> = MutableMapAccessorImpl(this, defaultKeyProvider)
// todo: inline keyselector into anonymous object
public fun <K, V> MutableMap<in K, V>.forProperties(keySelector: (PropertyMetadata) -> K): MutableMapAccessor<K, PropertyMetadata, V> = MutableMapAccessorImpl(this, keySelector)
// extensions to delegate properties to MapAccessors
public fun <K, V> MapAccessor<K, PropertyMetadata, V>.get(thisRef: Any?, property: PropertyMetadata): V = this.getOrDefault(property)
platformName("getVar")
public fun <K, V> MutableMapAccessor<K, PropertyMetadata, in V>.get(thisRef: Any?, property: PropertyMetadata): V = this.getOrDefault(property) as V
public fun <K, V> MutableMapAccessor<K, PropertyMetadata, in V>.set(thisRef: Any?, property: PropertyMetadata, value: V) {
this.put(property, value)
}
// MapAccessor implementations
private open class MapAccessorImpl<KMap, in KAccess, out V>(public override val map: Map<in KMap, V>, public val transform: (KAccess) -> KMap, public val default: (KMap) -> V = { k1 -> defaultValueProvider(map, k1) })
: MapAccessor<KMap, KAccess, V> {
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<V>(default: (KMap) -> V): MapAccessor<KMap, KAccess, V> = MapAccessorImpl<KMap, KAccess, V>(map as Map<in KMap, V>, transform, default)
}
private class MutableMapAccessorImpl<KMap, in KAccess, V>(public override val map: MutableMap<in KMap, V>, transform: (KAccess) -> KMap, default: (KMap) -> V = { k1 -> defaultValueProvider(map, k1) })
: MapAccessorImpl<KMap, KAccess, V>(map, transform, default), MutableMapAccessor<KMap, KAccess, V> {
override fun put(key: KAccess, value: V): V? = map.put(transform(key), value)
override fun <V> withDefault(default: (KMap) -> V): MutableMapAccessor<KMap, KAccess, V> = MutableMapAccessorImpl<KMap, KAccess, V>(map as MutableMap<in KMap, V>, transform, default)
}
@@ -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<String, String> = hashMapOf("a" to "all", "b" to "bar", "c" to "code")
val genericMap = mapOf<String, Any?>("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<String, Any?>("a" to "all", "b" to null, "c" to 1, "xProperty" to 1.0)
val map2: MutableMap<String, CharSequence> = 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<String, String> = mapOf("a" to "all", "b" to "bar", "c" to "code")
val genericMap = mapOf<String, Any?>("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<String, Any?>("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
}
}
@@ -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"
}
}