Skip to content

Commit 5d2e9bc

Browse files
authored
Merge pull request #811 from simple-robot/ID-API
提供更多与 ID 相关的API
2 parents 92b8aa9 + 7271926 commit 5d2e9bc

File tree

5 files changed

+441
-123
lines changed

5 files changed

+441
-123
lines changed

simbot-commons/simbot-common-core/src/commonMain/kotlin/love/forte/simbot/common/id/ID.kt

Lines changed: 217 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Project https://github.com/simple-robot/simpler-robot
55
66
*
7-
* This file is part of the Simple Robot Library.
7+
* This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
88
*
99
* This program is free software: you can redistribute it and/or modify
1010
* it under the terms of the GNU Lesser General Public License as published by
@@ -21,20 +21,26 @@
2121
*
2222
*/
2323

24+
@file:JvmName("Identifies")
25+
@file:JvmMultifileClass
26+
2427
package love.forte.simbot.common.id
2528

26-
import kotlinx.serialization.KSerializer
27-
import kotlinx.serialization.Serializable
29+
import kotlinx.serialization.*
2830
import kotlinx.serialization.builtins.serializer
2931
import kotlinx.serialization.descriptors.*
30-
import kotlinx.serialization.encoding.Decoder
31-
import kotlinx.serialization.encoding.Encoder
32+
import kotlinx.serialization.encoding.*
33+
import love.forte.simbot.common.id.IntID.Companion.ID
34+
import love.forte.simbot.common.id.LongID.Companion.ID
3235
import love.forte.simbot.common.id.StringID.Companion.ID
36+
import love.forte.simbot.common.id.UIntID.Companion.ID
37+
import love.forte.simbot.common.id.ULongID.Companion.ID
3338
import love.forte.simbot.common.id.UUID.Companion.UUID
3439
import kotlin.concurrent.Volatile
3540
import kotlin.experimental.and
3641
import kotlin.experimental.or
3742
import kotlin.js.JsName
43+
import kotlin.jvm.JvmMultifileClass
3844
import kotlin.jvm.JvmName
3945
import kotlin.jvm.JvmOverloads
4046
import kotlin.jvm.JvmStatic
@@ -55,6 +61,43 @@ import kotlin.random.Random
5561
*
5662
* 它们可以粗略的被归类为字符串类型( [UUID] 的字面值表现为字符串)和数字类型。
5763
*
64+
* ## 构造
65+
*
66+
* 在 Kotlin 中,直接使用各类型的伴生对象提供的扩展属性构建即可:
67+
*
68+
* ```Kotlin
69+
* 100.ID
70+
* 100L.ID
71+
* 100u.ID
72+
* "100".ID
73+
* UUID.random()
74+
* ```
75+
*
76+
* 在 Java 中,可以使用 `Identifies` 中提供的构造方法,它们通常被命名为 `of` 或以 `of` 为开头:
77+
*
78+
* ```Java
79+
* Identifies.of(100);
80+
* Identifies.of(100L);
81+
* Identifies.of("100");
82+
* Identifies.ofULong(100L);
83+
* Identifies.ofULong("100");
84+
* Identifies.uuid();
85+
* ```
86+
*
87+
* 也可以使用具体类型的伴生对象所提供的静态API:
88+
*
89+
* ```Java
90+
* IntID.valueOf(100);
91+
* LongID.valueOf(100L);
92+
* UIntID.valueOf(100);
93+
* ULongID.valueOf(100L);
94+
* StringID.valueOf("100");
95+
* UUID.random();
96+
* ```
97+
*
98+
* 这些伴生对象提供的静态API与 `Identifies` 中的内容相比缺少了一些辅助性的API,
99+
* 例如使用字符串构建无符号ID `ULongID`。
100+
*
58101
* ## 序列化
59102
*
60103
* 所有**具体的**ID类型都是可序列化的,它们都会通过 Kotlinx serialization
@@ -506,6 +549,59 @@ public class UUID private constructor(
506549
}
507550
}
508551

