diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c6b72beb1e..4d25d223521 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,17 +27,17 @@ jobs: run: chmod -R 777 * - name: Init gradle project - run: ./gradlew clean --scan + run: ./gradlew clean --scan "-Porg.gradle.parallel=true" "-Porg.gradle.daemon=false" - name: Build all - run: ./gradlew assemble --scan + run: ./gradlew assemble --scan "-Porg.gradle.parallel=true" "-Porg.gradle.daemon=false" - name: All Tests run: > - ./gradlew check --scan + ./gradlew check --scan "-Porg.gradle.parallel=true" "-Porg.gradle.daemon=false" "-Dmirai.network.show.all.components=true" "-Dkotlinx.coroutines.debug=on" "-Dmirai.network.show.packet.details=true" - name: Ensure KDoc valid - run: ./gradlew dokkaHtmlMultiModule + run: ./gradlew dokkaHtmlMultiModule "-Porg.gradle.parallel=true" "-Porg.gradle.daemon=false" diff --git a/gradle.properties b/gradle.properties index d6d206af13c..156f4f5b36c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ kotlin.code.style=official # config kotlin.incremental.multiplatform=true kotlin.parallel.tasks.in.project=true -org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx6g -Dfile.encoding=UTF-8 org.gradle.parallel=true org.gradle.vfs.watch=true kotlin.mpp.enableGranularSourceSetsMetadata=true diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index a0017452e01..9b15fc7d684 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -31,7 +31,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect import net.mamoe.mirai.internal.utils.AtomicIntSeq import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache -import net.mamoe.mirai.internal.utils._miraiContentToString +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* @@ -112,7 +112,7 @@ internal sealed class AbstractUser( } else { throw contextualBugReportException( "Failed to compute friend image image from resourceId: ${resp.resourceId}", - resp._miraiContentToString(), + resp.structureToString(), additional = "并附加此时正在上传的文件" ) } diff --git a/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt b/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt index 55f161ac240..bfa2efadcf3 100644 --- a/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/imagesImpl.kt @@ -21,9 +21,9 @@ import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.User import net.mamoe.mirai.internal.network.protocol.data.proto.* -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX import net.mamoe.mirai.utils.* @@ -109,7 +109,7 @@ OnlineFriendImage() { Image.logger.warning( contextualBugReportException( "Failed to compute friend imageId: resId=${delegate.resId}", - delegate._miraiContentToString(), + delegate.structureToString(), additional = "并描述此时 Bot 是否正在从好友或群接受消息, 尽量附加该图片原文件" ) ) diff --git a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt index 0a8e8f5c498..ffca228e6c3 100644 --- a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt @@ -25,8 +25,8 @@ import net.mamoe.mirai.internal.getGroupByUinOrCodeOrFail import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.SourceMsg -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.toByteArray +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.OnlineMessageSource @@ -177,10 +177,10 @@ internal class OnlineMessageSourceFromGroupImpl( override val subject: GroupImpl by lazy { val groupCode = msg.first().msgHead.groupInfo?.groupCode - ?: error("cannot find groupCode for OnlineMessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") + ?: error("cannot find groupCode for OnlineMessageSourceFromGroupImpl. msg=${msg.structureToString()}") val group = bot.getGroup(groupCode)?.checkIsGroupImpl() - ?: error("cannot find group for OnlineMessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") + ?: error("cannot find group for OnlineMessageSourceFromGroupImpl. msg=${msg.structureToString()}") group } @@ -192,7 +192,7 @@ internal class OnlineMessageSourceFromGroupImpl( if (member != null) return@lazy member val anonymousInfo = msg.first().msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null } - ?: error("cannot find member for OnlineMessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") + ?: error("cannot find member for OnlineMessageSourceFromGroupImpl. msg=${msg.structureToString()}") anonymousInfo.run { group.newAnonymous(anonGroupMsg!!.anonNick.decodeToString(), anonGroupMsg.anonId.encodeBase64()) diff --git a/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt b/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt index aca2be60ca1..a5466163fa6 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt @@ -20,7 +20,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact -import net.mamoe.mirai.internal.utils._miraiContentToString +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.utils.* internal class UnconsumedNoticesAlerter( @@ -81,7 +81,7 @@ internal class UnconsumedNoticesAlerter( logger.debug( contextualBugReportException( "解析 OnlinePush.PbPushTransMsg, msgType=${data.msgType}", - data._miraiContentToString(), + data.structureToString(), null, "并描述此时机器人是否被踢出, 或是否有成员列表变更等动作.", ) @@ -127,7 +127,7 @@ internal class UnconsumedNoticesAlerter( data.msg?.context { throw contextualBugReportException( "解析 NewContact.SystemMsgNewGroup, subType=$subType, groupMsgType=$groupMsgType", - forDebug = this._miraiContentToString(), + forDebug = this.structureToString(), additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群", ) } @@ -139,7 +139,7 @@ internal class UnconsumedNoticesAlerter( if (logger.isEnabled && logger.isDebugEnabled) { throw contextualBugReportException( "decode SvcRequestPushStatus (PC Client status change)", - data._miraiContentToString(), + data.structureToString(), additional = "unknown status=${data.status}", ) } diff --git a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt index 5a47c27b529..5f9d60783c7 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt @@ -28,9 +28,9 @@ import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x122 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27 import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857 -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.parseToMessageDataList +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.utils.* internal class GroupNotificationProcessor( @@ -362,7 +362,7 @@ internal class GroupNotificationProcessor( else -> { markNotConsumed() logger.debug { - "Unknown Transformers528 0x14 template\ntemplId=${grayTip?.templId}\nPermList=${grayTip?.msgTemplParam?._miraiContentToString()}" + "Unknown Transformers528 0x14 template\ntemplId=${grayTip?.templId}\nPermList=${grayTip?.msgTemplParam?.structureToString()}" } } } diff --git a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt index 203845cd717..759f2d0d769 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt @@ -33,9 +33,9 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44 -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.parseToMessageDataList +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.internal.utils.toMemberInfo import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.context @@ -212,7 +212,7 @@ internal class GroupOrMemberListNoticeProcessor( } else { throw contextualBugReportException( "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType", - data._miraiContentToString(), + data.structureToString(), null, "并描述此时机器人是否被邀请加入群等其他", ) @@ -244,7 +244,7 @@ internal class GroupOrMemberListNoticeProcessor( } else -> throw contextualBugReportException( "parse SystemMsgNewGroup, subType=1", - this._miraiContentToString(), + this.structureToString(), additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群" ) } @@ -287,7 +287,7 @@ internal class GroupOrMemberListNoticeProcessor( else -> { throw contextualBugReportException( "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType", - this._miraiContentToString(), + this.structureToString(), null, "并描述此时机器人是否被踢出群等", ) diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt index e764c91fab8..3521ed24e34 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt @@ -36,9 +36,9 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0xb3.SubMs import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList.GetFriendGroupList import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.utils.* /** @@ -216,7 +216,7 @@ internal class FriendNoticeProcessor( } } if (body.msgProfileInfos.isEmpty() || containsUnknown) { - logger.debug { "Transformers528 0x27L: ProfileChanged new data: ${body._miraiContentToString()}" } + logger.debug { "Transformers528 0x27L: ProfileChanged new data: ${body.structureToString()}" } } } diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/OtherClientNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/OtherClientNoticeProcessor.kt index b470ce1e93b..855fdbe4890 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/priv/OtherClientNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/OtherClientNoticeProcessor.kt @@ -31,8 +31,8 @@ import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x7 -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.loadAs +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.buildMessageChain import net.mamoe.mirai.utils.context @@ -70,9 +70,9 @@ internal class OtherClientNoticeProcessor : MixedNoticeProcessor() { bot.network.logger.warning( contextualBugReportException( "SvcRequestPushStatus (OtherClient online)", - "packet: \n" + data._miraiContentToString() + + "packet: \n" + data.structureToString() + "\n\nquery: \n" + - Mirai.getOnlineOtherClientsList(bot)._miraiContentToString(), + Mirai.getOnlineOtherClientsList(bot).structureToString(), additional = "Failed to find corresponding instanceInfo.", ), ) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt index 7843b7eb513..44ff388ddbb 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt @@ -26,10 +26,10 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit import net.mamoe.mirai.internal.network.protocol.data.proto.MultiMsg import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.message.data.ForwardMessage import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.toMessageChain @@ -120,7 +120,7 @@ internal class MultiMsg { ) : Response() { override fun toString(): String { if (PacketCodec.PacketLogger.isEnabled) { - return _miraiContentToString() + return structureToString() } return "MultiMsg.ApplyUp.Response.RequireUpload" } @@ -168,7 +168,7 @@ internal class MultiMsg { //1 -> Response.OK(resId = response.msgResid) else -> { error(kotlin.run { - println(response._miraiContentToString()) + println(response.structureToString()) }.let { "Protocol error: MultiMsg.ApplyUp failed with result ${response.result}" }) } } @@ -221,7 +221,7 @@ internal class MultiMsg { //1 -> Response.OK(resId = response.msgResid) else -> throw contextualBugReportException( "MultiMsg.ApplyDown", - response._miraiContentToString(), + response.structureToString(), additional = "Decode failure result=${response.result}" ) } diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt index 955aae08e53..74d7d854303 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt @@ -41,8 +41,8 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.StatSvcGetOnline import net.mamoe.mirai.internal.network.protocol.data.proto.StatSvcSimpleGet import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.utils.NetworkType -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.* +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.internal.utils.toIpV4Long import net.mamoe.mirai.utils.* @@ -369,7 +369,7 @@ internal class StatSvc { else -> throw contextualBugReportException( "decode SvcReqMSFLoginNotify (OtherClient status change)", - notify._miraiContentToString(), + notify.structureToString(), additional = "unknown notify.status=${notify.status}" ) } diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt index c7c4aa861d1..1a3c6d8d533 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt @@ -22,9 +22,9 @@ import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLoginExt import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.analysisTlv0x531 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.orEmpty -import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.crypto.TEA -import net.mamoe.mirai.internal.utils.soutv +import net.mamoe.mirai.internal.utils.printStructure +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.utils.* internal class WtLogin { @@ -163,7 +163,7 @@ internal class WtLogin { val tlvMap: TlvMap = this._readTLVMap() if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) { - tlvMap.smartToString().soutv("tlvMap outer") + tlvMap.smartToString().printStructure("tlvMap outer") } // tlvMap.printTLVMap() @@ -185,7 +185,7 @@ internal class WtLogin { // 1, 15 -> onErrorMessage(tlvMap) ?: error("Cannot find error message") else -> { onErrorMessage(type.toInt(), tlvMap, bot) - ?: error("Cannot find error message, unknown login result type: $type, TLVMap = ${tlvMap._miraiContentToString()}") + ?: error("Cannot find error message, unknown login result type: $type, TLVMap = ${tlvMap.structureToString()}") } } } @@ -242,7 +242,7 @@ internal class WtLogin { // } else error("UNKNOWN CAPTCHA QUESTION: ${question.toUHexString()}, tlvMap=" + tlvMap.contentToString()) } - error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap._miraiContentToString()) + error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap.structureToString()) } fun onLoginSuccess(subCommand: Int, tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success { @@ -266,7 +266,7 @@ internal class WtLogin { val tlvMap119 = this._readTLVMap() if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) { - tlvMap119.smartToString().soutv("TlvMap119") + tlvMap119.smartToString().printStructure("TlvMap119") } tlvMap119[0x106]?.let { client.analyzeTlv106(it) } @@ -366,7 +366,7 @@ internal class WtLogin { } ?: emptyMap() if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) { - changeTokenTimeMap._miraiContentToString().soutv("tokenChangeTime") + changeTokenTimeMap.structureToString().printStructure("tokenChangeTime") } val outPSKeyMap: PSKeyMap? diff --git a/mirai-core/src/commonMain/kotlin/utils/contentToString.kt b/mirai-core/src/commonMain/kotlin/utils/contentToString.kt index 7c4832c4ad5..a70a13dbb1a 100644 --- a/mirai-core/src/commonMain/kotlin/utils/contentToString.kt +++ b/mirai-core/src/commonMain/kotlin/utils/contentToString.kt @@ -11,205 +11,53 @@ package net.mamoe.mirai.internal.utils -import kotlinx.serialization.Transient -import net.mamoe.mirai.IMirai +import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.debug -import net.mamoe.mirai.utils.toUHexString -import java.lang.reflect.Modifier -import kotlin.reflect.KClass -import kotlin.reflect.KProperty -import kotlin.reflect.KProperty1 -import kotlin.reflect.full.hasAnnotation -import kotlin.reflect.jvm.javaField +import net.mamoe.mirai.utils.loadService -private val indent: String = " ".repeat(4) +internal fun Any?.structureToString(): String = StructureToStringTransformer.instance.transform(this) -/** - * 将所有元素加入转换为多行的字符串表示. - */ -private fun Sequence.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { - return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform) -} +@Suppress("FunctionName") +@Deprecated( + "", + ReplaceWith("this.structureToString()", "net.mamoe.mirai.internal.utils.structureToString"), + level = DeprecationLevel.ERROR +) // kept for local developers for some time +@DeprecatedSinceMirai(errorSince = "2.10") +internal fun Any?._miraiContentToString(): String = this.structureToString() -private val SoutvLogger: MiraiLogger by lazy { MiraiLogger.Factory.create(IMirai::class, "soutv") } -internal fun Any?.soutv(name: String = "unnamed") { - @Suppress("DEPRECATION") - SoutvLogger.debug { "$name = ${this._miraiContentToString()}" } +private val SoutvLogger: MiraiLogger by lazy { + MiraiLogger.Factory.create( + StructureToStringTransformer::class, + "printStructurally" + ) } -/** - * 将内容格式化为较可读的字符串输出. - * - * 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. e.g. `0x1000(4096)` - * [ByteArray] 和 [UByteArray]: 十六进制表示, 通过 [ByteArray.toUHexString] - * [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString. - * [Map]: 多行输出. 每行显示一个值. 递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 - * `data class`: 调用其 [toString] - * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 - */ -@Suppress("FunctionName") // 这样就不容易被 IDE 提示 -internal fun Any?._miraiContentToString(prefix: String = ""): String = when (this) { - is Unit -> "Unit" - is UInt -> "0x" + this.toUHexString("") + "($this)" - is UByte -> "0x" + this.toUHexString() + "($this)" - is UShort -> "0x" + this.toUHexString("") + "($this)" - is ULong -> "0x" + this.toUHexString("") + "($this)" - is Int -> "0x" + this.toUHexString("") + "($this)" - is Byte -> "0x" + this.toUHexString() + "($this)" - is Short -> "0x" + this.toUHexString("") + "($this)" - is Long -> "0x" + this.toUHexString("") + "($this)" +@Deprecated( + "", + ReplaceWith("this.printStructurally(name)", "net.mamoe.mirai.internal.utils.printStructurally"), + level = DeprecationLevel.ERROR +) +@DeprecatedSinceMirai(errorSince = "2.10") +internal fun Any?.soutv(name: String = "unnamed") = this.printStructure(name) - is Boolean -> if (this) "true" else "false" +internal fun Any?.printStructure(name: String = "unnamed") { + return SoutvLogger.debug { "$name = ${this.structureToString()}" } +} - is ByteArray -> { - if (this.size == 0) "" - else this.toUHexString() - } - is UByteArray -> { - if (this.size == 0) "" - else this.toUHexString() - } - is ShortArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is IntArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is LongArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is FloatArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is DoubleArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is UShortArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is UIntArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is ULongArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is Array<*> -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } - is BooleanArray -> { - if (this.size == 0) "" - else this.iterator()._miraiContentToString() - } +internal fun interface StructureToStringTransformer { + fun transform(any: Any?): String - is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } - is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } - is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } - is Map<*, *> -> this.entries.joinToString( - prefix = "{", - postfix = "}" - ) { it.key._miraiContentToString(prefix) + "=" + it.value._miraiContentToString(prefix) } - else -> { - if (this == null) "null" - else if (this::class.isData) this.toString() - else { - if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) { - this.contentToStringReflectively(prefix + indent) - } else this.toString() - /* - (this::class.simpleName ?: "") + "#" + this::class.hashCode() + "{\n" + - this::class.members.asSequence().filterIsInstance>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC } - .joinToStringPrefixed( - prefix = indent - ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(indent) }.getOrElse { "" } } - */ + companion object { + private class ObjectToStringStructureToStringTransformer : StructureToStringTransformer { + override fun transform(any: Any?): String = any.toString() } - } -} -internal fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? { - return this.javaField?.apply { isAccessible = true }?.get(receiver) -} - -private fun Any.canBeIgnored(): Boolean { - return when (this) { - is String -> this.isEmpty() - is ByteArray -> this.isEmpty() - is Array<*> -> this.isEmpty() - is Number -> this == 0 - is Int -> this == 0 - is Float -> this == 0 - is Double -> this == 0 - is Byte -> this == 0 - is Short -> this == 0 - is Long -> this == 0 - else -> false + val instance by lazy { + loadService(StructureToStringTransformer::class) { ObjectToStringStructureToStringTransformer() } + } } } -private fun Any.contentToStringReflectively( - prefix: String, - filter: ((name: String, value: Any?) -> Boolean)? = null, -): String { - val newPrefix = "$prefix " - return (this::class.simpleName ?: "") + "#" + this::class.hashCode() + " {\n" + - this.allMembersFromSuperClassesMatching { it.qualifiedName?.startsWith("net.mamoe.mirai") == true } - .distinctBy { it.name } - .filterNot { it.name.contains("$") || it.name == "Companion" || it.isConst || it.name == "serialVersionUID" } - .mapNotNull { - val value = it.getValueAgainstPermission(this) ?: return@mapNotNull null - if (filter != null) { - if (!filter(it.name, value)) - return@mapNotNull it.name to value - else { - return@mapNotNull null - } - } - it.name to value - } - .joinToStringPrefixed( - prefix = newPrefix - ) { (name: String, value: Any?) -> - if (value.canBeIgnored()) "" - else { - "$name=" + kotlin.runCatching { - if (value == this) "" - else value._miraiContentToString(newPrefix) - }.getOrElse { "" } - } - }.lines().filterNot { it.isBlank() }.joinToString("\n") + "\n$prefix}" -} - -private fun KClass.thisClassAndSuperclassSequence(): Sequence> { - return sequenceOf(this) + - this.supertypes.asSequence() - .mapNotNull { type -> - type.classifier?.takeIf { it is KClass<*> }?.takeIf { it != Any::class } as? KClass - }.flatMap { it.thisClassAndSuperclassSequence() } -} - -@Suppress("UNCHECKED_CAST") -private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass) -> Boolean): Sequence> { - return this::class.thisClassAndSuperclassSequence() - .filter { classFilter(it) } - .map { it.members } - .flatMap { it.asSequence() } - .filterIsInstance>() - .filterNot { it.hasAnnotation() } - .filterNot { it.isTransient() } - .mapNotNull { it as KProperty1 } -} - -internal fun KProperty<*>.isTransient(): Boolean = - javaField?.modifiers?.and(Modifier.TRANSIENT) != 0 - diff --git a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt index dfc9f094aec..d25ff113d17 100644 --- a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt +++ b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt @@ -24,7 +24,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars -import net.mamoe.mirai.internal.utils.soutv +import net.mamoe.mirai.internal.utils.printStructure import net.mamoe.mirai.utils.read import net.mamoe.mirai.utils.readPacketExact import kotlin.contracts.InvocationKind @@ -171,7 +171,7 @@ internal fun ByteArray.loadAs(deserializer: DeserializationStrate internal fun ByteArray.loadOidb(deserializer: DeserializationStrategy, log: Boolean = false): T { val oidb = loadAs(OidbSso.OIDBSSOPkg.serializer()) if (log) { - oidb.soutv("OIDB") + oidb.printStructure("OIDB") } return oidb.bodybuffer.loadAs(deserializer) } diff --git a/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt b/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt index 1fe94c5d398..d8ad24a96f8 100644 --- a/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt +++ b/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.internal.notice.test import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.protobuf.ProtoNumber -import net.mamoe.mirai.internal.notice.Desensitizer +import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.yamlkt.Yaml diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDescVisitor.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/RemoveDefaultValuesVisitor.kt similarity index 52% rename from mirai-core/src/commonTest/kotlin/utils/codegen/ValueDescVisitor.kt rename to mirai-core/src/commonTest/kotlin/testFramework/codegen/RemoveDefaultValuesVisitor.kt index 7c9a0083230..60f2e93b310 100644 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDescVisitor.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/RemoveDefaultValuesVisitor.kt @@ -7,107 +7,33 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.utils.codegen - -import net.mamoe.mirai.utils.cast -import kotlin.reflect.KClass +package net.mamoe.mirai.internal.testFramework.codegen + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.accept +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitorUnit +import net.mamoe.mirai.internal.testFramework.codegen.visitors.AnalyzeDefaultValuesMappingVisitor +import net.mamoe.mirai.internal.testFramework.codegen.visitors.DefaultValuesMapping import kotlin.reflect.KParameter -import kotlin.reflect.KProperty -import kotlin.reflect.KProperty1 -import kotlin.reflect.full.memberProperties -import kotlin.reflect.full.primaryConstructor - -interface ValueDescVisitor { - fun visitValue(desc: ValueDesc) {} - - fun visitPlain(desc: PlainValueDesc) { - visitValue(desc) - } - - fun visitArray(desc: ArrayValueDesc) { - visitValue(desc) - for (element in desc.elements) { - element.accept(this) - } - } - - fun visitObjectArray(desc: ObjectArrayValueDesc) { - visitArray(desc) - } - fun visitCollection(desc: CollectionValueDesc) { - visitArray(desc) - } - fun visitMap(desc: MapValueDesc) { - visitValue(desc) - for ((key, value) in desc.elements.entries) { - key.accept(this) - value.accept(this) - } - } - - fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc) { - visitArray(desc) - } - - fun visitClass(desc: ClassValueDesc) { - visitValue(desc) - desc.properties.forEach { (_, u) -> - u.accept(this) - } - } -} - - -class DefaultValuesMapping( - val forClass: KClass<*>, - val mapping: MutableMap = mutableMapOf() -) { - operator fun get(property: KProperty<*>): Any? = mapping[property.name] -} - -class AnalyzeDefaultValuesMappingVisitor : ValueDescVisitor { - val mappings: MutableList = mutableListOf() - - override fun visitClass(desc: ClassValueDesc) { - super.visitClass(desc) - - if (mappings.any { it.forClass == desc.type }) return - - val defaultInstance = - createInstanceWithMostDefaultValues(desc.type, desc.properties.mapValues { it.value.origin }) - - val optionalParameters = desc.type.primaryConstructor!!.parameters.filter { it.isOptional } - - mappings.add( - DefaultValuesMapping( - desc.type, - optionalParameters.associateTo(mutableMapOf()) { param -> - val value = findCorrespondingProperty(desc, param).get(defaultInstance) - param.name!! to value - } - ) - ) - } - - - private fun findCorrespondingProperty( - desc: ClassValueDesc, - param: KParameter - ) = desc.type.memberProperties.single { it.name == param.name }.cast>() - - private fun createInstanceWithMostDefaultValues(clazz: KClass, arguments: Map): T { - val primaryConstructor = clazz.primaryConstructor ?: error("Type $clazz does not have primary constructor.") - return primaryConstructor.callBy(arguments.filter { !it.key.isOptional }) - } +fun ValueDesc.removeDefaultValues(): ValueDesc { + val def = AnalyzeDefaultValuesMappingVisitor() + this.accept(def) + this.accept(RemoveDefaultValuesVisitor(def.mappings)) + return this } class RemoveDefaultValuesVisitor( private val mappings: MutableList, -) : ValueDescVisitor { - override fun visitClass(desc: ClassValueDesc) { - super.visitClass(desc) +) : ValueDescVisitorUnit { + override fun visitValue(desc: ValueDesc, data: Nothing?) { + desc.acceptChildren(this, data) + } + + override fun visitClass(desc: ClassValueDesc, data: Nothing?) { + super.visitClass(desc, data) val mapping = mappings.find { it.forClass == desc.type }?.mapping ?: return // remove properties who have the same values as their default values, this would significantly reduce code size. @@ -175,4 +101,4 @@ class RemoveDefaultValuesVisitor( else -> false } } -} +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/ValueDescAnalyzer.kt similarity index 64% rename from mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt rename to mirai-core/src/commonTest/kotlin/testFramework/codegen/ValueDescAnalyzer.kt index 60bdbdd6da3..2bea3b924e0 100644 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/ValueDescAnalyzer.kt @@ -7,9 +7,12 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.utils.codegen +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen import kotlinx.serialization.Serializable +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* import net.mamoe.mirai.utils.cast import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 @@ -20,7 +23,10 @@ import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.valueParameters import kotlin.reflect.typeOf -object ConstructorCallCodegenFacade { +object ValueDescAnalyzer { + private val anyType = typeOf() + + /** * Analyze [value] and give its correspondent [ValueDesc]. */ @@ -46,17 +52,22 @@ object ConstructorCallCodegenFacade { return ClassValueDesc(null, value, map) } - ArrayValueDesc.createOrNull(value, type, null)?.let { return it } + CollectionLikeValueDesc.createOrNull(value, type, null)?.let { return it } if (value is Collection<*>) { - return CollectionValueDesc(null, value, arrayType = type, elementType = type.arguments.first().type!!) + return CollectionValueDesc( + null, + value, + arrayType = type, + elementType = type.arguments.firstOrNull()?.type ?: anyType + ) } else if (value is Map<*, *>) { return MapValueDesc( null, value.cast(), value.cast(), type, - type.arguments.first().type!!, - type.arguments[1].type!! + type.arguments.firstOrNull()?.type ?: anyType, + type.arguments.getOrNull(1)?.type ?: anyType ) } @@ -70,36 +81,9 @@ object ConstructorCallCodegenFacade { else -> PlainValueDesc(null, value.toString(), value) } } - - /** - * Generate source code to construct the value represented by [desc]. - */ - fun generate(desc: ValueDesc, context: CodegenContext = CodegenContext()): String { - if (context.configuration.removeDefaultValues) { - val def = AnalyzeDefaultValuesMappingVisitor() - desc.accept(def) - desc.accept(RemoveDefaultValuesVisitor(def.mappings)) - } - - ValueCodegen(context).generate(desc) - return context.getResult() - } - - fun analyzeAndGenerate(value: Any?, type: KType, context: CodegenContext = CodegenContext()): String { - return generate(analyze(value, type), context) - } } @OptIn(ExperimentalStdlibApi::class) -inline fun ConstructorCallCodegenFacade.analyze(value: T): ValueDesc { +inline fun ValueDescAnalyzer.analyze(value: T): ValueDesc { return analyze(value, typeOf()) -} - -@OptIn(ExperimentalStdlibApi::class) -inline fun ConstructorCallCodegenFacade.analyzeAndGenerate( - value: T, - context: CodegenContext = CodegenContext() -): String { - return analyzeAndGenerate(value, typeOf(), context) -} - +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt new file mode 100644 index 00000000000..a5693b90af5 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt @@ -0,0 +1,46 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KClass +import kotlin.reflect.KParameter + +class ClassValueDesc( + override val parent: ValueDesc?, + override val origin: T, + val properties: MutableMap, +) : ValueDesc { + val type: KClass by lazy { origin::class } + + override fun accept(visitor: ValueDescVisitor, data: D): R { + return visitor.visitClass(this, data) + } + + override fun acceptChildren(visitor: ValueDescVisitor, data: D) { + properties.forEach { (_, u) -> + u.accept(visitor, data) + } + } + + override fun transformChildren(visitor: ValueDescTransformer, data: D) { + val result = mutableMapOf() + for (entry in this.properties.entries) { + entry.value.acceptChildren(visitor, data) + val newValue = entry.value.accept(visitor, data) + if (newValue != null) { + result[entry.key] = newValue + } + } + this.properties.clear() + this.properties.putAll(result) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt new file mode 100644 index 00000000000..b0eb95a350b --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt @@ -0,0 +1,67 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +sealed interface CollectionLikeValueDesc : ValueDesc { + val value: Any + + val arrayType: KType + val elementType: KType + val elements: MutableList + + override fun acceptChildren(visitor: ValueDescVisitor, data: D) { + for (element in elements) { + element.accept(visitor, data) + } + } + + override fun transformChildren(visitor: ValueDescTransformer, data: D) { + elements.transform { it.accept(visitor, data) } + } + + companion object { + @OptIn(ExperimentalStdlibApi::class) + fun createOrNull(array: Any, type: KType, parent: ValueDesc?): CollectionLikeValueDesc? { + if (array is Array<*>) return ObjectArrayValueDesc(parent, array, arrayType = type) + return when (array) { + is IntArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) + is ByteArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) + is ShortArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) + is CharArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) + is LongArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) + is FloatArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) + is DoubleArray -> PrimitiveArrayValueDesc( + parent, + array, + arrayType = type, + elementType = typeOf() + ) + is BooleanArray -> PrimitiveArrayValueDesc( + parent, + array, + arrayType = type, + elementType = typeOf() + ) + else -> return null + } + } + } +} + +fun MutableList.transform(transformer: (E) -> E?) { + val result = this.asSequence().mapNotNull(transformer).toMutableList() + this.clear() + this.addAll(result) +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt new file mode 100644 index 00000000000..3baae13adc4 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt @@ -0,0 +1,42 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +class CollectionValueDesc( + override val parent: ValueDesc?, + value: Collection<*>, + override val origin: Collection<*> = value, + override val arrayType: KType, + override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType() +) : CollectionLikeValueDesc { + + override var value: Collection<*> = value + set(value) { + field = value + elements.clear() + elements.addAll(initializeElements(value)) + } + + override val elements: MutableList by lazy { + initializeElements(value) + } + + private fun initializeElements(value: Collection<*>) = value.mapTo(ArrayList(value.size)) { + ValueDescAnalyzer.analyze(it, elementType) + } + + + override fun accept(visitor: ValueDescVisitor, data: D): R = visitor.visitCollection(this, data) +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt new file mode 100644 index 00000000000..e928b808bc5 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt @@ -0,0 +1,56 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +class MapValueDesc( + override val parent: ValueDesc?, + var value: Map, + override val origin: Map = value, + val mapType: KType, + val keyType: KType = mapType.arguments.first().type ?: Any::class.createType(), + val valueType: KType = mapType.arguments[1].type ?: Any::class.createType(), +) : ValueDesc { + val elements: MutableMap by lazy { + value.map { + ValueDescAnalyzer.analyze(it.key, keyType) to ValueDescAnalyzer.analyze( + it.value, + valueType + ) + }.toMap(mutableMapOf()) + } + + override fun accept(visitor: ValueDescVisitor, data: D): R = visitor.visitMap(this, data) + + override fun acceptChildren(visitor: ValueDescVisitor, data: D) { + for ((key, value) in elements.entries) { + key.accept(visitor, data) + value.accept(visitor, data) + } + } + + override fun transformChildren(visitor: ValueDescTransformer, data: D) { + val resultMap = mutableMapOf() + for (entry in this.elements.entries) { + val newKey = entry.key.accept(visitor, data) + val newValue = entry.value.accept(visitor, data) + if (newKey != null && newValue != null) { + resultMap[newKey] = newValue + } + } + this.elements.clear() + this.elements.putAll(resultMap) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt new file mode 100644 index 00000000000..9b93a7b34e0 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt @@ -0,0 +1,43 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +class ObjectArrayValueDesc( + override val parent: ValueDesc?, + value: Array<*>, + override val origin: Array<*> = value, + override val arrayType: KType, + override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType(), +) : CollectionLikeValueDesc { + + override var value: Array<*> = value + set(value) { + field = value + elements.clear() + elements.addAll(initializeElements(value)) + } + + override val elements: MutableList by lazy { + initializeElements(value) + } + + private fun initializeElements(value: Array<*>) = value.mapTo(ArrayList(value.size)) { + ValueDescAnalyzer.analyze(it, elementType) + } + + + override fun accept(visitor: ValueDescVisitor, data: D): R = visitor.visitObjectArray(this, data) + +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt new file mode 100644 index 00000000000..b9578d7a8f2 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt @@ -0,0 +1,33 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor + +class PlainValueDesc( + override val parent: ValueDesc?, + var value: String, + override val origin: Any? +) : ValueDesc { + init { + require(value.isNotBlank()) + } + + override fun accept(visitor: ValueDescVisitor, data: D): R { + return visitor.visitPlain(this, data) + } + + override fun acceptChildren(visitor: ValueDescVisitor, data: D) { + } + + override fun transformChildren(visitor: ValueDescTransformer, data: D) { + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt new file mode 100644 index 00000000000..90e3df80908 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt @@ -0,0 +1,49 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType + +class PrimitiveArrayValueDesc( + override val parent: ValueDesc?, + value: Any, + override val origin: Any = value, + override val arrayType: KType, + override val elementType: KType +) : CollectionLikeValueDesc { + override var value: Any = value + set(value) { + field = value + elements.clear() + elements.addAll(initializeElements(value)) + } + + override val elements: MutableList by lazy { + initializeElements(value) + } + + private fun initializeElements(value: Any): MutableList = when (value) { + is IntArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + is ByteArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + is ShortArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + is CharArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + is LongArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + is FloatArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + is DoubleArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + is BooleanArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } + else -> error("$value is not an array.") + } + + override fun accept(visitor: ValueDescVisitor, data: D): R { + return visitor.visitPrimitiveArray(this, data) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt new file mode 100644 index 00000000000..aa18185d355 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt @@ -0,0 +1,43 @@ +/* + * 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.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor + +sealed interface ValueDesc { + val origin: Any? + val parent: ValueDesc? + + fun accept(visitor: ValueDescVisitor, data: D): R + fun acceptChildren(visitor: ValueDescVisitor, data: D) + + fun transform(visitor: ValueDescTransformer, data: D): ValueDesc? = this.accept(visitor, data) + fun transformChildren(visitor: ValueDescTransformer, data: D) +} + +fun ValueDesc.accept(visitor: ValueDescVisitor): R = accept(visitor, null) +fun ValueDesc.transform(visitor: ValueDescTransformer) = transform(visitor, null) +fun ValueDesc.transform(visitor: ValueDescTransformerNotNull) = transform(visitor, null)!! +fun ValueDesc.acceptChildren(visitor: ValueDescVisitor) = acceptChildren(visitor, null) +fun ValueDesc.transformChildren(visitor: ValueDescTransformer) = transformChildren(visitor, null) + +val ValueDesc.parents + get() = sequence { + var parent = parent + do { + parent ?: return@sequence + yield(parent) + parent = parent.parent + } while (true) + } + +inline fun ValueDesc.findParent(): T? = parents.filterIsInstance().firstOrNull() diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/IndenterTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/IndenterTest.kt new file mode 100644 index 00000000000..824bbfb12d5 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/IndenterTest.kt @@ -0,0 +1,23 @@ +/* + * 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.internal.testFramework.codegen.test + +import net.mamoe.mirai.internal.test.AbstractTest +import net.mamoe.mirai.internal.testFramework.codegen.visitors.WordingIndenter +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class IndenterTest : AbstractTest() { + + @Test + fun `can indentize`() { + assertEquals(" test", WordingIndenter.spacing(4).indentize("test")) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt new file mode 100644 index 00000000000..0e7c8f061e4 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt @@ -0,0 +1,50 @@ +/* + * 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.internal.testFramework.codegen.test + +import net.mamoe.mirai.internal.test.AbstractTest +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.transform +import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.ValueDescToStringRenderer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class OptimizeByteArrayAsHexStringTransformerTest : AbstractTest() { + + private inline fun analyzeTransformAndRender( + value: T, + renderer: ValueDescToStringRenderer = ValueDescToStringRenderer() + ): String? { + return ValueDescAnalyzer.analyze(value) + .transform(OptimizeByteArrayAsHexStringTransformer()) + ?.renderToString(renderer) + } + + @Test + fun `can optimize as string`() { + assertEquals( + """ + "test".toByteArray() /* 74 65 73 74 */ + """.trimIndent(), analyzeTransformAndRender("test".toByteArray()) + ) + } + + @Test + fun `can optimize as hex`() { + assertEquals( + """ + "O".toByteArray() /* 4F 02 */ + """.trimIndent(), analyzeTransformAndRender(byteArrayOf(0x4f, 0x02)) + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt new file mode 100644 index 00000000000..7b88fda1a93 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt @@ -0,0 +1,261 @@ +/* + * 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.internal.testFramework.codegen.test.visitors + +import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg +import net.mamoe.mirai.internal.testFramework.codegen.RemoveDefaultValuesVisitor +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.accept +import net.mamoe.mirai.internal.testFramework.codegen.visitors.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class ValueDescAnalyzerTest { + + private inline fun analyzeAndRender( + value: T + ): String { + return ValueDescAnalyzer.analyze(value).renderToString( + rendererContext = RendererContext( + indenter = Indenter.NoIndent, + classFormatter = object : ClassFormatter() { + override fun formatClassProperty( + context: ClassFormatterContext, + propertyString: String?, + valueString: String + ): String = "$propertyString=$valueString" + } + ), + ) + } + + @Test + fun `test plain`() { + assertEquals( + "\"test\"", + analyzeAndRender("test") + ) + assertEquals( + "1", + analyzeAndRender(1) + ) + assertEquals( + "1.0", + analyzeAndRender(1.0) + ) + } + + @Test + fun `test array`() { + assertEquals( + "arrayOf(1, 2)", + analyzeAndRender(arrayOf(1, 2)) + ) + assertEquals( + "arrayOf(5.0)", + analyzeAndRender(arrayOf(5.0)) + ) + assertEquals( + "arrayOf(\"1\")", + analyzeAndRender(arrayOf("1")) + ) + assertEquals( + """ + arrayOf( + arrayOf(1), + ) + """.trimIndent(), + analyzeAndRender(arrayOf(arrayOf(1))) + ) + } + + data class TestClass( + val value: String + ) + + data class TestClass2( + val value: Any + ) + + @Test + fun `test class`() { + assertEquals( + """ + ${TestClass::class.qualifiedName!!}( + value="test", + ) + """.trimIndent(), + analyzeAndRender(TestClass("test")) + ) + assertEquals( + """ + ${TestClass2::class.qualifiedName!!}( + value="test", + ) + """.trimIndent(), + analyzeAndRender(TestClass2("test")) + ) + assertEquals( + """ + ${TestClass2::class.qualifiedName!!}( + value=1, + ) + """.trimIndent(), + analyzeAndRender(TestClass2(1)) + ) + } + + data class TestNesting( + val nested: Nested + ) { + data class Nested( + val value: String + ) + } + + @Test + fun `test nesting`() { + assertEquals( + """ + ${TestNesting::class.qualifiedName}( + nested=${TestNesting.Nested::class.qualifiedName}( + value="test", + ), + ) + """.trimIndent(), + analyzeAndRender(TestNesting(TestNesting.Nested("test"))) + ) + } + + @Test + fun `test complex nesting`() { + assertEquals( + """ + net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.StructMsg( + version = 1, + msgType = 2, + msgSeq = 1630, + msgTime = 1630, + reqUin = 1230, + msg = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsg( + subType = 1, + msgTitle = "邀请加群", + msgDescribe = "邀请你加入 %group_name%", + actions = mutableListOf( + net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgAction( + name = "拒绝", + result = "已拒绝", + actionInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgActionInfo( + type = 12, + groupCode = 2230203, + ), + detailName = "拒绝", + ), + net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgAction( + name = "同意", + result = "已同意", + actionInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgActionInfo( + type = 11, + groupCode = 2230203, + ), + detailName = "同意", + ), + net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgAction( + name = "忽略", + result = "已忽略", + actionInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgActionInfo( + type = 14, + groupCode = 2230203, + ), + detailName = "忽略", + ), + ), + groupCode = 2230203, + actionUin = 1230001, + groupMsgType = 2, + groupInviterRole = 1, + groupInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.GroupInfo( + appPrivilegeFlag = 67698880, + ), + reqUinNick = "user3", + groupName = "testtest", + actionUinNick = "user1", + groupExtFlag = 1075905600, + actionUinQqNick = "user1", + reqUinGender = 255, + c2cInviteJoinGroupFlag = 1, + ), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze( + Structmsg.StructMsg( + version = 1, + msgType = 2, + msgSeq = 1630, + msgTime = 1630, + reqUin = 1230, + msg = Structmsg.SystemMsg( + subType = 1, + msgTitle = "邀请加群", + msgDescribe = "邀请你加入 %group_name%", + actions = mutableListOf( + Structmsg.SystemMsgAction( + name = "拒绝", + result = "已拒绝", + actionInfo = Structmsg.SystemMsgActionInfo( + type = 12, + groupCode = 2230203, + ), + detailName = "拒绝", + ), + Structmsg.SystemMsgAction( + name = "同意", + result = "已同意", + actionInfo = Structmsg.SystemMsgActionInfo( + type = 11, + groupCode = 2230203, + ), + detailName = "同意", + ), + Structmsg.SystemMsgAction( + name = "忽略", + result = "已忽略", + actionInfo = Structmsg.SystemMsgActionInfo( + type = 14, + groupCode = 2230203, + ), + detailName = "忽略", + ), + ), + groupCode = 2230203, + actionUin = 1230001, + groupMsgType = 2, + groupInviterRole = 1, + groupInfo = Structmsg.GroupInfo( + appPrivilegeFlag = 67698880, + ), + reqUinNick = "user3", + groupName = "testtest", + actionUinNick = "user1", + groupExtFlag = 1075905600, + actionUinQqNick = "user1", + reqUinGender = 255, + c2cInviteJoinGroupFlag = 1, + ), + ) + ).apply { + val def = AnalyzeDefaultValuesMappingVisitor() + accept(def) + accept(RemoveDefaultValuesVisitor(def.mappings)) + }.renderToString(ValueDescToStringRenderer()) + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt new file mode 100644 index 00000000000..e97a28feb8f --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt @@ -0,0 +1,198 @@ +/* + * 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 + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen.test.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.visitors.ValueDescToStringRenderer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class ValueDescToStringRendererTest { + private val renderer = ValueDescToStringRenderer() + + @Test + fun `plain value`() { + assertEquals("\"str\"", ValueDescAnalyzer.analyze("str").renderToString(renderer)) + assertEquals("1", ValueDescAnalyzer.analyze(1).renderToString(renderer)) + } + + @Test + fun `object array value`() { + assertEquals( + "arrayOf(\"str\", \"obj\")", + ValueDescAnalyzer.analyze(arrayOf("str", "obj")).renderToString(renderer) + ) + assertEquals("arrayOf(1, 2)", ValueDescAnalyzer.analyze(arrayOf(1, 2)).renderToString(renderer)) + } + + @Test + fun `object array value nested`() { + assertEquals( + """ + arrayOf( + arrayOf("str", "obj"), + arrayOf("str", "obj"), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(arrayOf(arrayOf("str", "obj"), arrayOf("str", "obj"))).renderToString(renderer) + ) + assertEquals( + """ + arrayOf( + arrayOf(1, 2), + arrayOf(1, 2), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(arrayOf(arrayOf(1, 2), arrayOf(1, 2))).renderToString(renderer) + ) + } + + @Test + fun `primitive array value`() { + assertEquals("intArrayOf(1, 2)", ValueDescAnalyzer.analyze(intArrayOf(1, 2)).renderToString(renderer)) + } + + @Test + fun `collection value`() { + assertEquals("mutableListOf(1, 2)", ValueDescAnalyzer.analyze(listOf(1, 2)).renderToString(renderer)) + assertEquals("mutableSetOf(1, 2)", ValueDescAnalyzer.analyze(setOf(1, 2)).renderToString(renderer)) + } + + @Test + fun `collection value nested`() { + assertEquals( + """ + mutableListOf( + mutableListOf(1, 2), + mutableListOf(1, 2), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(listOf(listOf(1, 2), listOf(1, 2))).renderToString(renderer) + ) + assertEquals( + """ + mutableSetOf( + mutableListOf(1, 2), + mutableListOf(2, 2), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(setOf(listOf(1, 2), listOf(2, 2))).renderToString(renderer) + ) + } + + @Test + fun `map value`() { + assertEquals( + """ + mutableMapOf( + 1 to 2, + 3 to 2, + ) + """.trimIndent(), ValueDescAnalyzer.analyze(mapOf(1 to 2, 3 to 2)).renderToString(renderer) + ) + } + + @Test + fun `map value nested`() { + assertEquals( + """ + |mutableMapOf( + | 1 to 2, + | 5 to mutableMapOf( + | 1 to 2, + | 3 to 2, + | ), + |) + """.trimMargin(), + ValueDescAnalyzer.analyze(mapOf(1 to 2, 5 to mapOf(1 to 2, 3 to 2))) + .renderToString(renderer) + ) + } + + data class MyClass( + val str: String, + val int: Int, + val self: MyClass?, + ) + + @Test + fun `class value`() { + data class MyClass2( + val str: String, + val int: Int, + val self: MyClass2?, + ) + + assertEquals( + """ + ${MyClass::class.qualifiedName}( + str = "str", + int = 1, + self = null, + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass("str", 1, null)).renderToString(renderer) + ) + + assertEquals( + """ + `${MyClass2::class.java.name}`( + str = "str", + int = 1, + self = null, + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass2("str", 1, null)).renderToString(renderer) + ) + } + + @Test + fun `class value nested`() { + data class MyClass2( + val str: String, + val int: Int, + val self: MyClass2?, + ) + + assertEquals( + """ + ${MyClass::class.qualifiedName}( + str = "str", + int = 1, + self = ${MyClass::class.qualifiedName}( + str = "str", + int = 1, + self = null, + ), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass("str", 1, MyClass("str", 1, null))).renderToString(renderer) + ) + + assertEquals( + """ + `${MyClass2::class.java.name}`( + str = "str", + int = 1, + self = `${MyClass2::class.java.name}`( + str = "str", + int = 1, + self = null, + ), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass2("str", 1, MyClass2("str", 1, null))).renderToString(renderer) + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt new file mode 100644 index 00000000000..32d4707eda5 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt @@ -0,0 +1,100 @@ +/* + * 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 + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen.visitor + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import kotlin.reflect.KParameter + +abstract class ValueDescTransformer : ValueDescVisitor { + override fun visitValue(desc: ValueDesc, data: D): ValueDesc? { + return desc + } + + override fun visitObjectArray(desc: ObjectArrayValueDesc, data: D): ValueDesc? { + val newElements = desc.elements.mapNotNull { element -> + element.acceptChildren(this, data) + element.accept(this, data) + } + return ObjectArrayValueDesc(desc.parent, desc.value, desc.origin, desc.arrayType, desc.elementType).apply { + elements.clear() + elements.addAll(newElements) + } + } + + override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: D): ValueDesc? { + val newElements = desc.elements.mapNotNull { element -> + element.acceptChildren(this, data) + element.accept(this, data) + } + return PrimitiveArrayValueDesc(desc.parent, desc.value, desc.origin, desc.arrayType, desc.elementType).apply { + elements.clear() + elements.addAll(newElements) + } + } + + override fun visitCollection(desc: CollectionValueDesc, data: D): ValueDesc? { + val newElements = desc.elements.mapNotNull { element -> + element.acceptChildren(this, data) + element.accept(this, data) + } + return CollectionValueDesc(desc.parent, desc.value, desc.origin, desc.arrayType, desc.elementType).apply { + elements.clear() + elements.addAll(newElements) + } + } + + override fun visitClass(desc: ClassValueDesc, data: D): ValueDesc? { + val resultMap = mutableMapOf() + for ((param, value) in desc.properties) { + value.accept(this, data)?.let { resultMap.put(param, it) } + } + return ClassValueDesc(desc.parent, desc.origin, resultMap) + } +} + +abstract class ValueDescTransformerNotNull : ValueDescTransformer() { + private fun fail(): Nothing { + throw IllegalStateException("ValueDescTransformerNotNull cannot return null from its 'visit' functions.") + } + + override fun visitValue(desc: ValueDesc, data: D): ValueDesc { + return super.visitValue(desc, data) ?: fail() + } + + override fun visitObjectArray(desc: ObjectArrayValueDesc, data: D): ValueDesc { + return super.visitObjectArray(desc, data) ?: fail() + } + + override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: D): ValueDesc { + return super.visitPrimitiveArray(desc, data) ?: fail() + } + + override fun visitCollection(desc: CollectionValueDesc, data: D): ValueDesc { + return super.visitCollection(desc, data) ?: fail() + } + + override fun visitClass(desc: ClassValueDesc, data: D): ValueDesc { + return super.visitClass(desc, data) ?: fail() + } + + override fun visitPlain(desc: PlainValueDesc, data: D): ValueDesc? { + return super.visitPlain(desc, data) ?: fail() + } + + override fun visitArray(desc: CollectionLikeValueDesc, data: D): ValueDesc { + return super.visitArray(desc, data) ?: fail() + } + + override fun visitMap(desc: MapValueDesc, data: D): ValueDesc { + return super.visitMap(desc, data) ?: fail() + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt new file mode 100644 index 00000000000..655eb897886 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt @@ -0,0 +1,44 @@ +/* + * 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.internal.testFramework.codegen.visitor + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* + +interface ValueDescVisitor { + fun visitValue(desc: ValueDesc, data: D): R + + fun visitPlain(desc: PlainValueDesc, data: D): R { + return visitValue(desc, data) + } + + fun visitArray(desc: CollectionLikeValueDesc, data: D): R { + return visitValue(desc, data) + } + + fun visitObjectArray(desc: ObjectArrayValueDesc, data: D): R { + return visitArray(desc, data) + } + + fun visitCollection(desc: CollectionValueDesc, data: D): R { + return visitArray(desc, data) + } + + fun visitMap(desc: MapValueDesc, data: D): R { + return visitValue(desc, data) + } + + fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: D): R { + return visitArray(desc, data) + } + + fun visitClass(desc: ClassValueDesc, data: D): R { + return visitValue(desc, data) + } +} diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt new file mode 100644 index 00000000000..b7831ce7312 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt @@ -0,0 +1,17 @@ +/* + * 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.internal.testFramework.codegen.visitor + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc + +interface ValueDescVisitorUnit : ValueDescVisitor { + override fun visitValue(desc: ValueDesc, data: Nothing?) { + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt new file mode 100644 index 00000000000..8e9b6740a4c --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt @@ -0,0 +1,68 @@ +/* + * 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.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitorUnit +import net.mamoe.mirai.utils.cast +import kotlin.reflect.KClass +import kotlin.reflect.KParameter +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor + +class DefaultValuesMapping( + val forClass: KClass<*>, + val mapping: MutableMap = mutableMapOf() +) { + operator fun get(property: KProperty<*>): Any? = mapping[property.name] +} + +class AnalyzeDefaultValuesMappingVisitor : ValueDescVisitorUnit { + val mappings: MutableList = mutableListOf() + + override fun visitValue(desc: ValueDesc, data: Nothing?) { + desc.acceptChildren(this, data) + } + + override fun visitClass(desc: ClassValueDesc, data: Nothing?) { + super.visitClass(desc, data) + + if (mappings.any { it.forClass == desc.type }) return + + val defaultInstance = + createInstanceWithMostDefaultValues(desc.type, desc.properties.mapValues { it.value.origin }) + + val optionalParameters = desc.type.primaryConstructor!!.parameters.filter { it.isOptional } + + mappings.add( + DefaultValuesMapping( + desc.type, + optionalParameters.associateTo(mutableMapOf()) { param -> + val value = findCorrespondingProperty(desc, param).get(defaultInstance) + param.name!! to value + } + ) + ) + } + + + private fun findCorrespondingProperty( + desc: ClassValueDesc, + param: KParameter + ) = desc.type.memberProperties.single { it.name == param.name }.cast>() + + private fun createInstanceWithMostDefaultValues(clazz: KClass, arguments: Map): T { + val primaryConstructor = clazz.primaryConstructor ?: error("Type $clazz does not have primary constructor.") + return primaryConstructor.callBy(arguments.filter { !it.key.isOptional }) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt new file mode 100644 index 00000000000..068258dfdb3 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt @@ -0,0 +1,34 @@ +/* + * 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.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc + +data class ClassFormatterContext( + val desc: ClassValueDesc<*>, + val visitor: ValueDescToStringRenderer, + val rendererContext: RendererContext, +) + +open class ClassFormatter { + open fun formatClassName(context: ClassFormatterContext): String { + val name = context.desc.type.qualifiedName ?: context.desc.type.java.name + return wrapBacktickIfNecessary(name) + } + + open fun formatClassProperty(context: ClassFormatterContext, propertyString: String?, valueString: String): String = + "$propertyString = $valueString" + + companion object { + protected fun wrapBacktickIfNecessary(name: String) = if (name.contains(' ') || name.contains('$')) { + "`$name`" + } else name + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/Indenter.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/Indenter.kt new file mode 100644 index 00000000000..e99c8f963c6 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/Indenter.kt @@ -0,0 +1,29 @@ +/* + * 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.internal.testFramework.codegen.visitors + +fun interface Indenter { + fun indentize(string: String): String + + object NoIndent : Indenter { + override fun indentize(string: String): String = string + } +} + +class WordingIndenter constructor( + private val separator: String, +) : Indenter { + + override fun indentize(string: String): String = "$separator$string" + + companion object { + fun spacing(count: Int = 4): Indenter = WordingIndenter(" ".repeat(count)) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt new file mode 100644 index 00000000000..e15243f40c5 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt @@ -0,0 +1,88 @@ +/* + * 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 + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull +import net.mamoe.mirai.utils.toUHexString +import kotlin.reflect.typeOf + +/** + * If the byte array was from [String.toByteArray], transform it to the [String]. + * Otherwise, transform it as hex string. + */ +open class OptimizeByteArrayAsHexStringTransformer : ValueDescTransformerNotNull() { + open fun transform(desc: CollectionLikeValueDesc, value: ByteArray): ValueDesc { + return if (isReadableString(value)) { + // prefers to show readable string + PlainValueDesc( + desc.parent, + value = "\"${ + value.decodeToString().escapeQuotation() + }\".toByteArray() /* ${value.toUHexString()} */", + origin = desc.origin + ) + } else { + PlainValueDesc( + desc.parent, + value = "\"${value.toUHexString()}\".hexToBytes()", + origin = desc.origin + ) + } + + } + + protected fun isReadableString(value: ByteArray) = + value.decodeToString().all { Character.isUnicodeIdentifierPart(it) || it.isWhitespace() } + + override fun visitValue(desc: ValueDesc, data: Nothing?): ValueDesc { + desc.acceptChildren(this, data) + return super.visitValue(desc, data) + } + + override fun visitObjectArray(desc: ObjectArrayValueDesc, data: Nothing?): ValueDesc { + if (desc.arrayType == arrayOfByteType) { + val array = desc.elements.mapNotNull { (it as? PlainValueDesc)?.value?.toByteOrNull() }.toByteArray() + if (array.size != desc.elements.size) return desc + + return transform(desc, array) + } + return super.visitObjectArray(desc, data) + } + + override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: Nothing?): ValueDesc { + if (desc.value is ByteArray) { + return transform(desc, desc.value as ByteArray) + } + return super.visitPrimitiveArray(desc, data) + } + + companion object { + private val arrayOfByteType = typeOf>() + private fun String.escapeQuotation(): String = buildString { this@escapeQuotation.escapeQuotationTo(this) } + + private fun String.escapeQuotationTo(out: StringBuilder) { + for (element in this) { + when (element) { + '\\' -> out.append("\\\\") + '\n' -> out.append("\\n") + '\r' -> out.append("\\r") + '\t' -> out.append("\\t") + '\"' -> out.append("\\\"") + else -> out.append(element) + } + } + } + + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt new file mode 100644 index 00000000000..a8a9c128c5f --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt @@ -0,0 +1,138 @@ +/* + * 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.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor + +class RendererContext( + val indenter: Indenter, + val classFormatter: ClassFormatter = ClassFormatter() +) + +fun ValueDesc.renderToString( + renderer: ValueDescToStringRenderer = ValueDescToStringRenderer(), + rendererContext: RendererContext = RendererContext(WordingIndenter.spacing(4)) +): String { + return accept( + renderer, + rendererContext + ) +} + +open class ValueDescToStringRenderer : ValueDescVisitor { + private inline val visitor get() = this + + private inline fun Appendable.withIndent(indenter: Indenter, block: Appendable.() -> Unit) { + append( + buildString(block) + .lineSequence() + .map { indenter.indentize(it) } + .joinToString("\n") + .trimEnd { it != '\n' && it.isWhitespace() } + ) + } + + override fun visitValue(desc: ValueDesc, data: RendererContext): String { + return desc.toString() // tentative fallback + } + + override fun visitPlain(desc: PlainValueDesc, data: RendererContext): String { + return desc.value + } + + override fun visitArray(desc: CollectionLikeValueDesc, data: RendererContext): String = buildString { + val array = desc.value + + fun impl(funcName: String, elements: List) { + if (elements.any { it !is PlainValueDesc }) { + // complex types + append(funcName) + append('(') + appendLine() + withIndent(data.indenter) { + val list = elements.toList() + list.forEach { desc -> + append(desc.accept(visitor, data)) + appendLine(", ") + } + } + append(')') + } else { + // primitive types + append(funcName) + append('(') + val list = elements.toList() + list.forEachIndexed { index, desc -> + append(desc.accept(visitor, data)) + if (index != list.lastIndex) append(", ") + } + append(')') + } + } + + when (array) { + is Array<*> -> impl("arrayOf", desc.elements) + is IntArray -> impl("intArrayOf", desc.elements) + is ByteArray -> impl("byteArrayOf", desc.elements) + is ShortArray -> impl("shortArrayOf", desc.elements) + is CharArray -> impl("charArrayOf", desc.elements) + is LongArray -> impl("longArrayOf", desc.elements) + is FloatArray -> impl("floatArrayOf", desc.elements) + is DoubleArray -> impl("doubleArrayOf", desc.elements) + is BooleanArray -> impl("booleanArrayOf", desc.elements) + is List<*> -> impl("mutableListOf", desc.elements) + is Set<*> -> impl("mutableSetOf", desc.elements) + else -> error("$array is not an array.") + } + } + + override fun visitMap(desc: MapValueDesc, data: RendererContext): String = buildString { + appendLine("mutableMapOf(") + for ((key, value) in desc.elements) { + withIndent(data.indenter) { + append(key.accept(visitor, data)) + append(" to ") + append(value.accept(visitor, data)) + appendLine(",") + } + } + append(")") + } + + override fun visitClass(desc: ClassValueDesc, data: RendererContext): String = buildString { + val classFormatterContext = ClassFormatterContext(desc, visitor, data) + appendLine("${data.classFormatter.formatClassName(classFormatterContext)}(") + for ((param, valueDesc) in desc.properties) { + withIndent(data.indenter) { + append( + data.classFormatter.formatClassProperty( + classFormatterContext, + param.name, + valueDesc.accept(visitor, data) + ) + ) + } + appendLine(",") + } + append(")") + } + + open fun renderClassName(desc: ClassValueDesc<*>): String { + val name = desc.type.qualifiedName ?: desc.type.java.name + return wrapBacktickIfNecessary(name) + } + + companion object { + protected fun wrapBacktickIfNecessary(name: String) = if (name.contains(' ') || name.contains('$')) { + "`$name`" + } else name + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt b/mirai-core/src/commonTest/kotlin/testFramework/desensitizer/Desensitizer.kt similarity index 62% rename from mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt rename to mirai-core/src/commonTest/kotlin/testFramework/desensitizer/Desensitizer.kt index d06cc07d239..938768e4012 100644 --- a/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/desensitizer/Desensitizer.kt @@ -7,12 +7,17 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.notice +package net.mamoe.mirai.internal.testFramework.desensitizer import kotlinx.serialization.decodeFromString import net.mamoe.mirai.Mirai -import net.mamoe.mirai.internal.notice.Desensitizer.Companion.generateAndDesensitize -import net.mamoe.mirai.internal.utils.codegen.* +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import net.mamoe.mirai.internal.testFramework.codegen.removeDefaultValues +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull +import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.ProtocolStruct @@ -74,19 +79,20 @@ internal class Desensitizer private constructor( fun desensitize(string: String): String = instance.desensitize(string) - fun ConstructorCallCodegenFacade.generateAndDesensitize( + fun ValueDescAnalyzer.generateAndDesensitize( value: Any?, type: KType, desensitizer: Desensitizer = instance, ): String { - val a = analyze(value, type).apply { - accept(DesensitizationVisitor(desensitizer)) - } - return generate(a) + return analyze(value, type) + .transform(OptimizeByteArrayAsHexStringTransformer()) + .removeDefaultValues() + .transform(DesensitizationVisitor(desensitizer)) + .renderToString() } @OptIn(ExperimentalStdlibApi::class) - inline fun ConstructorCallCodegenFacade.generateAndDesensitize( + inline fun ValueDescAnalyzer.generateAndDesensitize( value: T, desensitizer: Desensitizer = instance, ): String = generateAndDesensitize(value, typeOf(), desensitizer) @@ -161,52 +167,70 @@ private val format = Yaml { private class DesensitizationVisitor( private val desensitizer: Desensitizer, -) : ValueDescVisitor { - override fun visitPlain(desc: PlainValueDesc) { - desc.value = desensitizer.desensitize(desc.value) +) : ValueDescTransformerNotNull() { + override fun visitValue(desc: ValueDesc, data: Nothing?): ValueDesc { + desc.acceptChildren(this, data) + return super.visitValue(desc, data) } - override fun visitObjectArray(desc: ObjectArrayValueDesc) { - if (desc.arrayType.arguments.first().type?.classifier == Byte::class) { // variance is ignored - @Suppress("UNCHECKED_CAST") - desc.value = desensitizer.desensitize(desc.value as Array) - } else { - for (element in desc.elements) { - element.accept(this) - } - } + override fun visitPlain(desc: PlainValueDesc, data: Nothing?): ValueDesc { + return PlainValueDesc(desc.parent, desensitizer.desensitize(desc.value), desc.origin) } - override fun visitCollection(desc: CollectionValueDesc) { - for (element in desc.elements) { - element.accept(this) + override fun visitObjectArray(desc: ObjectArrayValueDesc, data: Nothing?): ValueDesc { + return if ( + desc.arrayType.arguments.firstOrNull()?.type?.classifier == Byte::class + || (desc.value as? Array<*>)?.getOrNull(0) is Byte + ) { + @Suppress("UNCHECKED_CAST") + ObjectArrayValueDesc( + desc.parent, + desensitizer.desensitize(desc.value as Array), + desc.origin, + desc.arrayType, + desc.elementType + ) + } else { + super.visitObjectArray(desc, data) } } - override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc) { - if (desc.value is ByteArray) { - desc.value = desensitizer.desensitize(desc.value as ByteArray) - } + override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: Nothing?): ValueDesc { + return if (desc.value is ByteArray) { + PrimitiveArrayValueDesc( + desc.parent, + desensitizer.desensitize(desc.value as ByteArray), + desc.origin, + desc.arrayType, + desc.elementType + ) + } else super.visitPrimitiveArray(desc, data) } - override fun visitClass(desc: ClassValueDesc) { - super.visitClass(desc) - desc.properties.replaceAll() { key, value -> - val annotation = key.findAnnotation() - if (annotation != null && value.origin is ByteArray) { - val instance = annotation.serializer.objectInstance ?: annotation.serializer.createInstance() - - val result = instance.cast>() - .deserialize(desc.origin as ProtocolStruct, value.origin as ByteArray) - ?: desc.origin - - val generate = ConstructorCallCodegenFacade.generateAndDesensitize(result) - PlainValueDesc( - desc, - "$generate.toByteArray(${result::class.qualifiedName}.serializer())", - value.origin - ) - } else value + override fun visitClass(desc: ClassValueDesc, data: Nothing?): ValueDesc { + ClassValueDesc(desc.parent, desc.origin, desc.properties.toMutableMap().apply { + replaceAll() { key, value -> + val annotation = key.findAnnotation() + if (annotation != null && value.origin is ByteArray) { + val instance = annotation.serializer.objectInstance ?: annotation.serializer.createInstance() + + val result = instance.cast>() + .deserialize(desc.origin as ProtocolStruct, value.origin as ByteArray) + ?: desc.origin + + val generate = ValueDescAnalyzer.analyze(result) + .transform(OptimizeByteArrayAsHexStringTransformer()) + .transform(DesensitizationVisitor(desensitizer)) + .renderToString() + PlainValueDesc( + desc, + "$generate.toByteArray(${result::class.qualifiedName}.serializer())", + value.origin + ) + } else value + } + }).let { + return super.visitClass(it, data) } } } \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt b/mirai-core/src/commonTest/kotlin/testFramework/notice/RecordingNoticeHandler.kt similarity index 82% rename from mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt rename to mirai-core/src/commonTest/kotlin/testFramework/notice/RecordingNoticeHandler.kt index b3e5299443d..fde34f06221 100644 --- a/mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/notice/RecordingNoticeHandler.kt @@ -7,15 +7,15 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.notice +package net.mamoe.mirai.internal.testFramework.notice import kotlinx.atomicfu.atomic import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor -import net.mamoe.mirai.internal.notice.Desensitizer.Companion.generateAndDesensitize -import net.mamoe.mirai.internal.utils.codegen.ConstructorCallCodegenFacade +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer.Companion.generateAndDesensitize import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info @@ -42,7 +42,7 @@ internal class RecordingNoticeProcessor : SimpleNoticeProcessor( lock.withLock { id.getAndDecrement() logger.info { "Recorded #${id.value} ${data::class.simpleName}" } - logger.info { "Desensitized: \n\n\u001B[0m" + ConstructorCallCodegenFacade.generateAndDesensitize(data) + "\n\n" } + logger.info { "Desensitized: \n\n\u001B[0m" + ValueDescAnalyzer.generateAndDesensitize(data) + "\n\n" } } } } diff --git a/mirai-core/src/commonTest/kotlin/testFramework/package.kt b/mirai-core/src/commonTest/kotlin/testFramework/package.kt new file mode 100644 index 00000000000..9d69d364ddf --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/package.kt @@ -0,0 +1,10 @@ +/* + * 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.internal.testFramework \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueCodegen.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/ValueCodegen.kt deleted file mode 100644 index 6954d9120c1..00000000000 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueCodegen.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.internal.utils.codegen - -import net.mamoe.mirai.utils.toUHexString - -class ValueCodegen( - val context: CodegenContext -) { - fun generate(desc: ValueDesc) { - when (desc) { - is PlainValueDesc -> generate(desc) - is ObjectArrayValueDesc -> generate(desc) - is PrimitiveArrayValueDesc -> generate(desc) - is CollectionValueDesc -> generate(desc) - is ClassValueDesc<*> -> generate(desc) - is MapValueDesc -> generate(desc) - } - } - - fun generate(desc: PlainValueDesc) { - context.append(desc.value) - } - - fun generate(desc: MapValueDesc) { - context.run { - appendLine("mutableMapOf(") - for ((key, value) in desc.elements) { - generate(key) - append(" to ") - generate(value) - appendLine(",") - } - append(")") - } - } - - fun generate(desc: ClassValueDesc) { - context.run { - appendLine("${desc.type.qualifiedName}(") - for ((param, valueDesc) in desc.properties) { - append(param.name) - append("=") - generate(valueDesc) - appendLine(",") - } - append(")") - } - } - - fun generate(desc: ArrayValueDesc) { - val array = desc.value - - fun impl(funcName: String, elements: List) { - context.run { - append(funcName) - append('(') - val list = elements.toList() - list.forEachIndexed { index, desc -> - generate(desc) - if (index != list.lastIndex) append(", ") - } - append(')') - } - } - - return when (array) { - is Array<*> -> impl("arrayOf", desc.elements) - is IntArray -> impl("intArrayOf", desc.elements) - is ByteArray -> { - if (array.size == 0) { - context.append("net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY") // let IDE to shorten references. - return - } else { - if (array.decodeToString().all { Character.isUnicodeIdentifierPart(it) || it.isWhitespace() }) { - // prefers to show readable string - context.append( - "\"${ - array.decodeToString().escapeQuotation() - }\".toByteArray() /* ${array.toUHexString()} */" - ) - } else { - context.append("\"${array.toUHexString()}\".hexToBytes()") - } - return - } - } - is ShortArray -> impl("shortArrayOf", desc.elements) - is CharArray -> impl("charArrayOf", desc.elements) - is LongArray -> impl("longArrayOf", desc.elements) - is FloatArray -> impl("floatArrayOf", desc.elements) - is DoubleArray -> impl("doubleArrayOf", desc.elements) - is BooleanArray -> impl("booleanArrayOf", desc.elements) - is List<*> -> impl("mutableListOf", desc.elements) - is Set<*> -> impl("mutableSetOf", desc.elements) - else -> error("$array is not an array.") - } - } -} - -class CodegenContext( - val sb: StringBuilder = StringBuilder(), - val configuration: CodegenConfiguration = CodegenConfiguration() -) : Appendable by sb { - fun getResult(): String { - return sb.toString() - } -} - -class CodegenConfiguration( - var removeDefaultValues: Boolean = true, -) - - -private fun String.escapeQuotation(): String = buildString { this@escapeQuotation.escapeQuotationTo(this) } - -private fun String.escapeQuotationTo(out: StringBuilder) { - for (i in 0 until length) { - when (val ch = this[i]) { - '\\' -> out.append("\\\\") - '\n' -> out.append("\\n") - '\r' -> out.append("\\r") - '\t' -> out.append("\\t") - '\"' -> out.append("\\\"") - else -> out.append(ch) - } - } -} - diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt deleted file mode 100644 index 767cf692647..00000000000 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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.internal.utils.codegen - -import kotlin.reflect.KClass -import kotlin.reflect.KParameter -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.typeOf - -sealed interface ValueDesc { - val origin: Any? - val parent: ValueDesc? - - fun accept(visitor: ValueDescVisitor) -} - -val ValueDesc.parents - get() = sequence { - var parent = parent - do { - parent ?: return@sequence - yield(parent) - parent = parent.parent - } while (true); - } - -inline fun ValueDesc.findParent(): T? = parents.filterIsInstance().firstOrNull() - -sealed interface ArrayValueDesc : ValueDesc { - val value: Any - - val arrayType: KType - val elementType: KType - val elements: MutableList - - companion object { - @OptIn(ExperimentalStdlibApi::class) - fun createOrNull(array: Any, type: KType, parent: ValueDesc?): ArrayValueDesc? { - if (array is Array<*>) return ObjectArrayValueDesc(parent, array, arrayType = type) - return when (array) { - is IntArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) - is ByteArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) - is ShortArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) - is CharArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) - is LongArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) - is FloatArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf()) - is DoubleArray -> PrimitiveArrayValueDesc( - parent, - array, - arrayType = type, - elementType = typeOf() - ) - is BooleanArray -> PrimitiveArrayValueDesc( - parent, - array, - arrayType = type, - elementType = typeOf() - ) - else -> return null - } - } - } -} - -class ObjectArrayValueDesc( - override val parent: ValueDesc?, - override var value: Array<*>, - override val origin: Array<*> = value, - override val arrayType: KType, - override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType(), -) : ArrayValueDesc { - override val elements: MutableList by lazy { - value.mapTo(mutableListOf()) { - ConstructorCallCodegenFacade.analyze(it, elementType) - } - } - - override fun accept(visitor: ValueDescVisitor) { - visitor.visitObjectArray(this) - } -} - -class CollectionValueDesc( - override val parent: ValueDesc?, - override var value: Collection<*>, - override val origin: Collection<*> = value, - override val arrayType: KType, - override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType() -) : ArrayValueDesc { - override val elements: MutableList by lazy { - value.mapTo(mutableListOf()) { - ConstructorCallCodegenFacade.analyze(it, elementType) - } - } - - override fun accept(visitor: ValueDescVisitor) { - visitor.visitCollection(this) - } -} - -class MapValueDesc( - override val parent: ValueDesc?, - var value: Map, - override val origin: Map = value, - val mapType: KType, - val keyType: KType = mapType.arguments.first().type ?: Any::class.createType(), - val valueType: KType = mapType.arguments[1].type ?: Any::class.createType(), -) : ValueDesc { - val elements: MutableMap by lazy { - value.map { - ConstructorCallCodegenFacade.analyze(it.key, keyType) to ConstructorCallCodegenFacade.analyze( - it.value, - valueType - ) - }.toMap(mutableMapOf()) - } - - override fun accept(visitor: ValueDescVisitor) { - visitor.visitMap(this) - } -} - -class PrimitiveArrayValueDesc( - override val parent: ValueDesc?, - override var value: Any, - override val origin: Any = value, - override val arrayType: KType, - override val elementType: KType -) : ArrayValueDesc { - override val elements: MutableList by lazy { - when (val value = value) { - is IntArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - is ByteArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - is ShortArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - is CharArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - is LongArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - is FloatArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - is DoubleArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - is BooleanArray -> value.mapTo(mutableListOf()) { ConstructorCallCodegenFacade.analyze(it, elementType) } - else -> error("$value is not an array.") - } - } - - override fun accept(visitor: ValueDescVisitor) { - visitor.visitPrimitiveArray(this) - } -} - -class PlainValueDesc( - override val parent: ValueDesc?, - var value: String, - override val origin: Any? -) : ValueDesc { - init { - require(value.isNotBlank()) - } - - override fun accept(visitor: ValueDescVisitor) { - visitor.visitPlain(this) - } -} - -class ClassValueDesc( - override val parent: ValueDesc?, - override val origin: T, - val properties: MutableMap, -) : ValueDesc { - val type: KClass by lazy { origin::class } - - override fun accept(visitor: ValueDescVisitor) { - visitor.visitClass(this) - } -} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/test/ConstructorCallCodegenTest.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/test/ConstructorCallCodegenTest.kt deleted file mode 100644 index fec8533cf54..00000000000 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/test/ConstructorCallCodegenTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.internal.utils.codegen.test - -import net.mamoe.mirai.internal.utils.codegen.ConstructorCallCodegenFacade -import net.mamoe.mirai.internal.utils.codegen.analyzeAndGenerate -import kotlin.test.Test -import kotlin.test.assertEquals - -class ConstructorCallCodegenTest { - - @Test - fun `test plain`() { - assertEquals( - "\"test\"", - ConstructorCallCodegenFacade.analyzeAndGenerate("test") - ) - assertEquals( - "1", - ConstructorCallCodegenFacade.analyzeAndGenerate(1) - ) - assertEquals( - "1.0", - ConstructorCallCodegenFacade.analyzeAndGenerate(1.0) - ) - } - - @Test - fun `test array`() { - assertEquals( - "arrayOf(1, 2)", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf(1, 2)) - ) - assertEquals( - "arrayOf(5.0)", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf(5.0)) - ) - assertEquals( - "arrayOf(\"1\")", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf("1")) - ) - assertEquals( - "arrayOf(arrayOf(1))", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf(arrayOf(1))) - ) - } - - data class TestClass( - val value: String - ) - - data class TestClass2( - val value: Any - ) - - @Test - fun `test class`() { - assertEquals( - """ - ${TestClass::class.qualifiedName!!}( - value="test", - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestClass("test")) - ) - assertEquals( - """ - ${TestClass2::class.qualifiedName!!}( - value="test", - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestClass2("test")) - ) - assertEquals( - """ - ${TestClass2::class.qualifiedName!!}( - value=1, - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestClass2(1)) - ) - } - - data class TestNesting( - val nested: Nested - ) { - data class Nested( - val value: String - ) - } - - @Test - fun `test nesting`() { - assertEquals( - """ - net.mamoe.mirai.internal.utils.codegen.test.ConstructorCallCodegenTest.TestNesting( - nested=net.mamoe.mirai.internal.utils.codegen.test.ConstructorCallCodegenTest.TestNesting.Nested( - value="test", - ), - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestNesting(TestNesting.Nested("test"))) - ) - } -} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt b/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt index abd154fca75..ebffce21325 100644 --- a/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt +++ b/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt @@ -14,8 +14,8 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline -import net.mamoe.mirai.internal.notice.Desensitizer -import net.mamoe.mirai.internal.notice.RecordingNoticeProcessor +import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer +import net.mamoe.mirai.internal.testFramework.notice.RecordingNoticeProcessor import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.readResource import net.mamoe.yamlkt.Yaml diff --git a/mirai-core/src/jvmTest/kotlin/message/data/ForwardRefineTest.kt b/mirai-core/src/jvmTest/kotlin/message/data/ForwardRefineTest.kt index a1679b0e4c9..5d0485986a6 100644 --- a/mirai-core/src/jvmTest/kotlin/message/data/ForwardRefineTest.kt +++ b/mirai-core/src/jvmTest/kotlin/message/data/ForwardRefineTest.kt @@ -16,7 +16,7 @@ import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.ForwardMessageInternal import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.test.runBlockingUnit -import net.mamoe.mirai.internal.utils._miraiContentToString +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.message.data.* import org.junit.jupiter.api.Test import kotlin.test.assertEquals @@ -49,7 +49,7 @@ internal class ForwardRefineTest : AbstractTestWithMiraiImpl() { }) println(refine.size) println(refine.first()::class) - println(refine._miraiContentToString()) + println(refine.structureToString()) assertTrue { refine.first() is MessageOrigin } assertTrue { refine.drop(1).first() is ForwardMessage } assertEquals( diff --git a/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt b/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt index abcf30fe62f..d164bfd93b6 100644 --- a/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt +++ b/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt @@ -20,7 +20,7 @@ import net.mamoe.mirai.internal.message.MarketFaceImpl import net.mamoe.mirai.internal.message.OnlineAudioImpl import net.mamoe.mirai.internal.message.UnsupportedMessageImpl import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody -import net.mamoe.mirai.internal.utils._miraiContentToString +import net.mamoe.mirai.internal.utils.structureToString import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.cast @@ -201,7 +201,7 @@ internal class MessageSerializationTest { ignoreUnknownKeys = true } val source = j.decodeFromString(MessageSource.Serializer, a) - println(source._miraiContentToString()) + println(source.structureToString()) assertEquals( expected = Mirai.buildMessageSource(692928873, MessageSourceKind.GROUP) { id(44) diff --git a/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerLegacy.kt b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerLegacy.kt new file mode 100644 index 00000000000..e5c289e6ab3 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerLegacy.kt @@ -0,0 +1,208 @@ +/* + * 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.internal.utils + +import kotlinx.serialization.Transient +import net.mamoe.mirai.utils.toUHexString +import java.lang.reflect.Modifier +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.jvm.javaField + +internal class StructureToStringTransformerLegacy : StructureToStringTransformer { + override fun transform(any: Any?): String = any._miraiContentToString() + + private val indent: String = " ".repeat(4) + + /** + * 将所有元素加入转换为多行的字符串表示. + */ + private fun Sequence.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { + return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform) + } + + /** + * 将内容格式化为较可读的字符串输出. + * + * 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. e.g. `0x1000(4096)` + * [ByteArray] 和 [UByteArray]: 十六进制表示, 通过 [ByteArray.toUHexString] + * [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString. + * [Map]: 多行输出. 每行显示一个值. 递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 + * `data class`: 调用其 [toString] + * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 + */ + @Suppress("FunctionName") // 这样就不容易被 IDE 提示 + internal fun Any?._miraiContentToString(prefix: String = ""): String = when (this) { + is Unit -> "Unit" + is UInt -> "0x" + this.toUHexString("") + "($this)" + is UByte -> "0x" + this.toUHexString() + "($this)" + is UShort -> "0x" + this.toUHexString("") + "($this)" + is ULong -> "0x" + this.toUHexString("") + "($this)" + is Int -> "0x" + this.toUHexString("") + "($this)" + is Byte -> "0x" + this.toUHexString() + "($this)" + is Short -> "0x" + this.toUHexString("") + "($this)" + is Long -> "0x" + this.toUHexString("") + "($this)" + + is Boolean -> if (this) "true" else "false" + + is ByteArray -> { + if (this.size == 0) "" + else this.toUHexString() + } + is UByteArray -> { + if (this.size == 0) "" + else this.toUHexString() + } + is ShortArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is IntArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is LongArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is FloatArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is DoubleArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is UShortArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is UIntArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is ULongArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is Array<*> -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + is BooleanArray -> { + if (this.size == 0) "" + else this.iterator()._miraiContentToString() + } + + is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } + is Iterator<*> -> this.asSequence() + .joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } + is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } + is Map<*, *> -> this.entries.joinToString( + prefix = "{", + postfix = "}" + ) { it.key._miraiContentToString(prefix) + "=" + it.value._miraiContentToString(prefix) } + else -> { + if (this == null) "null" + else if (this::class.isData) this.toString() + else { + if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) { + this.contentToStringReflectively(prefix + indent) + } else this.toString() + /* + (this::class.simpleName ?: "") + "#" + this::class.hashCode() + "{\n" + + this::class.members.asSequence().filterIsInstance>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC } + .joinToStringPrefixed( + prefix = indent + ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(indent) }.getOrElse { "" } } + */ + } + } + } + + internal fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? { + return this.javaField?.apply { isAccessible = true }?.get(receiver) + } + + private fun Any.canBeIgnored(): Boolean { + return when (this) { + is String -> this.isEmpty() + is ByteArray -> this.isEmpty() + is Array<*> -> this.isEmpty() + is Int -> this == 0 + is Float -> this == 0f + is Double -> this == 0.0 + is Byte -> this == 0.toByte() + is Short -> this == 0.toShort() + is Long -> this == 0.toLong() + else -> false + } + } + + private fun Any.contentToStringReflectively( + prefix: String, + filter: ((name: String, value: Any?) -> Boolean)? = null, + ): String { + val newPrefix = "$prefix " + return (this::class.simpleName ?: "") + "#" + this::class.hashCode() + " {\n" + + this.allMembersFromSuperClassesMatching { it.qualifiedName?.startsWith("net.mamoe.mirai") == true } + .distinctBy { it.name } + .filterNot { it.name.contains("$") || it.name == "Companion" || it.isConst || it.name == "serialVersionUID" } + .mapNotNull { + val value = it.getValueAgainstPermission(this) ?: return@mapNotNull null + if (filter != null) { + if (!filter(it.name, value)) + return@mapNotNull it.name to value + else { + return@mapNotNull null + } + } + it.name to value + } + .joinToStringPrefixed( + prefix = newPrefix + ) { (name: String, value: Any?) -> + if (value.canBeIgnored()) "" + else { + "$name=" + kotlin.runCatching { + if (value == this) "" + else value._miraiContentToString(newPrefix) + }.getOrElse { "" } + } + }.lines().filterNot { it.isBlank() }.joinToString("\n") + "\n$prefix}" + } + + private fun KClass.thisClassAndSuperclassSequence(): Sequence> { + return sequenceOf(this) + + this.supertypes.asSequence() + .mapNotNull { type -> + type.classifier?.takeIf { it is KClass<*> }?.takeIf { it != Any::class } as? KClass + }.flatMap { it.thisClassAndSuperclassSequence() } + } + + @Suppress("UNCHECKED_CAST") + private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass) -> Boolean): Sequence> { + return this::class.thisClassAndSuperclassSequence() + .filter { classFilter(it) } + .map { it.members } + .flatMap { it.asSequence() } + .filterIsInstance>() + .filterNot { it.hasAnnotation() } + .filterNot { it.isTransient() } + .map { it as KProperty1 } + } + + internal fun KProperty<*>.isTransient(): Boolean = + javaField?.modifiers?.and(Modifier.TRANSIENT) != 0 + + +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerNew.kt b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerNew.kt new file mode 100644 index 00000000000..744b86ad491 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerNew.kt @@ -0,0 +1,29 @@ +/* + * 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.internal.utils + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.transform +import net.mamoe.mirai.internal.testFramework.codegen.removeDefaultValues +import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString + +internal class StructureToStringTransformerNew : StructureToStringTransformer { + private val legacy = StructureToStringTransformerLegacy() + + override fun transform(any: Any?): String = + kotlin.runCatching { + ValueDescAnalyzer.analyze(any) + .transform(OptimizeByteArrayAsHexStringTransformer()) + ?.removeDefaultValues() + ?.renderToString() + }.getOrNull() ?: legacy.transform(any) +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt b/mirai-core/src/jvmTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt new file mode 100644 index 00000000000..1b0f37dcf94 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt @@ -0,0 +1,34 @@ +/* + * 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.internal.utils.test + +import net.mamoe.mirai.internal.test.AbstractTest +import net.mamoe.mirai.internal.utils.structureToString +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class StructureToStringTransformerNewTest : AbstractTest() { + + data class MyClass( + val value: String + ) + + @Test + fun `can load service`() { + assertEquals( + """ + ${MyClass::class.qualifiedName}( + value = "1", + ) + """.trimIndent(), + MyClass("1").structureToString() + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/resources/META-INF/services/net.mamoe.mirai.internal.utils.StructureToStringTransformer b/mirai-core/src/jvmTest/resources/META-INF/services/net.mamoe.mirai.internal.utils.StructureToStringTransformer new file mode 100644 index 00000000000..1b0ca247990 --- /dev/null +++ b/mirai-core/src/jvmTest/resources/META-INF/services/net.mamoe.mirai.internal.utils.StructureToStringTransformer @@ -0,0 +1,10 @@ +# +# 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 +# + +net.mamoe.mirai.internal.utils.StructureToStringTransformerNew \ No newline at end of file