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:
Ilya Gorbunov
2015-06-17 20:54:25 +03:00
parent d137a83471
commit 5306e88425
5 changed files with 139 additions and 42 deletions
@@ -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