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:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user