Move _contentToString outside mirai-core main sourceSets, and rename it to structureToString

This commit is contained in:
Him188 2021-12-18 23:41:58 +00:00
parent 575225874c
commit bf98ab7858
16 changed files with 289 additions and 229 deletions

View File

@ -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 = "并附加此时正在上传的文件"
)
}

View File

@ -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 是否正在从好友或群接受消息, 尽量附加该图片原文件"
)
)

View File

@ -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())

View File

@ -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}",
)
}

View File

@ -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()}"
}
}
}

View File

@ -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,
"并描述此时机器人是否被踢出群等",
)

View File

@ -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()}" }
}
}

View File

@ -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.",
),
)

View File

@ -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}"
)
}

View File

@ -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}"
)
}

View File

@ -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.printStructurally
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().printStructurally("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().printStructurally("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().printStructurally("tokenChangeTime")
}
val outPSKeyMap: PSKeyMap?

View File

@ -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 <T> Sequence<T>.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(
StructureToStringTransformer::class,
"printStructurally"
)
}
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()}" }
@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.printStructurally(name)
internal fun Any?.printStructurally(name: String = "unnamed") {
return SoutvLogger.debug { "$name = ${this.structureToString()}" }
}
/**
* 将内容格式化为较可读的字符串输出.
*
* 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. 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)"
internal fun interface StructureToStringTransformer {
fun transform(any: Any?): String
is Boolean -> if (this) "true" else "false"
companion object {
private class ObjectToStringStructureToStringTransformer : StructureToStringTransformer {
override fun transform(any: Any?): String = any.toString()
}
is ByteArray -> {
if (this.size == 0) "<Empty ByteArray>"
else this.toUHexString()
}
is UByteArray -> {
if (this.size == 0) "<Empty UByteArray>"
else this.toUHexString()
}
is ShortArray -> {
if (this.size == 0) "<Empty ShortArray>"
else this.iterator()._miraiContentToString()
}
is IntArray -> {
if (this.size == 0) "<Empty IntArray>"
else this.iterator()._miraiContentToString()
}
is LongArray -> {
if (this.size == 0) "<Empty LongArray>"
else this.iterator()._miraiContentToString()
}
is FloatArray -> {
if (this.size == 0) "<Empty FloatArray>"
else this.iterator()._miraiContentToString()
}
is DoubleArray -> {
if (this.size == 0) "<Empty DoubleArray>"
else this.iterator()._miraiContentToString()
}
is UShortArray -> {
if (this.size == 0) "<Empty ShortArray>"
else this.iterator()._miraiContentToString()
}
is UIntArray -> {
if (this.size == 0) "<Empty IntArray>"
else this.iterator()._miraiContentToString()
}
is ULongArray -> {
if (this.size == 0) "<Empty LongArray>"
else this.iterator()._miraiContentToString()
}
is Array<*> -> {
if (this.size == 0) "<Empty Array>"
else this.iterator()._miraiContentToString()
}
is BooleanArray -> {
if (this.size == 0) "<Empty BooleanArray>"
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 ?: "<UnnamedClass>") + "#" + this::class.hashCode() + "{\n" +
this::class.members.asSequence().filterIsInstance<KProperty<*>>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC }
.joinToStringPrefixed(
prefix = indent
) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(indent) }.getOrElse { "<!>" } }
*/
val instance by lazy {
loadService(StructureToStringTransformer::class) { ObjectToStringStructureToStringTransformer() }
}
}
}
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
}
}
private fun Any.contentToStringReflectively(
prefix: String,
filter: ((name: String, value: Any?) -> Boolean)? = null,
): String {
val newPrefix = "$prefix "
return (this::class.simpleName ?: "<UnnamedClass>") + "#" + 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) "<this>"
else value._miraiContentToString(newPrefix)
}.getOrElse { "<!>" }
}
}.lines().filterNot { it.isBlank() }.joinToString("\n") + "\n$prefix}"
}
private fun KClass<out Any>.thisClassAndSuperclassSequence(): Sequence<KClass<out Any>> {
return sequenceOf(this) +
this.supertypes.asSequence()
.mapNotNull { type ->
type.classifier?.takeIf { it is KClass<*> }?.takeIf { it != Any::class } as? KClass<out Any>
}.flatMap { it.thisClassAndSuperclassSequence() }
}
@Suppress("UNCHECKED_CAST")
private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any>) -> Boolean): Sequence<KProperty1<Any, *>> {
return this::class.thisClassAndSuperclassSequence()
.filter { classFilter(it) }
.map { it.members }
.flatMap { it.asSequence() }
.filterIsInstance<KProperty1<*, *>>()
.filterNot { it.hasAnnotation<Transient>() }
.filterNot { it.isTransient() }
.mapNotNull { it as KProperty1<Any, *> }
}
internal fun KProperty<*>.isTransient(): Boolean =
javaField?.modifiers?.and(Modifier.TRANSIENT) != 0