552+
/**
553+
* 直接将 [UUID] 作为一个具有两个 [Long] 的结构体进行序列化的序列化器。
554+
*/
555+
public object StructureSerializer : KSerializer<UUID> {
556+
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UUID") {
557+
element("mostSignificantBits", Long.serializer().descriptor)
558+
element("leastSignificantBits", Long.serializer().descriptor)
559+
}
560+
561+
@OptIn(ExperimentalSerializationApi::class)
562+
override fun deserialize(decoder: Decoder): UUID {
563+
return decoder.decodeStructure(descriptor) {
564+
var m = false
565+
var l = false
566+
var mostSignificantBits: Long = 0
567+
var leastSignificantBits: Long = 0
568+
569+
while (true) {
570+
when (val index = decodeElementIndex(descriptor)) {
571+
0 -> {
572+
m = true
573+
mostSignificantBits = decodeLongElement(descriptor, index)
574+
}
575+
576+
1 -> {
577+
l = true
578+
leastSignificantBits = decodeLongElement(descriptor, index)
579+
}
580+
581+
CompositeDecoder.DECODE_DONE -> break
582+
else -> error("Unexpected index: $index")
583+
}
584+
}
585+
if (!m || !l) {
586+
val properties = buildList {
587+
if (!m) add("mostSignificantBits")
588+
if (!l) add("leastSignificantBits")
589+
}
590+
throw MissingFieldException(properties, "love.forte.simbot.common.id.UUID")
591+
}
592+
593+
UUID(mostSignificantBits, leastSignificantBits)
594+
}
595+
}
596+
597+
override fun serialize(encoder: Encoder, value: UUID) {
598+
encoder.encodeStructure(descriptor) {
599+
encodeLongElement(descriptor, 0, value.mostSignificantBits)
600+
encodeLongElement(descriptor, 1, value.leastSignificantBits)
601+
}
602+
}
603+
}
604+
509605
}
510606

