From ce678a75b73449d0c85a9a1fb92d5555fa6c947f Mon Sep 17 00:00:00 2001
From: Karlatemp <karlatemp@vip.qq.com>
Date: Mon, 9 Nov 2020 23:19:42 +0800
Subject: [PATCH] Make SemVersion.Requirement data class

---
 .../internal/util/semver/RangeTokenReader.kt  |  6 ++--
 .../util/semver/RequirementInternal.kt        | 16 ++++++++++
 .../util/semver/SemVersionInternal.kt         | 30 ++++++++-----------
 backend/mirai-console/src/util/SemVersion.kt  | 30 +++++++++++++++++--
 .../mirai-console/test/util/TestSemVersion.kt |  5 ++--
 5 files changed, 61 insertions(+), 26 deletions(-)
 create mode 100644 backend/mirai-console/src/internal/util/semver/RequirementInternal.kt

diff --git a/backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt b/backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt
index 5f761cb6e..17064132b 100644
--- a/backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt
+++ b/backend/mirai-console/src/internal/util/semver/RangeTokenReader.kt
@@ -189,7 +189,7 @@ internal object RangeTokenReader {
         }
     }
 
-    fun parse(source: String, token: Token): SemVersion.Requirement {
+    fun parse(source: String, token: Token): RequirementInternal {
         return when (token) {
             is Token.Group -> {
                 if (token.values.size == 1) {
@@ -206,7 +206,7 @@ internal object RangeTokenReader {
                     }.map { parse(source, it) }.toList()
                     when (logic.first()) {
                         TokenType.OR -> {
-                            return object : SemVersion.Requirement {
+                            return object : RequirementInternal {
                                 override fun test(version: SemVersion): Boolean {
                                     rules.forEach { if (it.test(version)) return true }
                                     return false
@@ -214,7 +214,7 @@ internal object RangeTokenReader {
                             }
                         }
                         TokenType.AND -> {
-                            return object : SemVersion.Requirement {
+                            return object : RequirementInternal {
                                 override fun test(version: SemVersion): Boolean {
                                     rules.forEach { if (!it.test(version)) return false }
                                     return true
diff --git a/backend/mirai-console/src/internal/util/semver/RequirementInternal.kt b/backend/mirai-console/src/internal/util/semver/RequirementInternal.kt
new file mode 100644
index 000000000..903f19eea
--- /dev/null
+++ b/backend/mirai-console/src/internal/util/semver/RequirementInternal.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2019-2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.console.internal.util.semver
+
+import net.mamoe.mirai.console.util.SemVersion
+
+internal interface RequirementInternal {
+    fun test(version: SemVersion): Boolean
+}
diff --git a/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt b/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt
index 1c5e013af..1d9c1af03 100644
--- a/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt
+++ b/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt
@@ -75,11 +75,11 @@ internal object SemVersionInternal {
     }
 
     @JvmStatic
-    internal fun parseRule(rule: String): SemVersion.Requirement {
+    internal fun parseRule(rule: String): RequirementInternal {
         val trimmed = rule.trim()
         if (directVersion.matches(trimmed)) {
             val parsed = SemVersion.invoke(trimmed)
-            return object : SemVersion.Requirement {
+            return object : RequirementInternal {
                 override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0
             }
         }
@@ -89,7 +89,7 @@ internal object SemVersionInternal {
                     .replace("x", ".+") +
                 "$"
                 ).toRegex()
-            return object : SemVersion.Requirement {
+            return object : RequirementInternal {
                 override fun test(version: SemVersion): Boolean = regex.matches(version.toString())
             }
         }
@@ -120,7 +120,7 @@ internal object SemVersionInternal {
                 '(', ')' -> ({ it < end })
                 else -> throw AssertionError()
             }
-            return object : SemVersion.Requirement {
+            return object : RequirementInternal {
                 override fun test(version: SemVersion): Boolean = a(version) && b(version)
             }
         }
@@ -129,32 +129,32 @@ internal object SemVersionInternal {
             val version1 = SemVersion.invoke(result.groupValues[8])
             return when (operator) {
                 ">=" -> {
-                    object : SemVersion.Requirement {
+                    object : RequirementInternal {
                         override fun test(version: SemVersion): Boolean = version >= version1
                     }
                 }
                 ">" -> {
-                    object : SemVersion.Requirement {
+                    object : RequirementInternal {
                         override fun test(version: SemVersion): Boolean = version > version1
                     }
                 }
                 "<=" -> {
-                    object : SemVersion.Requirement {
+                    object : RequirementInternal {
                         override fun test(version: SemVersion): Boolean = version <= version1
                     }
                 }
                 "<" -> {
-                    object : SemVersion.Requirement {
+                    object : RequirementInternal {
                         override fun test(version: SemVersion): Boolean = version < version1
                     }
                 }
                 "=" -> {
-                    object : SemVersion.Requirement {
+                    object : RequirementInternal {
                         override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0
                     }
                 }
                 "!=" -> {
-                    object : SemVersion.Requirement {
+                    object : RequirementInternal {
                         override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0
                     }
                 }
@@ -164,15 +164,9 @@ internal object SemVersionInternal {
         throw IllegalArgumentException("Cannot parse $rule")
     }
 
-    private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement {
-        return object : SemVersion.Requirement {
-            override fun test(version: SemVersion): Boolean = this@withRule.test(version)
-            override fun toString(): String = rule
-        }
-    }
 
     @JvmStatic
-    fun parseRangeRequirement(requirement: String): SemVersion.Requirement {
+    fun parseRangeRequirement(requirement: String): RequirementInternal {
         if (requirement.isBlank()) {
             throw IllegalArgumentException("Invalid requirement: Empty requirement rule.")
         }
@@ -180,7 +174,7 @@ internal object SemVersionInternal {
         val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true)
         RangeTokenReader.check(requirement, collected.iterator(), null)
         return kotlin.runCatching {
-            RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)).withRule(requirement)
+            RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0))
         }.onFailure { error ->
             throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString {
                 collected.forEach { dump("", it) }
diff --git a/backend/mirai-console/src/util/SemVersion.kt b/backend/mirai-console/src/util/SemVersion.kt
index 47c207fa8..f350c0f58 100644
--- a/backend/mirai-console/src/util/SemVersion.kt
+++ b/backend/mirai-console/src/util/SemVersion.kt
@@ -81,9 +81,33 @@ internal constructor(
      * 一条依赖规则
      * @see [parseRangeRequirement]
      */
-    public interface Requirement {
+    @Serializable(Requirement.RequirementAsStringSerializer::class)
+    public data class Requirement internal constructor(
+        /**
+         * 规则的字符串表示方式
+         *
+         * @see [SemVersion.parseRangeRequirement]
+         */
+        val rule: String
+    ) {
+        @Transient
+        private val impl = SemVersionInternal.parseRangeRequirement(rule)
+
         /** 在 [version] 满足此要求时返回 true */
-        public fun test(version: SemVersion): Boolean
+        public fun test(version: SemVersion): Boolean {
+            return impl.test(version)
+        }
+
+        public object RequirementAsStringSerializer : KSerializer<Requirement> by String.serializer().map(
+            serializer = { it.rule },
+            deserializer = { parseRangeRequirement(it) }
+        )
+
+        public companion object {
+            @JvmSynthetic
+            public operator fun invoke(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement =
+                parseRangeRequirement(requirement)
+        }
     }
 
     public object SemVersionAsStringSerializer : KSerializer<SemVersion> by String.serializer().map(
@@ -149,7 +173,7 @@ internal constructor(
         @JvmStatic
         @Throws(IllegalArgumentException::class)
         public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement =
-            SemVersionInternal.parseRangeRequirement(requirement)
+            Requirement(requirement)
 
         /** @see [Requirement.test] */
         @JvmStatic
diff --git a/backend/mirai-console/test/util/TestSemVersion.kt b/backend/mirai-console/test/util/TestSemVersion.kt
index 60881b310..3021de67b 100644
--- a/backend/mirai-console/test/util/TestSemVersion.kt
+++ b/backend/mirai-console/test/util/TestSemVersion.kt
@@ -16,6 +16,7 @@ package net.mamoe.mirai.console.util
 
 import net.mamoe.mirai.console.util.SemVersion.Companion.test
 import org.junit.jupiter.api.Test
+import kotlin.test.assertFails
 
 internal class TestSemVersion {
     @Test
@@ -50,9 +51,9 @@ internal class TestSemVersion {
         }
 
         fun assertInvalid(requirement: String) {
-            kotlin.runCatching {
+            assertFails(requirement) {
                 SemVersion.parseRangeRequirement(requirement)
-            }.onSuccess { assert(false) { requirement } }
+            }
         }
 
         fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement {