Provide the way to specify implicit default for a readonly or mutable map.
Refactor: rename default key and value providers.
This commit is contained in:
@@ -0,0 +1,91 @@
|
|||||||
|
package kotlin
|
||||||
|
|
||||||
|
import kotlin.platform.platformName
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when the map does not contain the corresponding key.
|
||||||
|
*/
|
||||||
|
public class KeyMissingException(message: String): RuntimeException(message)
|
||||||
|
|
||||||
|
|
||||||
|
//private inline fun <K, V> Map<K, V>.getOrDefault(key: K, defaultValue: () -> V): V {
|
||||||
|
// val value = get(key)
|
||||||
|
// if (value == null && !containsKey(key)) {
|
||||||
|
// return defaultValue()
|
||||||
|
// } else {
|
||||||
|
// return value as V
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
public fun <K, V> Map<K, V>.getOrImplicitDefault(key: K): V {
|
||||||
|
if (this is MapWithDefault)
|
||||||
|
return this.getOrImplicitDefault(key)
|
||||||
|
|
||||||
|
return getOrElse(key, { throw KeyMissingException("Key $key is missing in the map.") })
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <K, V> Map<K, V>.withDefault(default: (key: K) -> V): Map<K, V> =
|
||||||
|
when (this) {
|
||||||
|
is MapWithDefault -> this.map.withDefault(default)
|
||||||
|
else -> MapWithDefaultImpl(this, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
platformName("withDefaultMutable")
|
||||||
|
public fun <K, V> MutableMap<K, V>.withDefault(default: (key: K) -> V): MutableMap<K, V> =
|
||||||
|
when (this) {
|
||||||
|
is MutableMapWithDefault -> this.map.withDefault(default)
|
||||||
|
else -> MutableMapWithDefaultImpl(this, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private interface MapWithDefault<K, out V>: Map<K, V> {
|
||||||
|
public val map: Map<K, V>
|
||||||
|
public fun getOrImplicitDefault(key: K): V
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface MutableMapWithDefault<K, V>: MutableMap<K, V>, MapWithDefault<K, V> {
|
||||||
|
public override val map: MutableMap<K, V>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class MapWithDefaultImpl<K, out V>(public override val map: Map<K,V>, private val default: (key: K) -> V) : MapWithDefault<K, V> {
|
||||||
|
override fun equals(other: Any?): Boolean = map.equals(other)
|
||||||
|
override fun hashCode(): Int = map.hashCode()
|
||||||
|
override fun toString(): String = map.toString()
|
||||||
|
override fun size(): Int = map.size()
|
||||||
|
override fun isEmpty(): Boolean = map.isEmpty()
|
||||||
|
override fun containsKey(key: Any?): Boolean = map.containsKey(key)
|
||||||
|
override fun containsValue(value: Any?): Boolean = map.containsValue(value)
|
||||||
|
override fun get(key: Any?): V? = map.get(key)
|
||||||
|
override fun keySet(): Set<K> = map.keySet()
|
||||||
|
override fun values(): Collection<V> = map.values()
|
||||||
|
override fun entrySet(): Set<Map.Entry<K, V>> = map.entrySet()
|
||||||
|
|
||||||
|
override fun getOrImplicitDefault(key: K): V = map.getOrElse(key, { default(key) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MutableMapWithDefaultImpl<K, V>(public override val map: MutableMap<K, V>, private val default: (key: K) -> V): MutableMapWithDefault<K, V> {
|
||||||
|
override fun equals(other: Any?): Boolean = map.equals(other)
|
||||||
|
override fun hashCode(): Int = map.hashCode()
|
||||||
|
override fun toString(): String = map.toString()
|
||||||
|
override fun size(): Int = map.size()
|
||||||
|
override fun isEmpty(): Boolean = map.isEmpty()
|
||||||
|
override fun containsKey(key: Any?): Boolean = map.containsKey(key)
|
||||||
|
override fun containsValue(value: Any?): Boolean = map.containsValue(value)
|
||||||
|
override fun get(key: Any?): V? = map.get(key)
|
||||||
|
override fun keySet(): MutableSet<K> = map.keySet()
|
||||||
|
override fun values(): MutableCollection<V> = map.values()
|
||||||
|
override fun entrySet(): MutableSet<MutableMap.MutableEntry<K, V>> = map.entrySet()
|
||||||
|
|
||||||
|
override fun put(key: K, value: V): V? = map.put(key, value)
|
||||||
|
override fun remove(key: Any?): V? = map.remove(key)
|
||||||
|
override fun putAll(m: Map<out K, V>) = map.putAll(m)
|
||||||
|
override fun clear() = map.clear()
|
||||||
|
|
||||||
|
override fun getOrImplicitDefault(key: K): V = map.getOrElse(key, { default(key) })
|
||||||
|
}
|
||||||
|
|
||||||
@@ -61,8 +61,8 @@ public object Delegates {
|
|||||||
* @param default the function returning the value of the property for a given object if it's missing from the given map.
|
* @param default the function returning the value of the property for a given object if it's missing from the given map.
|
||||||
*/
|
*/
|
||||||
public fun mapVar<T>(map: MutableMap<in String, Any?>,
|
public fun mapVar<T>(map: MutableMap<in String, Any?>,
|
||||||
default: (thisRef: Any?, desc: String) -> T = defaultValueProvider): ReadWriteProperty<Any?, T> {
|
default: (thisRef: Any?, desc: String) -> T = throwKeyNotFound): ReadWriteProperty<Any?, T> {
|
||||||
return FixedMapVar<Any?, String, T>(map, defaultKeyProvider, default)
|
return FixedMapVar<Any?, String, T>(map, propertyNameSelector, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,8 +72,8 @@ public object Delegates {
|
|||||||
* @param default the function returning the value of the property for a given object if it's missing from the given map.
|
* @param default the function returning the value of the property for a given object if it's missing from the given map.
|
||||||
*/
|
*/
|
||||||
public fun mapVal<T>(map: Map<in String, Any?>,
|
public fun mapVal<T>(map: Map<in String, Any?>,
|
||||||
default: (thisRef: Any?, desc: String) -> T = defaultValueProvider): ReadOnlyProperty<Any?, T> {
|
default: (thisRef: Any?, desc: String) -> T = throwKeyNotFound): ReadOnlyProperty<Any?, T> {
|
||||||
return FixedMapVal<Any?, String, T>(map, defaultKeyProvider, default)
|
return FixedMapVal<Any?, String, T>(map, propertyNameSelector, default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,12 +157,6 @@ private class BlockingLazyVal<T>(lock: Any?, private val initializer: () -> T) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown by the default implementation of property delegates which store values in a map
|
|
||||||
* when the map does not contain the corresponding key.
|
|
||||||
*/
|
|
||||||
public class KeyMissingException(message: String): RuntimeException(message)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the core logic for a property delegate that stores property values in a map.
|
* Implements the core logic for a property delegate that stores property values in a map.
|
||||||
* @param T the type of the object that owns the delegated property.
|
* @param T the type of the object that owns the delegated property.
|
||||||
@@ -217,8 +211,8 @@ public abstract class MapVar<T, K, V>() : MapVal<T, K, V>(), ReadWriteProperty<T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val defaultKeyProvider:(PropertyMetadata) -> String = {it.name}
|
private val propertyNameSelector: (PropertyMetadata) -> String = {it.name}
|
||||||
private val defaultValueProvider:(Any?, Any?) -> Nothing = {thisRef, key -> throw KeyMissingException("The value for key $key is missing from $thisRef.")}
|
private val throwKeyNotFound: (Any?, Any?) -> Nothing = {thisRef, key -> throw KeyMissingException("The value for key $key is missing from $thisRef.")}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a read-only property delegate that stores the property values in a given map instance and uses the given
|
* Implements a read-only property delegate that stores the property values in a given map instance and uses the given
|
||||||
@@ -229,7 +223,7 @@ private val defaultValueProvider:(Any?, Any?) -> Nothing = {thisRef, key -> thro
|
|||||||
*/
|
*/
|
||||||
public open class FixedMapVal<T, K, out V>(private val map: Map<in K, Any?>,
|
public open class FixedMapVal<T, K, out V>(private val map: Map<in K, Any?>,
|
||||||
private val key: (PropertyMetadata) -> K,
|
private val key: (PropertyMetadata) -> K,
|
||||||
private val default: (ref: T, key: K) -> V = defaultValueProvider) : MapVal<T, K, V>() {
|
private val default: (ref: T, key: K) -> V = throwKeyNotFound) : MapVal<T, K, V>() {
|
||||||
protected override fun map(ref: T): Map<in K, Any?> {
|
protected override fun map(ref: T): Map<in K, Any?> {
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
@@ -252,7 +246,7 @@ public open class FixedMapVal<T, K, out V>(private val map: Map<in K, Any?>,
|
|||||||
*/
|
*/
|
||||||
public open class FixedMapVar<T, K, V>(private val map: MutableMap<in K, Any?>,
|
public open class FixedMapVar<T, K, V>(private val map: MutableMap<in K, Any?>,
|
||||||
private val key: (PropertyMetadata) -> K,
|
private val key: (PropertyMetadata) -> K,
|
||||||
private val default: (ref: T, key: K) -> V = defaultValueProvider) : MapVar<T, K, V>() {
|
private val default: (ref: T, key: K) -> V = throwKeyNotFound) : MapVar<T, K, V>() {
|
||||||
protected override fun map(ref: T): MutableMap<in K, Any?> {
|
protected override fun map(ref: T): MutableMap<in K, Any?> {
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,14 @@
|
|||||||
package kotlin.properties
|
package kotlin.properties
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
import kotlin.platform.platformName
|
import kotlin.platform.platformName
|
||||||
|
|
||||||
// extensions for Map and MutableMap
|
// extensions for Map and MutableMap
|
||||||
|
|
||||||
public fun <V> Map<in String, *>.get(thisRef: Any?, property: PropertyMetadata): V {
|
public fun <V> Map<in String, *>.get(thisRef: Any?, property: PropertyMetadata): V = getOrImplicitDefault(property.name) as V
|
||||||
val value = this.get(property.name)
|
|
||||||
if (value == null && !containsKey(property.name))
|
|
||||||
defaultValueProvider(thisRef, property.name)
|
|
||||||
return value as V
|
|
||||||
}
|
|
||||||
|
|
||||||
platformName("getVar")
|
platformName("getVar")
|
||||||
public fun <V> MutableMap<in String, in V>.get(thisRef: Any?, property: PropertyMetadata): V {
|
public fun <V> MutableMap<in String, in V>.get(thisRef: Any?, property: PropertyMetadata): V = getOrImplicitDefault(property.name) as 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) {
|
public fun <V> MutableMap<in String, in V>.set(thisRef: Any?, property: PropertyMetadata, value: V) {
|
||||||
this.put(property.name, value)
|
this.put(property.name, value)
|
||||||
@@ -25,6 +16,8 @@ public fun <V> MutableMap<in String, in V>.set(thisRef: Any?, property: Property
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Map accessors
|
// Map accessors
|
||||||
|
|
||||||
public interface MapAccessor<KMap, in KAccess, out V>
|
public interface MapAccessor<KMap, in KAccess, out V>
|
||||||
@@ -34,7 +27,7 @@ public interface MapAccessor<KMap, in KAccess, out V>
|
|||||||
/** Returns `true` if the map contains the specified [key]. */
|
/** Returns `true` if the map contains the specified [key]. */
|
||||||
public fun containsKey(key: KAccess): Boolean
|
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. */
|
/** Returns the value corresponding to the given [key], or the default value if such a key is not present in the map. */
|
||||||
public fun getOrDefault(key: KAccess): V
|
public fun getOrDefault(key: KAccess): V
|
||||||
|
|
||||||
public open fun withDefault<V>(default: (KMap) -> V): MapAccessor<KMap, KAccess, V>
|
public open fun withDefault<V>(default: (KMap) -> V): MapAccessor<KMap, KAccess, V>
|
||||||
@@ -49,12 +42,12 @@ public interface MutableMapAccessor<KMap, in KAccess, V>: MapAccessor<KMap, KAcc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun <V> Map<in String, V>.forProperties(): MapAccessor<String, PropertyMetadata, V> = MapAccessorImpl(this, defaultKeyProvider)
|
public fun <V> Map<in String, V>.forProperties(): MapAccessor<String, PropertyMetadata, V> = MapAccessorImpl(this, propertyNameSelector)
|
||||||
|
|
||||||
// todo: inline keyselector into anonymous object
|
// 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 <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)
|
public fun <V> MutableMap<in String, V>.forProperties(): MutableMapAccessor<String, PropertyMetadata, V> = MutableMapAccessorImpl(this, propertyNameSelector)
|
||||||
|
|
||||||
// todo: inline keyselector into anonymous object
|
// 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)
|
public fun <K, V> MutableMap<in K, V>.forProperties(keySelector: (PropertyMetadata) -> K): MutableMapAccessor<K, PropertyMetadata, V> = MutableMapAccessorImpl(this, keySelector)
|
||||||
@@ -75,25 +68,18 @@ public fun <K, V> MutableMapAccessor<K, PropertyMetadata, in V>.set(thisRef: Any
|
|||||||
|
|
||||||
// MapAccessor implementations
|
// 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) })
|
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 = { key -> throwKeyNotFound(map, key) })
|
||||||
: MapAccessor<KMap, KAccess, V> {
|
: MapAccessor<KMap, KAccess, V> {
|
||||||
|
|
||||||
override fun containsKey(key: KAccess): Boolean = map.containsKey(transform(key))
|
override fun containsKey(key: KAccess): Boolean = map.containsKey(transform(key))
|
||||||
|
|
||||||
override fun getOrDefault(key: KAccess): V {
|
override fun getOrDefault(key: KAccess): V = transform(key).let { map.getOrElse(it, { default(it) }) }
|
||||||
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)
|
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) })
|
private class MutableMapAccessorImpl<KMap, in KAccess, V>(public override val map: MutableMap<in KMap, V>, transform: (KAccess) -> KMap, default: (KMap) -> V = { key -> throwKeyNotFound(map, key) })
|
||||||
: MapAccessorImpl<KMap, KAccess, V>(map, transform, default), MutableMapAccessor<KMap, KAccess, V> {
|
: MapAccessorImpl<KMap, KAccess, V>(map, transform, default), MutableMapAccessor<KMap, KAccess, V> {
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package test.collections
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
import kotlin.test.fails
|
||||||
import org.junit.Test as test
|
import org.junit.Test as test
|
||||||
|
|
||||||
class MapTest {
|
class MapTest {
|
||||||
@@ -21,6 +22,27 @@ class MapTest {
|
|||||||
assertEquals(null, c)
|
assertEquals(null, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test fun getOrImplicitDefault() {
|
||||||
|
val data: MutableMap<String, Int> = hashMapOf("bar" to 1)
|
||||||
|
assertTrue(fails { data.getOrImplicitDefault("foo") } is KeyMissingException)
|
||||||
|
assertEquals(1, data.getOrImplicitDefault("bar"))
|
||||||
|
|
||||||
|
val mutableWithDefault = data.withDefault { 42 }
|
||||||
|
assertEquals(42, mutableWithDefault.getOrImplicitDefault("foo"))
|
||||||
|
|
||||||
|
// verify that it is wrapper
|
||||||
|
mutableWithDefault["bar"] = 2
|
||||||
|
assertEquals(2, data["bar"])
|
||||||
|
data["bar"] = 3
|
||||||
|
assertEquals(3, mutableWithDefault["bar"])
|
||||||
|
|
||||||
|
val readonlyWithDefault = (data as Map<String, Int>).withDefault { it.length() }
|
||||||
|
assertEquals(4, readonlyWithDefault.getOrImplicitDefault("loop"))
|
||||||
|
|
||||||
|
val withReplacedDefault = readonlyWithDefault.withDefault { 42 }
|
||||||
|
assertEquals(42, withReplacedDefault.getOrImplicitDefault("loop"))
|
||||||
|
}
|
||||||
|
|
||||||
test fun getOrPut() {
|
test fun getOrPut() {
|
||||||
val data = hashMapOf<String, Int>()
|
val data = hashMapOf<String, Int>()
|
||||||
val a = data.getOrPut("foo") { 2 }
|
val a = data.getOrPut("foo") { 2 }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class ValByMapExtensionsTest {
|
|||||||
val b: String by map
|
val b: String by map
|
||||||
val c: Any by map
|
val c: Any by map
|
||||||
val d: String? by map
|
val d: String? by map
|
||||||
val e: Any by map
|
val e: String? by map.withDefault { "default" }
|
||||||
val i: Int by genericMap
|
val i: Int by genericMap
|
||||||
val x: Double by genericMap
|
val x: Double by genericMap
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ class ValByMapExtensionsTest {
|
|||||||
assertEquals("all", a)
|
assertEquals("all", a)
|
||||||
assertEquals("bar", b)
|
assertEquals("bar", b)
|
||||||
assertEquals("code", c)
|
assertEquals("code", c)
|
||||||
|
assertEquals("default", e)
|
||||||
assertEquals(1, i)
|
assertEquals(1, i)
|
||||||
assertEquals(1.0, x)
|
assertEquals(1.0, x)
|
||||||
fails { d }
|
fails { d }
|
||||||
@@ -37,7 +38,7 @@ class VarByMapExtensionsTest {
|
|||||||
var b: Any? by map
|
var b: Any? by map
|
||||||
var c: Int by map
|
var c: Int by map
|
||||||
var d: String? by map
|
var d: String? by map
|
||||||
var a2: String by map2
|
var a2: String by map2.withDefault { "empty" }
|
||||||
//var x: Int by map2 // prohibited by type system
|
//var x: Int by map2 // prohibited by type system
|
||||||
|
|
||||||
test fun doTest() {
|
test fun doTest() {
|
||||||
@@ -48,9 +49,12 @@ class VarByMapExtensionsTest {
|
|||||||
assertEquals(2, c)
|
assertEquals(2, c)
|
||||||
assertEquals(2, map["c"])
|
assertEquals(2, map["c"])
|
||||||
|
|
||||||
|
assertEquals("all", a2)
|
||||||
|
map2.remove("a2")
|
||||||
|
assertEquals("empty", a2)
|
||||||
|
|
||||||
map["c"] = "string"
|
map["c"] = "string"
|
||||||
fails { c }
|
// fails { c } // does not fail in JS due to KT-8135
|
||||||
|
|
||||||
map["a"] = null
|
map["a"] = null
|
||||||
a // fails { a } // does not fail due to KT-8135
|
a // fails { a } // does not fail due to KT-8135
|
||||||
|
|||||||
Reference in New Issue
Block a user