jvmBaseMain.StructureToStringTransformerLegacy.kt Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2019-2023 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.utils
import kotlinx.serialization.Transient
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
public class StructureToStringTransformerLegacy : StructureToStringTransformer {
override fun transform(any: Any?): String = any._miraiContentToString()
override fun transformAndDesensitize(any: Any?): String = transform(any) // desensitization not supported
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]: 十六进制表示, 通过 [ByteArray.toUHexString]
* [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString.
* [Map]: 多行输出. 每行显示一个值. 递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示
* `data class`: 调用其 [toString]
* 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示
*/
@Suppress("FunctionName") // 这样就不容易被 IDE 提示
private 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 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 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 { "" } }
*/
}
}
}
private 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 }
}
private fun KProperty<*>.isTransient(): Boolean =
javaField?.modifiers?.and(Modifier.TRANSIENT) != 0
}