511607
/**
@@ -914,3 +1010,119 @@ private inline fun <reified T : ID> T.idExactEq(other: Any?, orElse: T.(T) -> Bo
9141010
*/
9151011
public inline val ID.literal: String
9161012
get() = toString()
1013+
1014+
/**
1015+
* 尝试将 [this] 转化为 [Int]。
1016+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toInt],
1017+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toIntOrNull()][String.toIntOrNull]。
1018+
*/
1019+
@JvmOverloads
1020+
@JvmName("toIntOrNull")
1021+
public inline fun ID.toIntOrNull(notNumerical: ID.() -> Int? = { literal.toIntOrNull() }): Int? =
1022+
(this as? NumericalID)?.toInt() ?: notNumerical()
1023+
1024+
/**
1025+
* 尝试将 [this] 转化为 [Int]。
1026+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toInt],
1027+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toInt()][String.toInt]。
1028+
*/
1029+
@JvmOverloads
1030+
@JvmName("toInt")
1031+
public inline fun ID.toInt(notNumerical: ID.() -> Int = { literal.toInt() }): Int =
1032+
(this as? NumericalID)?.toInt() ?: notNumerical()
1033+
1034+
/**
1035+
* 尝试将 [this] 转化为 [Long]。
1036+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toLong],
1037+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toLongOrNull()][String.toLongOrNull]。
1038+
*/
1039+
@JvmOverloads
1040+
@JvmName("toLongOrNull")
1041+
public inline fun ID.toLongOrNull(notNumerical: ID.() -> Long? = { literal.toLongOrNull() }): Long? =
1042+
(this as? NumericalID)?.toLong() ?: notNumerical()
1043+
1044+
/**
1045+
* 尝试将 [this] 转化为 [Long]。
1046+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toLong],
1047+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toLong()][String.toLong]。
1048+
*/
1049+
@JvmOverloads
1050+
@JvmName("toLong")
1051+
public inline fun ID.toLong(notNumerical: ID.() -> Long = { literal.toLong() }): Long =
1052+
(this as? NumericalID)?.toLong() ?: notNumerical()
1053+
1054+
/**
1055+
* 尝试将 [this] 转化为 [UInt]。
1056+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toInt].[toUInt][Int.toUInt],
1057+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toUIntOrNull()][String.toIntOrNull]。
1058+
*/
1059+
@JvmOverloads
1060+
@JvmName("toUIntOrNull")
1061+
public inline fun ID.toUIntOrNull(notNumerical: ID.() -> UInt? = { literal.toUIntOrNull() }): UInt? =
1062+
(this as? NumericalID)?.toInt()?.toUInt() ?: notNumerical()
1063+
1064+
/**
1065+
* 尝试将 [this] 转化为 [UInt]。
1066+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toInt].[toUInt][Int.toUInt],
1067+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toUInt()][String.toInt]。
1068+
*/
1069+
@JvmOverloads
1070+
@JvmName("toUInt")
1071+
public inline fun ID.toUInt(notNumerical: ID.() -> UInt = { literal.toUInt() }): UInt =
1072+
(this as? NumericalID)?.toInt()?.toUInt() ?: notNumerical()
1073+
1074+
/**
1075+
* 尝试将 [this] 转化为 [ULong]。
1076+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toLong].[toULong][Long.toULong],
1077+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toULongOrNull()][String.toLongOrNull]。
1078+
*/
1079+
@JvmOverloads
1080+
@JvmName("toULongOrNull")
1081+
public inline fun ID.toULongOrNull(notNumerical: ID.() -> ULong? = { literal.toULongOrNull() }): ULong? =
1082+
(this as? NumericalID)?.toLong()?.toULong() ?: notNumerical()
1083+
1084+
/**
1085+
* 尝试将 [this] 转化为 [ULong]。
1086+
* 如果为 [NumericalID] 则直接使用 [NumericalID.toLong].[toULong][Long.toULong],
1087+
* 否则使用 [notNumerical] 转化。默认会尝试使用 [literal.toULong()][String.toLong]。
1088+
*/
1089+
@JvmOverloads
1090+
@JvmName("toULong")
1091+
public inline fun ID.toULong(notNumerical: ID.() -> ULong = { literal.toULong() }): ULong =
1092+
(this as? NumericalID)?.toLong()?.toULong() ?: notNumerical()
1093+
1094+
/**
1095+
* 尝试将 [this] 转为 [IntID] 类型。
1096+
* 如果不是数字ID,则会使用 [notNumerical] 获取结果。默认使用 [String.toInt]。
1097+
*/
1098+
@JvmOverloads
1099+
@JvmName("toIntID")
1100+
public inline fun ID.toIntID(notNumerical: ID.() -> IntID = { literal.toInt().ID }): IntID =
1101+
this as? IntID ?: (this as? NumericalID)?.toInt()?.ID ?: notNumerical()
1102+
1103+
/**
1104+
* 尝试将 [this] 转为 [UIntID] 类型。
1105+
* 如果不是数字ID,则会使用 [notNumerical] 获取结果。默认使用 [String.toUInt]。
1106+
*/
1107+
@JvmOverloads
1108+
@JvmName("toUIntID")
1109+
public inline fun ID.toUIntID(notNumerical: ID.() -> UIntID = { literal.toUInt().ID }): UIntID =
1110+
this as? UIntID ?: (this as? NumericalID)?.toUInt()?.ID ?: notNumerical()
1111+
1112+
/**
1113+
* 尝试将 [this] 转为 [LongID] 类型。
1114+
* 如果不是数字ID,则会使用 [notNumerical] 获取结果。默认使用 [String.toLong]。
1115+
*/
1116+
@JvmOverloads
1117+
@JvmName("toLongID")
1118+
public inline fun ID.toLongID(notNumerical: ID.() -> LongID = { literal.toLong().ID }): LongID =
1119+
this as? LongID ?: (this as? NumericalID)?.toLong()?.ID ?: notNumerical()
1120+
1121+
/**
1122+
* 尝试将 [this] 转为 [ULongID] 类型。
1123+
* 如果不是数字ID,则会使用 [notNumerical] 获取结果。默认使用 [String.toULong]。
1124+
*/
1125+
@JvmOverloads
1126+
@JvmName("toULongID")
1127+
public inline fun ID.toULongID(notNumerical: ID.() -> ULongID = { literal.toULong().ID }): ULongID =
1128+
this as? ULongID ?: (this as? NumericalID)?.toULong()?.ID ?: notNumerical()
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2024. ForteScarlet.
3+
*
4+
* Project https://github.com/simple-robot/simpler-robot
5+
6+
*
7+
* This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* Lesser GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the Lesser GNU General Public License
20+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
24+
@file:JvmName("Identifies")
25+
@file:JvmMultifileClass
26+
27+
package love.forte.simbot.common.id
28+
29+
import love.forte.simbot.common.id.ULongID.Companion.ID
30+
import kotlin.jvm.JvmMultifileClass
31+
import kotlin.jvm.JvmName
32+
33+
34+
/**
35+
* 构建一个 [ULongID].
36+
* @see ULong.ID
37+
*/
38+
@JvmName("ofULong")
39+
public fun uLongIDOf(value: ULong): ULongID = value.ID
40+

simbot-commons/simbot-common-core/src/commonTest/kotlin/love/forte/simbot/common/id/IDTests.kt

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Project https://github.com/simple-robot/simpler-robot
55
66
*
7-
* This file is part of the Simple Robot Library.
7+
* This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.).
88
*
99
* This program is free software: you can redistribute it and/or modify
1010
* it under the terms of the GNU Lesser General Public License as published by
@@ -23,6 +23,8 @@
2323

2424
package love.forte.simbot.common.id
2525

26+
import kotlinx.serialization.ExperimentalSerializationApi
27+
import kotlinx.serialization.MissingFieldException
2628
import kotlinx.serialization.json.Json
2729
import love.forte.simbot.common.id.IntID.Companion.ID
2830
import love.forte.simbot.common.id.LongID.Companion.ID
@@ -33,9 +35,7 @@ import love.forte.simbot.common.id.UUID.Companion.UUID
3335
import kotlin.random.Random
3436
import kotlin.random.nextUInt
3537
import kotlin.random.nextULong
36-
import kotlin.test.Test
37-
import kotlin.test.assertEquals
38-
import kotlin.test.assertTrue
38+
import kotlin.test.*
3939

4040
class IDTests {
4141

@@ -72,7 +72,57 @@ class IDTests {
7272
assertEquals(json.encodeToString(LongID.serializer(), l.ID), l.toString())
7373
assertEquals(json.encodeToString(UIntID.serializer(), ui.ID), ui.toString())
7474
assertEquals(json.encodeToString(ULongID.serializer(), ul.ID), ul.toString())
75+
}
7576

77+
@Test
78+
fun numericalIDTransformTests() {
79+
with(1.ID) {
80+
assertEquals(1L, toLong())
81+
assertEquals(1L.ID, toLongID())
82+
assertSame(this, toIntID())
83+
}
84+
with(1L.ID) {
85+
assertEquals(1, toInt())
86+
assertSame(this, toLongID())
87+
assertEquals(1.ID, toIntID())
88+
}
89+
with("1".ID) {
90+
assertEquals(1, toInt())
91+
assertEquals(1L, toLong())
92+
assertEquals(1L.ID, toLongID())
93+
assertEquals(1.ID, toIntID())
94+
}
95+
with(1u.ID) {
96+
assertEquals(1u, toUInt())
97+
val ul: ULong = 1u
98+
assertEquals(ul.ID, toULongID())
99+
assertEquals(1.ID, toIntID())
100+
assertSame(this, toUIntID())
101+
}
102+
with(uLongIDOf(1u)) {
103+
assertEquals(1, toInt())
104+
assertEquals(1L.ID, toLongID())
105+
assertSame(this, toULongID())
106+
assertEquals(1.ID, toIntID())
107+
}
76108
}
77109

110+
111+
@OptIn(ExperimentalSerializationApi::class)
112+
@Test
113+
fun uuidSerializerTests() {
114+
val mv = 111L
115+
val lv = 222L
116+
val uuid = UUID.from(mv, lv)
117+
val jsonString = Json.encodeToString(UUID.StructureSerializer, uuid)
118+
assertEquals("""{"mostSignificantBits":$mv,"leastSignificantBits":$lv}""", jsonString)
119+
val decodedUUID = Json.decodeFromString(UUID.StructureSerializer, """{"mostSignificantBits":$mv,"leastSignificantBits":$lv}""")
120+
assertEquals(uuid, decodedUUID)
121+
122+
val err = assertFails {
123+
Json.decodeFromString(UUID.StructureSerializer, """{"leastSignificantBits":$lv}""")
124+
}
125+
assertIs<MissingFieldException>(err)
126+
err.printStackTrace()
127+
}
78128
}

0 commit comments

Comments
 (0)