View File

@ -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.printStructurally
import net.mamoe.mirai.utils.read
import net.mamoe.mirai.utils.readPacketExact
import kotlin.contracts.InvocationKind
@ -171,7 +171,7 @@ internal fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrate
internal fun <T : ProtoBuf> ByteArray.loadOidb(deserializer: DeserializationStrategy<T>, log: Boolean = false): T {
val oidb = loadAs(OidbSso.OIDBSSOPkg.serializer())
if (log) {
oidb.soutv("OIDB")
oidb.printStructurally("OIDB")
}
return oidb.bodybuffer.loadAs(deserializer)
}

View File

@ -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(

View File

@ -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)

View File

@ -0,0 +1,212 @@
/*
* 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.IMirai
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
// Kept for souvenir. Not used.
internal class StructureToStringLegacy : StructureToStringTransformer {
override fun transform(any: Any?): String = any._miraiContentToString()
private val indent: String = " ".repeat(4)
/**
* 将所有元素加入转换为多行的字符串表示.
*/
private fun <T> Sequence<T>.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) "<Empty ByteArray>"
else this.toUHexString()
}
is UByteArray -> {
if (this.size == 0) "<Empty UByteArray>"
else this.toUHexString()
}
is ShortArray -> {
if (this.size == 0) "<Empty ShortArray>"
else this.iterator()._miraiContentToString()
}
is IntArray -> {
if (this.size == 0) "<Empty IntArray>"
else this.iterator()._miraiContentToString()
}
is LongArray -> {
if (this.size == 0) "<Empty LongArray>"
else this.iterator()._miraiContentToString()
}
is FloatArray -> {
if (this.size == 0) "<Empty FloatArray>"
else this.iterator()._miraiContentToString()
}
is DoubleArray -> {
if (this.size == 0) "<Empty DoubleArray>"
else this.iterator()._miraiContentToString()
}
is UShortArray -> {
if (this.size == 0) "<Empty ShortArray>"
else this.iterator()._miraiContentToString()
}
is UIntArray -> {
if (this.size == 0) "<Empty IntArray>"
else this.iterator()._miraiContentToString()
}
is ULongArray -> {
if (this.size == 0) "<Empty LongArray>"
else this.iterator()._miraiContentToString()
}
is Array<*> -> {
if (this.size == 0) "<Empty Array>"
else this.iterator()._miraiContentToString()
}
is BooleanArray -> {
if (this.size == 0) "<Empty BooleanArray>"
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 ?: "<UnnamedClass>") + "#" + this::class.hashCode() + "{\n" +
this::class.members.asSequence().filterIsInstance<KProperty<*>>().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 ?: "<UnnamedClass>") + "#" + 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) "<this>"
else value._miraiContentToString(newPrefix)
}.getOrElse { "<!>" }
}
}.lines().filterNot { it.isBlank() }.joinToString("\n") + "\n$prefix}"
}
private fun KClass<out Any>.thisClassAndSuperclassSequence(): Sequence<KClass<out Any>> {
return sequenceOf(this) +
this.supertypes.asSequence()
.mapNotNull { type ->
type.classifier?.takeIf { it is KClass<*> }?.takeIf { it != Any::class } as? KClass<out Any>
}.flatMap { it.thisClassAndSuperclassSequence() }
}
@Suppress("UNCHECKED_CAST")
private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any>) -> Boolean): Sequence<KProperty1<Any, *>> {
return this::class.thisClassAndSuperclassSequence()
.filter { classFilter(it) }
.map { it.members }
.flatMap { it.asSequence() }
.filterIsInstance<KProperty1<*, *>>()
.filterNot { it.hasAnnotation<Transient>() }
.filterNot { it.isTransient() }
.map { it as KProperty1<Any, *> }
}
internal fun KProperty<*>.isTransient(): Boolean =
javaField?.modifiers?.and(Modifier.TRANSIENT) != 0
}