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.
|
||||
*/
|
||||
public fun mapVar<T>(map: MutableMap<in String, Any?>,
|
||||
default: (thisRef: Any?, desc: String) -> T = defaultValueProvider): ReadWriteProperty<Any?, T> {
|
||||
return FixedMapVar<Any?, String, T>(map, defaultKeyProvider, default)
|
||||
default: (thisRef: Any?, desc: String) -> T = throwKeyNotFound): ReadWriteProperty<Any?, T> {
|
||||
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.
|
||||
*/
|
||||
public fun mapVal<T>(map: Map<in String, Any?>,
|
||||
default: (thisRef: Any?, desc: String) -> T = defaultValueProvider): ReadOnlyProperty<Any?, T> {
|
||||
return FixedMapVal<Any?, String, T>(map, defaultKeyProvider, default)
|
||||
default: (thisRef: Any?, desc: String) -> T = throwKeyNotFound): ReadOnlyProperty<Any?, T> {
|
||||
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.
|
||||
* @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 defaultValueProvider:(Any?, Any?) -> Nothing = {thisRef, key -> throw KeyMissingException("The value for key $key is missing from $thisRef.")}
|
||||
private val propertyNameSelector: (PropertyMetadata) -> String = {it.name}
|
||||
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
|
||||
@@ -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?>,
|
||||
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?> {
|
||||
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?>,
|
||||
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?> {
|
||||
return map
|
||||
}
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
package kotlin.properties
|
||||
|
||||
import java.io.Serializable
|
||||
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
|
||||
}
|
||||
public fun <V> Map<in String, *>.get(thisRef: Any?, property: PropertyMetadata): V = getOrImplicitDefault(property.name) 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>.get(thisRef: Any?, property: PropertyMetadata): V = getOrImplicitDefault(property.name) as V
|
||||
|
||||
public fun <V> MutableMap<in String, in V>.set(thisRef: Any?, property: PropertyMetadata, value: V) {
|
||||
this.put(property.name, value)
|
||||
@@ -25,6 +16,8 @@ public fun <V> MutableMap<in String, in V>.set(thisRef: Any?, property: Property
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Map accessors
|
||||
|
||||
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]. */
|
||||
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 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
|
||||
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
|
||||
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
|
||||
|
||||
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> {
|
||||
|
||||
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 getOrDefault(key: KAccess): V = transform(key).let { map.getOrElse(it, { default(it) }) }
|
||||
|
||||
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> {
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package test.collections
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fails
|
||||
import org.junit.Test as test
|
||||
|
||||
class MapTest {
|
||||
@@ -21,6 +22,27 @@ class MapTest {
|
||||
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() {
|
||||
val data = hashMapOf<String, Int>()
|
||||
val a = data.getOrPut("foo") { 2 }
|
||||
|
||||
@@ -12,7 +12,7 @@ class ValByMapExtensionsTest {
|
||||
val b: String by map
|
||||
val c: Any 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 x: Double by genericMap
|
||||
|
||||
@@ -21,6 +21,7 @@ class ValByMapExtensionsTest {
|
||||
assertEquals("all", a)
|
||||
assertEquals("bar", b)
|
||||
assertEquals("code", c)
|
||||
assertEquals("default", e)
|
||||
assertEquals(1, i)
|
||||
assertEquals(1.0, x)
|
||||
fails { d }
|
||||
@@ -37,7 +38,7 @@ class VarByMapExtensionsTest {
|
||||
var b: Any? by map
|
||||
var c: Int 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
|
||||
|
||||
test fun doTest() {
|
||||
@@ -48,9 +49,12 @@ class VarByMapExtensionsTest {
|
||||
assertEquals(2, c)
|
||||
assertEquals(2, map["c"])
|
||||
|
||||
assertEquals("all", a2)
|
||||
map2.remove("a2")
|
||||
assertEquals("empty", a2)
|
||||
|
||||
map["c"] = "string"
|
||||
fails { c }
|
||||
// fails { c } // does not fail in JS due to KT-8135
|
||||
|
||||
map["a"] = null
|
||||
a // fails { a } // does not fail due to KT-8135
|
||||
|
||||
Reference in New Issue
Block a user