瀏覽代碼

Enhance MessageChainBuilder

Him188 5 年之前
父節點
當前提交
1fae390fba

+ 95 - 36
mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt

@@ -14,6 +14,7 @@
 package net.mamoe.mirai.message.data
 
 import net.mamoe.mirai.utils.MiraiExperimentalAPI
+import net.mamoe.mirai.utils.SinceMirai
 import kotlin.jvm.JvmMultifileClass
 import kotlin.jvm.JvmName
 import kotlin.jvm.JvmSynthetic
@@ -53,35 +54,14 @@ class MessageChainBuilder private constructor(
     constructor() : this(mutableListOf())
     constructor(initialSize: Int) : this(ArrayList<SingleMessage>(initialSize))
 
-    private var firstConstrainSingleIndex = -1
-
-    private fun addAndCheckConstrainSingle(element: SingleMessage): Boolean {
-        if (element is ConstrainSingle<*>) {
-            if (firstConstrainSingleIndex == -1) {
-                firstConstrainSingleIndex = container.size
-                return container.add(element)
-            }
-            val key = element.key
-
-            val index = container.indexOfFirst(firstConstrainSingleIndex) { it is ConstrainSingle<*> && it.key == key }
-            if (index != -1) {
-                container[index] = element
-            } else {
-                container.add(element)
-            }
-
-            return true
-        } else {
-            return container.add(element)
-        }
-    }
-
     override fun add(element: SingleMessage): Boolean {
+        checkBuilt()
         flushCache()
         return addAndCheckConstrainSingle(element)
     }
 
     fun add(element: Message): Boolean {
+        checkBuilt()
         flushCache()
         @Suppress("UNCHECKED_CAST")
         return when (element) {
@@ -93,44 +73,113 @@ class MessageChainBuilder private constructor(
     }
 
     override fun addAll(elements: Collection<SingleMessage>): Boolean {
+        checkBuilt()
         flushCache()
         return addAll(elements.flatten())
     }
 
     @JvmName("addAllFlatten") // erased generic type cause declaration clash
     fun addAll(elements: Collection<Message>): Boolean {
+        checkBuilt()
         flushCache()
         return addAll(elements.flatten())
     }
 
     operator fun Message.unaryPlus() {
+        checkBuilt()
         flushCache()
         add(this)
     }
 
 
     operator fun String.unaryPlus() {
+        checkBuilt()
         add(this)
     }
 
     operator fun plusAssign(plain: String) {
+        checkBuilt()
         withCache { append(plain) }
     }
 
     operator fun plusAssign(message: Message) {
+        checkBuilt()
         flushCache()
         this.add(message)
     }
 
     operator fun plusAssign(message: SingleMessage) { // avoid resolution ambiguity
+        checkBuilt()
         flushCache()
         this.add(message)
     }
 
     fun add(plain: String) {
+        checkBuilt()
         withCache { append(plain) }
     }
 
+    operator fun plusAssign(charSequence: CharSequence) {
+        checkBuilt()
+        withCache { append(charSequence) }
+    }
+
+    override fun append(value: Char): MessageChainBuilder = withCache { append(value) }
+    override fun append(value: CharSequence?): MessageChainBuilder = withCache { append(value) }
+    override fun append(value: CharSequence?, startIndex: Int, endIndex: Int): MessageChainBuilder =
+        withCache { append(value, startIndex, endIndex) }
+
+    fun append(message: Message): MessageChainBuilder = apply { add(message) }
+    fun append(message: SingleMessage): MessageChainBuilder = apply { add(message) }
+
+    // avoid resolution to extensions
+    fun asMessageChain(): MessageChain {
+        built = true
+        this.flushCache()
+        return MessageChainImplByCollection(this) // fast-path, no need to constrain
+    }
+
+    /** 同 [asMessageChain] */
+    fun build(): MessageChain = asMessageChain()
+
+    /**
+     * 将所有已有元素引用复制到一个新的 [MessageChainBuilder]
+     */
+    @SinceMirai("0.38.0")
+    fun copy(): MessageChainBuilder {
+        return MessageChainBuilder(container.toMutableList())
+    }
+
+    ///////
+    // FOR IMMUTABLE SAFETY
+
+    override fun remove(element: SingleMessage): Boolean {
+        checkBuilt()
+        return container.remove(element)
+    }
+
+    override fun removeAll(elements: Collection<SingleMessage>): Boolean {
+        checkBuilt()
+        return container.removeAll(elements)
+    }
+
+    override fun removeAt(index: Int): SingleMessage {
+        checkBuilt()
+        return container.removeAt(index)
+    }
+
+    override fun clear() {
+        checkBuilt()
+        return container.clear()
+    }
+
+    override fun set(index: Int, element: SingleMessage): SingleMessage {
+        checkBuilt()
+        return container.set(index, element)
+    }
+
+    ///////
+    // IMPLEMENTATION
     private var cache: StringBuilder? = null
     private fun flushCache() {
         cache?.let {
@@ -140,6 +189,7 @@ class MessageChainBuilder private constructor(
     }
 
     private inline fun withCache(block: StringBuilder.() -> Unit): MessageChainBuilder {
+        checkBuilt()
         if (cache == null) {
             cache = StringBuilder().apply(block)
         } else {
@@ -148,20 +198,29 @@ class MessageChainBuilder private constructor(
         return this
     }
 
-    operator fun plusAssign(charSequence: CharSequence) {
-        withCache { append(charSequence) }
-    }
+    private var built = false
+    private fun checkBuilt() = check(!built) { "MessageChainBuilder is already built therefore can't modify" }
 
-    override fun append(value: Char): Appendable = withCache { append(value) }
-    override fun append(value: CharSequence?): Appendable = withCache { append(value) }
-    override fun append(value: CharSequence?, startIndex: Int, endIndex: Int) =
-        withCache { append(value, startIndex, endIndex) }
+    private var firstConstrainSingleIndex = -1
 
-    fun asMessageChain(): MessageChain {
-        this.flushCache()
-        return MessageChainImplByCollection(this) // fast-path, no need to constrain
-    }
+    private fun addAndCheckConstrainSingle(element: SingleMessage): Boolean {
+        if (element is ConstrainSingle<*>) {
+            if (firstConstrainSingleIndex == -1) {
+                firstConstrainSingleIndex = container.size
+                return container.add(element)
+            }
+            val key = element.key
 
-    /** 同 [asMessageChain] */
-    fun build(): MessageChain = asMessageChain()
+            val index = container.indexOfFirst(firstConstrainSingleIndex) { it is ConstrainSingle<*> && it.key == key }
+            if (index != -1) {
+                container[index] = element
+            } else {
+                container.add(element)
+            }
+
+            return true
+        } else {
+            return container.add(element)
+        }
+    }
 }

+ 2 - 2
mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/MessageChainBuilderTest.kt

@@ -21,11 +21,11 @@ internal class MessageChainBuilderTest {
             +PlainText("foo")
             +" "
             +(PlainText("bar") + " goo ")
-            +buildMessageChain {
+            buildMessageChain {
                 +"1"
                 +"2"
                 +"3"
-            }
+            }.joinTo(this)
         }
 
         assertEquals("test foo bar goo 123", chain.toString())