mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-09 19:50:27 +08:00
Avoid boxing in TypeSafeMap
and support serialization
This commit is contained in:
parent
2696f0b95e
commit
f5b2dbc65d
@ -67,6 +67,12 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
api(yamlkt)
|
||||
}
|
||||
}
|
||||
|
||||
if (isAndroidSDKAvailable) {
|
||||
val androidMain by getting {
|
||||
//
|
||||
|
@ -11,24 +11,30 @@
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@Serializable
|
||||
@JvmInline
|
||||
public value class TypeKey<out T>(public val name: String) {
|
||||
public value class TypeKey<T>(public val name: String) {
|
||||
override fun toString(): String = "Key($name)"
|
||||
|
||||
public inline infix fun <T> to(value: T): TypeSafeMap = buildTypeSafeMap { set(this@TypeKey, value) }
|
||||
public inline infix fun to(value: T): TypeSafeMap = buildTypeSafeMap { set(this@TypeKey, value) }
|
||||
}
|
||||
|
||||
public interface TypeSafeMap {
|
||||
/**
|
||||
* @see buildTypeSafeMap
|
||||
*/
|
||||
public sealed interface TypeSafeMap {
|
||||
public val size: Int
|
||||
|
||||
public operator fun <T> get(key: TypeKey<T>): T
|
||||
public operator fun <T> contains(key: TypeKey<T>): Boolean = get(key) != null
|
||||
|
||||
public fun toMap(): Map<TypeKey<*>, Any?>
|
||||
public fun toMapBoxed(): Map<TypeKey<*>, Any?>
|
||||
public fun toMap(): Map<String, Any?>
|
||||
|
||||
public companion object {
|
||||
public val EMPTY: TypeSafeMap = TypeSafeMapImpl(emptyMap())
|
||||
@ -46,15 +52,16 @@ public operator fun TypeSafeMap.plus(other: TypeSafeMap): TypeSafeMap {
|
||||
}
|
||||
}
|
||||
|
||||
public interface MutableTypeSafeMap : TypeSafeMap {
|
||||
public sealed interface MutableTypeSafeMap : TypeSafeMap {
|
||||
public operator fun <T> set(key: TypeKey<T>, value: T)
|
||||
public fun <T> remove(key: TypeKey<T>): T?
|
||||
public fun setAll(other: TypeSafeMap)
|
||||
}
|
||||
|
||||
|
||||
@PublishedApi
|
||||
internal open class TypeSafeMapImpl(
|
||||
internal open val map: Map<TypeKey<*>, Any?> = ConcurrentHashMap()
|
||||
@PublishedApi internal open val map: Map<String, Any?> = ConcurrentHashMap()
|
||||
) : TypeSafeMap {
|
||||
override val size: Int get() = map.size
|
||||
|
||||
@ -67,16 +74,17 @@ internal open class TypeSafeMapImpl(
|
||||
}
|
||||
|
||||
override operator fun <T> get(key: TypeKey<T>): T =
|
||||
map[key]?.uncheckedCast() ?: throw NoSuchElementException(key.toString())
|
||||
map[key.name]?.uncheckedCast() ?: throw NoSuchElementException(key.toString())
|
||||
|
||||
override operator fun <T> contains(key: TypeKey<T>): Boolean = get(key) != null
|
||||
|
||||
override fun toMap(): Map<TypeKey<*>, Any?> = map
|
||||
override fun toMapBoxed(): Map<TypeKey<*>, Any?> = map.mapKeys { TypeKey<Any?>(it.key) }
|
||||
override fun toMap(): Map<String, Any?> = map
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal class MutableTypeSafeMapImpl(
|
||||
override val map: MutableMap<TypeKey<*>, Any?> = ConcurrentHashMap()
|
||||
@PublishedApi override val map: MutableMap<String, Any?> = ConcurrentHashMap()
|
||||
) : TypeSafeMap, MutableTypeSafeMap, TypeSafeMapImpl(map) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is MutableTypeSafeMapImpl && other.map == this.map
|
||||
@ -87,7 +95,7 @@ internal class MutableTypeSafeMapImpl(
|
||||
}
|
||||
|
||||
override operator fun <T> set(key: TypeKey<T>, value: T) {
|
||||
map[key] = value
|
||||
map[key.name] = value
|
||||
}
|
||||
|
||||
override fun setAll(other: TypeSafeMap) {
|
||||
@ -98,9 +106,13 @@ internal class MutableTypeSafeMapImpl(
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> remove(key: TypeKey<T>): T? = map.remove(key)?.uncheckedCast()
|
||||
override fun <T> remove(key: TypeKey<T>): T? = map.remove(key.name)?.uncheckedCast()
|
||||
}
|
||||
|
||||
public inline fun MutableTypeSafeMap(): MutableTypeSafeMap = MutableTypeSafeMapImpl()
|
||||
public inline fun MutableTypeSafeMap(map: Map<String, Any?>): MutableTypeSafeMap =
|
||||
MutableTypeSafeMapImpl().also { it.map.putAll(map) }
|
||||
|
||||
public inline fun buildTypeSafeMap(block: MutableTypeSafeMap.() -> Unit): MutableTypeSafeMap {
|
||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||
return MutableTypeSafeMapImpl().apply(block)
|
||||
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import net.mamoe.yamlkt.Yaml
|
||||
import net.mamoe.yamlkt.YamlBuilder
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class TypeSafeMapTest {
|
||||
|
||||
private val myKey = TypeKey<String>("test")
|
||||
private val myKey2 = TypeKey<CharSequence>("test2")
|
||||
|
||||
@Test
|
||||
fun `can set get`() {
|
||||
val map = MutableTypeSafeMap()
|
||||
map[myKey] = "str"
|
||||
map[myKey2] = "str2"
|
||||
assertEquals(2, map.size)
|
||||
assertEquals("str", map[myKey])
|
||||
assertEquals("str2", map[myKey2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `key is inlined`() {
|
||||
val map = MutableTypeSafeMap()
|
||||
map[TypeKey<String>("test")] = "str"
|
||||
map[TypeKey<String>("test")] = "str2"
|
||||
assertEquals(1, map.size)
|
||||
assertEquals("str2", map[TypeKey("test")])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can toMap`() {
|
||||
val map = MutableTypeSafeMap()
|
||||
map[myKey] = "str"
|
||||
map[myKey2] = "str2"
|
||||
assertEquals(2, map.size)
|
||||
|
||||
val map1 = map.toMapBoxed()
|
||||
|
||||
assertEquals(2, map1.size)
|
||||
assertEquals("str", map1[myKey])
|
||||
assertEquals("str2", map1[myKey2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test serialization`() {
|
||||
val map = MutableTypeSafeMap()
|
||||
map[myKey] = "str"
|
||||
map[myKey2] = "str2"
|
||||
assertEquals(2, map.size)
|
||||
|
||||
val map1 = map.toMap()
|
||||
|
||||
// Json does not support reflective serialization, so we use Yaml in JSON format
|
||||
val yaml = Yaml {
|
||||
classSerialization = YamlBuilder.MapSerialization.FLOW_MAP
|
||||
mapSerialization = YamlBuilder.MapSerialization.FLOW_MAP
|
||||
listSerialization = YamlBuilder.ListSerialization.FLOW_SEQUENCE
|
||||
stringSerialization = YamlBuilder.StringSerialization.DOUBLE_QUOTATION
|
||||
encodeDefaultValues = true
|
||||
}
|
||||
|
||||
val string = yaml.encodeToString(map1)
|
||||
println(string) // { "test2": "str2" ,"test": "str" }
|
||||
|
||||
val result = MutableTypeSafeMap(Yaml.decodeMapFromString(string).cast())
|
||||
assertEquals(map, result)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user