mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-10 04:00:08 +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) {
|
if (isAndroidSDKAvailable) {
|
||||||
val androidMain by getting {
|
val androidMain by getting {
|
||||||
//
|
//
|
||||||
|
@ -11,24 +11,30 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.utils
|
package net.mamoe.mirai.utils
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
@Serializable
|
||||||
@JvmInline
|
@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)"
|
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 val size: Int
|
||||||
|
|
||||||
public operator fun <T> get(key: TypeKey<T>): T
|
public operator fun <T> get(key: TypeKey<T>): T
|
||||||
public operator fun <T> contains(key: TypeKey<T>): Boolean = get(key) != null
|
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 companion object {
|
||||||
public val EMPTY: TypeSafeMap = TypeSafeMapImpl(emptyMap())
|
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 operator fun <T> set(key: TypeKey<T>, value: T)
|
||||||
public fun <T> remove(key: TypeKey<T>): T?
|
public fun <T> remove(key: TypeKey<T>): T?
|
||||||
public fun setAll(other: TypeSafeMap)
|
public fun setAll(other: TypeSafeMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
internal open class TypeSafeMapImpl(
|
internal open class TypeSafeMapImpl(
|
||||||
internal open val map: Map<TypeKey<*>, Any?> = ConcurrentHashMap()
|
@PublishedApi internal open val map: Map<String, Any?> = ConcurrentHashMap()
|
||||||
) : TypeSafeMap {
|
) : TypeSafeMap {
|
||||||
override val size: Int get() = map.size
|
override val size: Int get() = map.size
|
||||||
|
|
||||||
@ -67,16 +74,17 @@ internal open class TypeSafeMapImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override operator fun <T> get(key: TypeKey<T>): T =
|
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 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
|
@PublishedApi
|
||||||
internal class MutableTypeSafeMapImpl(
|
internal class MutableTypeSafeMapImpl(
|
||||||
override val map: MutableMap<TypeKey<*>, Any?> = ConcurrentHashMap()
|
@PublishedApi override val map: MutableMap<String, Any?> = ConcurrentHashMap()
|
||||||
) : TypeSafeMap, MutableTypeSafeMap, TypeSafeMapImpl(map) {
|
) : TypeSafeMap, MutableTypeSafeMap, TypeSafeMapImpl(map) {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is MutableTypeSafeMapImpl && other.map == this.map
|
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) {
|
override operator fun <T> set(key: TypeKey<T>, value: T) {
|
||||||
map[key] = value
|
map[key.name] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setAll(other: TypeSafeMap) {
|
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 {
|
public inline fun buildTypeSafeMap(block: MutableTypeSafeMap.() -> Unit): MutableTypeSafeMap {
|
||||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||||
return MutableTypeSafeMapImpl().apply(block)
|
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