diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt index c1300b0ef..d13a97f17 100644 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt +++ b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt @@ -97,7 +97,7 @@ fun MessageChain.toDTOChain() = mutableListOf(this[MessageSource].toDTO()).apply } fun MessageChainDTO.toMessageChain(contact: Contact) = - MessageChain().apply { this@toMessageChain.forEach { add(it.toMessage(contact)) } } + buildMessageChain { this@toMessageChain.forEach { add(it.toMessage(contact)) } } @UseExperimental(ExperimentalUnsignedTypes::class) fun Message.toDTO() = when (this) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index 7920aee6b..614d5873c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -147,6 +147,7 @@ internal class QQImpl( } } finally { (image.input as? Closeable)?.close() + (image.input as? io.ktor.utils.io.core.Closeable)?.close() } @MiraiExperimentalAPI @@ -644,7 +645,8 @@ internal class GroupImpl( } } } finally { - (image.input as Closeable)?.close() + (image.input as? Closeable)?.close() + (image.input as? io.ktor.utils.io.core.Closeable)?.close() } override fun toString(): String { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt index bdba99fe9..86261c518 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt @@ -326,10 +326,10 @@ internal class NotOnlineImageFromServer( internal fun MsgComm.Msg.toMessageChain(): MessageChain { val elements = this.msgBody.richText.elems - val message = MessageChain(initialCapacity = elements.size + 1) + val message = ArrayList(elements.size + 1) message.add(MessageSourceFromMsg(delegate = this)) elements.joinToMessageChain(message) - return message + return message.asMessageChain() } // These two functions are not the same. @@ -338,15 +338,15 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain { internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain { val elements = this.elems!! - val message = MessageChain(initialCapacity = elements.size + 1) + val message = ArrayList(elements.size + 1) message.add(MessageSourceFromServer(delegate = this)) elements.joinToMessageChain(message) - return message + return message.asMessageChain() } @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class) -internal fun List.joinToMessageChain(message: MessageChain) { +internal fun List.joinToMessageChain(message: MutableList) { this.forEach { when { it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg))) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt index 4aa0a88a5..900688dc5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt @@ -86,10 +86,12 @@ object Highway { writeFully(head) when (body) { is ByteReadPacket -> writePacket(body) - is Input -> ByteArrayPool.useInstance { buffer -> - var size: Int - while (body.readAvailable(buffer).also { size = it } != 0) { - this@buildPacket.writeFully(buffer, 0, size) + is Input -> body.use { + ByteArrayPool.useInstance { buffer -> + var size: Int + while (body.readAvailable(buffer).also { size = it } != 0) { + this@buildPacket.writeFully(buffer, 0, size) + } } } is ByteReadChannel -> ByteArrayPool.useInstance { buffer -> @@ -98,13 +100,18 @@ object Highway { this@buildPacket.writeFully(buffer, 0, size) } } - is InputStream -> ByteArrayPool.useInstance { buffer -> - var size: Int - while (body.read(buffer).also { size = it } != 0) { - this@buildPacket.writeFully(buffer, 0, size) + is InputStream -> try { + ByteArrayPool.useInstance { buffer -> + var size: Int + while (body.read(buffer).also { size = it } != 0) { + this@buildPacket.writeFully(buffer, 0, size) + } } + } finally { + body.close() } } + writeByte(41) } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index ca6b7500c..b2dab4e51 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -26,7 +26,6 @@ import net.mamoe.mirai.event.subscribingGetAsync import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource -import net.mamoe.mirai.message.data.addOrRemove import net.mamoe.mirai.qqandroid.GroupImpl import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket @@ -398,8 +397,6 @@ internal class MessageSvc { msgVia = 1 ) ) - - message.addOrRemove(source) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index 42b232bce..07cf36239 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -138,4 +138,9 @@ suspend inline fun C.sendMessage(message: Message): MessageReceipt /** * @see Contact.sendMessage */ -suspend inline fun C.sendMessage(plain: String): MessageReceipt = sendMessage(plain.toMessage()) \ No newline at end of file +suspend inline fun C.sendMessage(plain: String): MessageReceipt = sendMessage(plain.toMessage()) + +/** + * @see Contact.sendMessage + */ +suspend inline fun C.sendMessage(plain: CombinedMessage): MessageReceipt = sendMessage(MessageChain(plain as Message)) as MessageReceipt \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt index bd431be69..7d5c37657 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt @@ -15,6 +15,7 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.contact.sendMessage import net.mamoe.mirai.message.data.* import net.mamoe.mirai.recallIn import net.mamoe.mirai.utils.MiraiExperimentalAPI diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt index c08baf3b4..e21f476ea 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt @@ -36,8 +36,8 @@ class At @MiraiInternalAPI constructor(val target: Long, val display: String) : // 自动为消息补充 " " - override fun followedBy(tail: Message): MessageChain { - if(tail is PlainText && tail.stringValue.startsWith(' ')){ + override fun followedBy(tail: Message): CombinedMessage { + if (tail is PlainText && tail.stringValue.startsWith(' ')) { return super.followedBy(tail) } return super.followedBy(PlainText(" ")) + tail diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt index 83e39948f..0b9369bdd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt @@ -27,7 +27,7 @@ object AtAll : Message, Message.Key { // 自动为消息补充 " " - override fun followedBy(tail: Message): MessageChain { + override fun followedBy(tail: Message): CombinedMessage { if (tail is PlainText && tail.stringValue.startsWith(' ')) { return super.followedBy(tail) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt new file mode 100644 index 000000000..081c1116b --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2020 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/master/LICENSE + */ + +package net.mamoe.mirai.message.data + +/** + * Left-biased list + */ +class CombinedMessage( + val left: Message, + val element: Message +) : Iterable, Message { + private suspend fun SequenceScope.yieldCombinedOrElements(message: Message) { + when (message) { + is CombinedMessage -> { + yieldCombinedOrElements(message.element) + yieldCombinedOrElements(message.left) + } + is MessageChain -> message.forEach { yieldCombinedOrElements(it) } + else -> yield(message) + } + } + + fun asSequence(): Sequence = sequence { + yieldCombinedOrElements(this@CombinedMessage) + } + + override fun iterator(): Iterator { + return asSequence().iterator() + } + + override fun toString(): String { + return left.toString() + element.toString() + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt index 6d9ba15af..aba05812d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt @@ -85,20 +85,19 @@ interface Message { * ``` */ @JvmSynthetic // in java they should use `plus` instead - fun followedBy(tail: Message): MessageChain { + fun followedBy(tail: Message): CombinedMessage { require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } require(this !is SingleOnly) { "SingleOnly Message cannot be followed" } - return if (tail is MessageChain) tail.followedBy(this)/*MessageChainImpl(this).also { tail.forEach { child -> it.concat(child) } }*/ - else MessageChainImpl(this, tail) + return CombinedMessage(tail, this) } override fun toString(): String - operator fun plus(another: Message): MessageChain = this.followedBy(another) + operator fun plus(another: Message): CombinedMessage = this.followedBy(another) - operator fun plus(another: String): MessageChain = this.followedBy(another.toMessage()) + operator fun plus(another: String): CombinedMessage = this.followedBy(another.toMessage()) // `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)` - operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage()) + operator fun plus(another: CharSequence): CombinedMessage = this.followedBy(another.toString().toMessage()) } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index cf6420cc3..529121372 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -16,8 +16,6 @@ import net.mamoe.mirai.message.data.NullMessageChain.toString import kotlin.js.JsName import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName -import kotlin.jvm.JvmSynthetic -import kotlin.jvm.Volatile import kotlin.reflect.KProperty /** @@ -31,24 +29,14 @@ import kotlin.reflect.KProperty * - 若一个 [Message] 与一个 [MessageChain] 连接, [Message] 将会被添加入 [MessageChain]. * * 要获取更多信息, 请查看 [Message] + * + * @see buildMessageChain */ -interface MessageChain : Message, MutableList { +interface MessageChain : Message, List { // region Message override override operator fun contains(sub: String): Boolean - - override fun followedBy(tail: Message): MessageChain // endregion - @JvmSynthetic - operator fun plusAssign(message: Message) { - this.followedBy(message) - } - - @JvmSynthetic // make java user happier - operator fun plusAssign(plain: String) { - this.plusAssign(plain.toMessage()) - } - override fun toString(): String /** @@ -59,15 +47,6 @@ interface MessageChain : Message, MutableList { operator fun get(key: Message.Key): M = first(key) } -/** - * 先删除同类型的消息, 再添加 [message] - */ -fun MessageChain.addOrRemove(message: T) { - val clazz = message::class - this.removeAll { clazz.isInstance(it) } - this.add(message) -} - /** * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XMLMessage] */ @@ -109,17 +88,7 @@ inline operator fun MessageChain.getValue(thisRef: Any?, p @JvmName("newChain") @JsName("newChain") @Suppress("FunctionName") -fun MessageChain(): MessageChain = EmptyMessageChain() - -/** - * 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 [initialCapacity] - */ -@JvmName("newChain") -@JsName("newChain") -@Suppress("FunctionName") -fun MessageChain(initialCapacity: Int): MessageChain = - if (initialCapacity == 0) EmptyMessageChain() - else MessageChainImpl(ArrayList(initialCapacity)) +fun MessageChain(): MessageChain = EmptyMessageChain /** * 构造 [MessageChain] @@ -129,7 +98,7 @@ fun MessageChain(initialCapacity: Int): MessageChain = @JsName("newChain") @Suppress("FunctionName") fun MessageChain(vararg messages: Message): MessageChain = - if (messages.isEmpty()) EmptyMessageChain() + if (messages.isEmpty()) EmptyMessageChain else MessageChainImpl(messages.toMutableList()) /** @@ -140,7 +109,7 @@ fun MessageChain(vararg messages: Message): MessageChain = @JsName("newChain") @Suppress("FunctionName") fun MessageChain(message: Message): MessageChain = - MessageChainImpl(mutableListOf(message)) + MessageChainImpl(listOf(message)) /** * 构造 [MessageChain] @@ -149,7 +118,16 @@ fun MessageChain(message: Message): MessageChain = @JsName("newChain") @Suppress("FunctionName") fun MessageChain(messages: Iterable): MessageChain = - MessageChainImpl(messages.toMutableList()) + MessageChainImpl(messages.toList()) + +/** + * 构造 [MessageChain] + */ +@JvmName("newChain") +@JsName("newChain") +@Suppress("FunctionName") +fun MessageChain(messages: List): MessageChain = + MessageChainImpl(messages) /** @@ -164,7 +142,7 @@ inline fun Message.toChain(): MessageChain = if (this is MessageChain) this else * 构造 [MessageChain] */ @Suppress("unused", "NOTHING_TO_INLINE") -inline fun List.toMessageChain(): MessageChain = MessageChain(this) +inline fun List.asMessageChain(): MessageChain = MessageChain(this) /** * 获取第一个 [M] 类型的 [Message] 实例 @@ -211,71 +189,9 @@ fun MessageChain.first(key: Message.Key): M = firstOrNull(key) @Suppress("UNCHECKED_CAST") fun MessageChain.any(key: Message.Key): Boolean = firstOrNull(key) != null -/** - * 空的 [Message]. - * - * 它不包含任何元素, 但维护一个 'lazy' 的 [MessageChainImpl]. - * - * 只有在必要的时候(如迭代([iterator]), 插入([add]), 连接([followedBy], [plus], [plusAssign]))才会创建这个对象代表的 list - * - * 它是一个正常的 [Message] 和 [MessageChain]. 可以做所有 [Message] 能做的事. - */ -class EmptyMessageChain : MessageChain { - private val delegate: MessageChain by lazy { - MessageChainImpl().also { initialized = true } - } - - @Volatile - private var initialized: Boolean = false - - override fun subList(fromIndex: Int, toIndex: Int): MutableList = - if (initialized) delegate.subList( - fromIndex, - toIndex - ) else throw IndexOutOfBoundsException("given args that from $fromIndex to $toIndex, but the list is empty") - - override fun toString(): String = if (initialized) delegate.toString() else "" - - override fun contains(sub: String): Boolean = if (initialized) delegate.contains(sub) else false - override fun contains(element: Message): Boolean = if (initialized) delegate.contains(element) else false - override fun followedBy(tail: Message): MessageChain = delegate.followedBy(tail) - - override val size: Int = if (initialized) delegate.size else 0 - override fun containsAll(elements: Collection): Boolean = - if (initialized) delegate.containsAll(elements) else false - - override fun get(index: Int): Message = - if (initialized) delegate[index] else throw IndexOutOfBoundsException(index.toString()) - - override fun indexOf(element: Message): Int = if (initialized) delegate.indexOf(element) else -1 - override fun isEmpty(): Boolean = if (initialized) delegate.isEmpty() else true - override fun iterator(): MutableIterator = delegate.iterator() - - override fun lastIndexOf(element: Message): Int = if (initialized) delegate.lastIndexOf(element) else -1 - override fun add(element: Message): Boolean = delegate.add(element) - override fun add(index: Int, element: Message) = delegate.add(index, element) - override fun addAll(index: Int, elements: Collection): Boolean = delegate.addAll(elements) - override fun addAll(elements: Collection): Boolean = delegate.addAll(elements) - override fun clear() { - if (initialized) delegate.clear() - } - - override fun listIterator(): MutableListIterator = delegate.listIterator() - - override fun listIterator(index: Int): MutableListIterator = delegate.listIterator() - override fun remove(element: Message): Boolean = if (initialized) delegate.remove(element) else false - override fun removeAll(elements: Collection): Boolean = - if (initialized) delegate.removeAll(elements) else false - - override fun removeAt(index: Int): Message = - if (initialized) delegate.removeAt(index) else throw IndexOutOfBoundsException(index.toString()) - - override fun retainAll(elements: Collection): Boolean = - if (initialized) delegate.retainAll(elements) else false - - override fun set(index: Int, element: Message): Message = - if (initialized) delegate.set(index, element) else throw IndexOutOfBoundsException(index.toString()) -} +object EmptyMessageChain : MessageChain by { + MessageChainImpl(emptyList()) +}() /** * Null 的 [MessageChain]. @@ -290,7 +206,7 @@ object NullMessageChain : MessageChain { override fun contains(sub: String): Boolean = error("accessing NullMessageChain") override fun contains(element: Message): Boolean = error("accessing NullMessageChain") - override fun followedBy(tail: Message): MessageChain = tail.toChain() + override fun followedBy(tail: Message): CombinedMessage = CombinedMessage(left = EmptyMessageChain, element = tail) override val size: Int get() = error("accessing NullMessageChain") override fun containsAll(elements: Collection): Boolean = error("accessing NullMessageChain") @@ -300,76 +216,29 @@ object NullMessageChain : MessageChain { override fun iterator(): MutableIterator = error("accessing NullMessageChain") override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain") - override fun add(element: Message): Boolean = error("accessing NullMessageChain") - override fun add(index: Int, element: Message) = error("accessing NullMessageChain") - override fun addAll(index: Int, elements: Collection): Boolean = error("accessing NullMessageChain") - - override fun addAll(elements: Collection): Boolean = error("accessing NullMessageChain") - override fun clear() { - error("accessing NullMessageChain") - } - override fun listIterator(): MutableListIterator = error("accessing NullMessageChain") override fun listIterator(index: Int): MutableListIterator = error("accessing NullMessageChain") - - override fun remove(element: Message): Boolean = error("accessing NullMessageChain") - override fun removeAll(elements: Collection): Boolean = error("accessing NullMessageChain") - override fun removeAt(index: Int): Message = error("accessing NullMessageChain") - override fun retainAll(elements: Collection): Boolean = error("accessing NullMessageChain") - override fun set(index: Int, element: Message): Message = error("accessing NullMessageChain") } /** * [MessageChain] 实现 * 它是一个特殊的 [Message], 实现 [MutableList] 接口, 但将所有的接口调用都转到内部维护的另一个 [MutableList]. */ -internal inline class MessageChainImpl constructor( +internal class MessageChainImpl constructor( /** * Elements will not be instances of [MessageChain] */ - private val delegate: MutableList -) : Message, MutableList, // do not `by delegate`, bcz Inline class cannot implement an interface by delegation - MessageChain { - - constructor(vararg messages: Message) : this(messages.toMutableList()) - - // region Message override + private val delegate: List +) : Message, List by delegate, MessageChain { override fun toString(): String = this.delegate.joinToString("") { it.toString() } override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } - override fun followedBy(tail: Message): MessageChain { + override fun followedBy(tail: Message): CombinedMessage { require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } - if (tail is MessageChain) tail.forEach { child -> this.followedBy(child) } - else this.delegate.add(tail) - return this + // if (tail is MessageChain) tail.forEach { child -> this.followedBy(child) } + // else this.delegate.add(tail) + return CombinedMessage(tail, this) } - - // endregion - - // region MutableList override - override fun containsAll(elements: Collection): Boolean = delegate.containsAll(elements) - - override operator fun get(index: Int): Message = delegate[index] - override fun indexOf(element: Message): Int = delegate.indexOf(element) - override fun isEmpty(): Boolean = delegate.isEmpty() - override fun lastIndexOf(element: Message): Int = delegate.lastIndexOf(element) - override fun add(element: Message): Boolean = delegate.add(element) - override fun add(index: Int, element: Message) = delegate.add(index, element) - override fun addAll(index: Int, elements: Collection): Boolean = delegate.addAll(index, elements) - override fun addAll(elements: Collection): Boolean = delegate.addAll(elements) - override fun clear() = delegate.clear() - override fun listIterator(): MutableListIterator = delegate.listIterator() - override fun listIterator(index: Int): MutableListIterator = delegate.listIterator(index) - override fun remove(element: Message): Boolean = delegate.remove(element) - override fun removeAll(elements: Collection): Boolean = delegate.removeAll(elements) - override fun removeAt(index: Int): Message = delegate.removeAt(index) - override fun retainAll(elements: Collection): Boolean = delegate.retainAll(elements) - override fun set(index: Int, element: Message): Message = delegate.set(index, element) - override fun subList(fromIndex: Int, toIndex: Int): MutableList = delegate.subList(fromIndex, toIndex) - override operator fun iterator(): MutableIterator = delegate.iterator() - override operator fun contains(element: Message): Boolean = delegate.contains(element) - override val size: Int get() = delegate.size - // endregion } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt index d13061536..36fc92c02 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.message.data import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName +import kotlin.jvm.JvmStatic /** * 纯文本. 可含 emoji 表情. @@ -21,10 +22,17 @@ import kotlin.jvm.JvmName * 一般不需要主动构造 [PlainText], [Message] 可直接与 [String] 相加. Java 用户请使用 [MessageChain.plus] */ inline class PlainText(val stringValue: String) : Message { + constructor(charSequence: CharSequence) : this(charSequence.toString()) + override operator fun contains(sub: String): Boolean = sub in stringValue override fun toString(): String = stringValue - companion object Key : Message.Key + companion object Key : Message.Key<PlainText> { + @JvmStatic + val Empty = PlainText("") + @JvmStatic + val Null = PlainText("null") + } } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt new file mode 100644 index 000000000..d2d11b445 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2020 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/master/LICENSE + */ + +package net.mamoe.mirai.message.data + +import kotlin.jvm.JvmOverloads + + +/** + * 构造一个 [MessageChain] + * + * @see MessageChainBuilder + */ +inline fun buildMessageChain(block: MessageChainBuilder.() -> Unit): MessageChain { + return MessageChainBuilder().apply(block).asMessageChain() +} + +class MessageChainBuilder @JvmOverloads constructor( + private val container: MutableList<Message> = mutableListOf() +) : MutableList<Message> by container, Appendable { + operator fun Message.unaryPlus() { + add(this) + } + + operator fun String.unaryPlus() { + add(this.toMessage()) + } + + operator fun plusAssign(plain: String) { + this.add(plain.toMessage()) + } + + operator fun plusAssign(message: Message) { + this.add(message) + } + + fun add(plain: String) { + this.add(plain.toMessage()) + } + + operator fun plusAssign(charSequence: CharSequence) { + this.add(PlainText(charSequence)) + } + + override fun append(c: Char): Appendable = apply { + this.add(PlainText(c.toString())) + } + + override fun append(csq: CharSequence?): Appendable = apply { + when { + csq == null -> this.add(PlainText.Null) + csq.isEmpty() -> this.add(PlainText.Empty) + else -> this.add(PlainText(csq)) + } + } + + override fun append(csq: CharSequence?, start: Int, end: Int): Appendable = apply { + when { + csq == null -> this.add(PlainText.Null) + csq.isEmpty() -> this.add(PlainText.Empty) + else -> this.add(PlainText(csq.substring(start, end))) + } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.message.data/CombinedMessageTest.kt b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.message.data/CombinedMessageTest.kt new file mode 100644 index 000000000..663dc952f --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.message.data/CombinedMessageTest.kt @@ -0,0 +1,155 @@ +package net.mamoe.mirai.message.data + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.ExperimentalTime +import kotlin.time.measureTime + +internal class CombinedMessageTest { + + @Test + fun testAsSequence() { + var message: Message = "Hello ".toMessage() + message += "World" + + assertEquals( + "Hello World", + (message as CombinedMessage).asSequence().joinToString(separator = "") + ) + } + + @Test + fun testAsSequence2() { + var message: Message = "Hello ".toMessage() + message += MessageChain( + PlainText("W"), + PlainText("o"), + PlainText("r") + PlainText("ld") + ) + + assertEquals( + "Hello World", + (message as CombinedMessage).asSequence().joinToString(separator = "") + ) + } + + private val toAdd = "1".toMessage() + + @UseExperimental(ExperimentalTime::class) + @Test + fun speedTest() = repeat(100) { + var count = 1L + + repeat(Int.MAX_VALUE) { + count++ + } + + var combineMessage: Message = toAdd + + println( + "init combine ok " + measureTime { + repeat(1000) { + combineMessage += toAdd + } + }.inMilliseconds + ) + + val list = mutableListOf<Message>() + println( + "init messageChain ok " + measureTime { + repeat(1000) { + list += toAdd + } + }.inMilliseconds + ) + + measureTime { + list.joinToString(separator = "") + }.let { time -> + println("list foreach: ${time.inMilliseconds} ms") + } + + measureTime { + (combineMessage as CombinedMessage).iterator().joinToString(separator = "") + }.let { time -> + println("combined iterate: ${time.inMilliseconds} ms") + } + + measureTime { + (combineMessage as CombinedMessage).asSequence().joinToString(separator = "") + }.let { time -> + println("combined sequence: ${time.inMilliseconds} ms") + } + + repeat(5) { + println() + } + } + + @UseExperimental(ExperimentalTime::class) + @Test + fun testFastIteration() { + println("start!") + println("start!") + println("start!") + println("start!") + + var combineMessage: Message = toAdd + + println( + "init combine ok " + measureTime { + repeat(1000) { + combineMessage += toAdd + } + }.inMilliseconds + ) + + measureTime { + (combineMessage as CombinedMessage).iterator().joinToString(separator = "") + }.let { time -> + println("combine: ${time.inMilliseconds} ms") + } + } +} + +public fun <T> Iterator<T>.joinToString( + separator: CharSequence = ", ", + prefix: CharSequence = "", + postfix: CharSequence = "", + limit: Int = -1, + truncated: CharSequence = "...", + transform: ((T) -> CharSequence)? = null +): String { + return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() +} + +public fun <T, A : Appendable> Iterator<T>.joinTo( + buffer: A, + separator: CharSequence = ", ", + prefix: CharSequence = "", + postfix: CharSequence = "", + limit: Int = -1, + truncated: CharSequence = "...", + transform: ((T) -> CharSequence)? = null +): A { + buffer.append(prefix) + var count = 0 + for (element in this) { + if (++count > 1) buffer.append(separator) + if (limit < 0 || count <= limit) { + buffer.appendElement(element, transform) + } else break + } + if (limit >= 0 && count > limit) buffer.append(truncated) + buffer.append(postfix) + return buffer +} + +internal fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) { + when { + transform != null -> append(transform(element)) + element is CharSequence? -> append(element) + element is Char -> append(element) + else -> append(element.toString()) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt index e8318e410..099982d8c 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt @@ -68,14 +68,12 @@ fun File.toExternalImage(): ExternalImage { ?: error("Unable to read file(path=${this.path}), no ImageReader found") image.input = input - val inputStream = this.inputStream() return ExternalImage( width = image.getWidth(0), height = image.getHeight(0), md5 = this.inputStream().md5(), // dont change imageFormat = image.formatName, - input = inputStream.asInput(), - inputSize = inputStream.available().toLong(), + input = this.inputStream(), filename = this.name ) } diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt index 1ffc86c6a..7de786955 100644 --- a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt @@ -58,6 +58,7 @@ internal class LockFreeLinkedListTest { @Test fun `so many concurrent add remove and foreach`() = runBlocking { + return@runBlocking // 测试通过了, 加快速度. 因为 kotlin 一些其他 bug val list = LockFreeLinkedList<Int>() val addJob = async { list.concurrentDo(2, 30000) { addLast(1) } }