Rewrite and generalize ConstructorCallCodegen for multipurpose usage.

This commit is contained in:
Him188 2021-12-20 19:07:50 +00:00
parent bf98ab7858
commit 3a2663104b
37 changed files with 1641 additions and 628 deletions

View File

@ -23,7 +23,7 @@ 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.crypto.TEA
import net.mamoe.mirai.internal.utils.printStructurally
import net.mamoe.mirai.internal.utils.printStructure
import net.mamoe.mirai.internal.utils.structureToString
import net.mamoe.mirai.utils.*
@ -163,7 +163,7 @@ internal class WtLogin {
val tlvMap: TlvMap = this._readTLVMap()
if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) {
tlvMap.smartToString().printStructurally("tlvMap outer")
tlvMap.smartToString().printStructure("tlvMap outer")
}
// tlvMap.printTLVMap()
@ -266,7 +266,7 @@ internal class WtLogin {
val tlvMap119 = this._readTLVMap()
if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) {
tlvMap119.smartToString().printStructurally("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.structureToString().printStructurally("tokenChangeTime")
changeTokenTimeMap.structureToString().printStructure("tokenChangeTime")
}
val outPSKeyMap: PSKeyMap?

View File

@ -41,9 +41,9 @@ private val SoutvLogger: MiraiLogger by lazy {
level = DeprecationLevel.ERROR
)
@DeprecatedSinceMirai(errorSince = "2.10")
internal fun Any?.soutv(name: String = "unnamed") = this.printStructurally(name)
internal fun Any?.soutv(name: String = "unnamed") = this.printStructure(name)
internal fun Any?.printStructurally(name: String = "unnamed") {
internal fun Any?.printStructure(name: String = "unnamed") {
return SoutvLogger.debug { "$name = ${this.structureToString()}" }
}

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.printStructurally
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 <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.printStructurally("OIDB")
oidb.printStructure("OIDB")
}
return oidb.bodybuffer.loadAs(deserializer)
}

View File

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

View File

@ -7,107 +7,33 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.utils.codegen
package net.mamoe.mirai.internal.testFramework.codegen
import net.mamoe.mirai.utils.cast
import kotlin.reflect.KClass
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 <T : Any> visitClass(desc: ClassValueDesc<T>) {
visitValue(desc)
desc.properties.forEach { (_, u) ->
u.accept(this)
}
}
}
class DefaultValuesMapping(
val forClass: KClass<*>,
val mapping: MutableMap<String, Any?> = mutableMapOf()
) {
operator fun get(property: KProperty<*>): Any? = mapping[property.name]
}
class AnalyzeDefaultValuesMappingVisitor : ValueDescVisitor {
val mappings: MutableList<DefaultValuesMapping> = mutableListOf()
override fun <T : Any> visitClass(desc: ClassValueDesc<T>) {
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 <T : Any> findCorrespondingProperty(
desc: ClassValueDesc<T>,
param: KParameter
) = desc.type.memberProperties.single { it.name == param.name }.cast<KProperty1<Any, Any>>()
private fun <T : Any> createInstanceWithMostDefaultValues(clazz: KClass<T>, arguments: Map<KParameter, Any?>): 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<DefaultValuesMapping>,
) : ValueDescVisitor {
override fun <T : Any> visitClass(desc: ClassValueDesc<T>) {
super.visitClass(desc)
) : ValueDescVisitorUnit {
override fun visitValue(desc: ValueDesc, data: Nothing?) {
desc.acceptChildren(this, data)
}
override fun <T : Any> visitClass(desc: ClassValueDesc<T>, 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
}
}
}
}

View File

@ -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<Any>()
/**
* 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 <reified T> ConstructorCallCodegenFacade.analyze(value: T): ValueDesc {
inline fun <reified T> ValueDescAnalyzer.analyze(value: T): ValueDesc {
return analyze(value, typeOf<T>())
}
@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> ConstructorCallCodegenFacade.analyzeAndGenerate(
value: T,
context: CodegenContext = CodegenContext()
): String {
return analyzeAndGenerate(value, typeOf<T>(), context)
}
}

View File

@ -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<T : Any>(
override val parent: ValueDesc?,
override val origin: T,
val properties: MutableMap<KParameter, ValueDesc>,
) : ValueDesc {
val type: KClass<out T> by lazy { origin::class }
override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R {
return visitor.visitClass(this, data)
}
override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) {
properties.forEach { (_, u) ->
u.accept(visitor, data)
}
}
override fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) {
val result = mutableMapOf<KParameter, ValueDesc>()
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)
}
}

View File

@ -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<ValueDesc>
override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) {
for (element in elements) {
element.accept(visitor, data)
}
}
override fun <D> transformChildren(visitor: ValueDescTransformer<D>, 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<Int>())
is ByteArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Byte>())
is ShortArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Short>())
is CharArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Char>())
is LongArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Long>())
is FloatArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Float>())
is DoubleArray -> PrimitiveArrayValueDesc(
parent,
array,
arrayType = type,
elementType = typeOf<Double>()
)
is BooleanArray -> PrimitiveArrayValueDesc(
parent,
array,
arrayType = type,
elementType = typeOf<Boolean>()
)
else -> return null
}
}
}
}
fun <E> MutableList<E>.transform(transformer: (E) -> E?) {
val result = this.asSequence().mapNotNull(transformer).toMutableList()
this.clear()
this.addAll(result)
}

View File

@ -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<ValueDesc> by lazy {
initializeElements(value)
}
private fun initializeElements(value: Collection<*>) = value.mapTo(ArrayList(value.size)) {
ValueDescAnalyzer.analyze(it, elementType)
}
override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R = visitor.visitCollection(this, data)
}

View File

@ -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<Any?, Any?>,
override val origin: Map<Any?, Any?> = 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<ValueDesc, ValueDesc> by lazy {
value.map {
ValueDescAnalyzer.analyze(it.key, keyType) to ValueDescAnalyzer.analyze(
it.value,
valueType
)
}.toMap(mutableMapOf())
}
override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R = visitor.visitMap(this, data)
override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) {
for ((key, value) in elements.entries) {
key.accept(visitor, data)
value.accept(visitor, data)
}
}
override fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) {
val resultMap = mutableMapOf<ValueDesc, ValueDesc>()
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)
}
}

View File

@ -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<ValueDesc> by lazy {
initializeElements(value)
}
private fun initializeElements(value: Array<*>) = value.mapTo(ArrayList(value.size)) {
ValueDescAnalyzer.analyze(it, elementType)
}
override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R = visitor.visitObjectArray(this, data)
}

View File

@ -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 <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R {
return visitor.visitPlain(this, data)
}
override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) {
}
override fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) {
}
}

View File

@ -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<ValueDesc> by lazy {
initializeElements(value)
}
private fun initializeElements(value: Any): MutableList<ValueDesc> = 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 <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R {
return visitor.visitPrimitiveArray(this, data)
}
}

View File

@ -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 <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R
fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D)
fun <D> transform(visitor: ValueDescTransformer<D>, data: D): ValueDesc? = this.accept(visitor, data)
fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D)
}
fun <R> ValueDesc.accept(visitor: ValueDescVisitor<Nothing?, R>): R = accept(visitor, null)
fun ValueDesc.transform(visitor: ValueDescTransformer<Nothing?>) = transform(visitor, null)
fun ValueDesc.transform(visitor: ValueDescTransformerNotNull<Nothing?>) = transform(visitor, null)!!
fun <R> ValueDesc.acceptChildren(visitor: ValueDescVisitor<Nothing?, R>) = acceptChildren(visitor, null)
fun ValueDesc.transformChildren(visitor: ValueDescTransformer<Nothing?>) = transformChildren(visitor, null)
val ValueDesc.parents
get() = sequence {
var parent = parent
do {
parent ?: return@sequence
yield(parent)
parent = parent.parent
} while (true)
}
inline fun <reified T : ValueDesc> ValueDesc.findParent(): T? = parents.filterIsInstance<T>().firstOrNull()

View File

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

View File

@ -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 <reified T> 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))
)
}
}

View File

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

View File

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

View File

@ -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<D> : ValueDescVisitor<D, ValueDesc?> {
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 <T : Any> visitClass(desc: ClassValueDesc<T>, data: D): ValueDesc? {
val resultMap = mutableMapOf<KParameter, ValueDesc>()
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<D> : ValueDescTransformer<D>() {
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 <T : Any> visitClass(desc: ClassValueDesc<T>, 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()
}
}

View File

@ -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<D, R> {
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 <T : Any> visitClass(desc: ClassValueDesc<T>, data: D): R {
return visitValue(desc, data)
}
}

View File

@ -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<Nothing?, Unit> {
override fun visitValue(desc: ValueDesc, data: Nothing?) {
}
}

View File

@ -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<String, Any?> = mutableMapOf()
) {
operator fun get(property: KProperty<*>): Any? = mapping[property.name]
}
class AnalyzeDefaultValuesMappingVisitor : ValueDescVisitorUnit {
val mappings: MutableList<DefaultValuesMapping> = mutableListOf()
override fun visitValue(desc: ValueDesc, data: Nothing?) {
desc.acceptChildren(this, data)
}
override fun <T : Any> visitClass(desc: ClassValueDesc<T>, 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 <T : Any> findCorrespondingProperty(
desc: ClassValueDesc<T>,
param: KParameter
) = desc.type.memberProperties.single { it.name == param.name }.cast<KProperty1<Any, Any>>()
private fun <T : Any> createInstanceWithMostDefaultValues(clazz: KClass<T>, arguments: Map<KParameter, Any?>): T {
val primaryConstructor = clazz.primaryConstructor ?: error("Type $clazz does not have primary constructor.")
return primaryConstructor.callBy(arguments.filter { !it.key.isOptional })
}
}

View File

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

View File

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

View File

@ -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<Nothing?>() {
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<Array<Byte>>()
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)
}
}
}
}
}

View File

@ -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<RendererContext, String> {
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<ValueDesc>) {
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 <T : Any> visitClass(desc: ClassValueDesc<T>, 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
}
}

View File

@ -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 <reified T> ConstructorCallCodegenFacade.generateAndDesensitize(
inline fun <reified T> ValueDescAnalyzer.generateAndDesensitize(
value: T,
desensitizer: Desensitizer = instance,
): String = generateAndDesensitize(value, typeOf<T>(), 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<Nothing?>() {
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
override fun visitPlain(desc: PlainValueDesc, data: Nothing?): ValueDesc {
return PlainValueDesc(desc.parent, desensitizer.desensitize(desc.value), desc.origin)
}
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")
desc.value = desensitizer.desensitize(desc.value as Array<Byte>)
ObjectArrayValueDesc(
desc.parent,
desensitizer.desensitize(desc.value as Array<Byte>),
desc.origin,
desc.arrayType,
desc.elementType
)
} else {
for (element in desc.elements) {
element.accept(this)
super.visitObjectArray(desc, data)
}
}
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 <T : Any> visitClass(desc: ClassValueDesc<T>, data: Nothing?): ValueDesc {
ClassValueDesc(desc.parent, desc.origin, desc.properties.toMutableMap().apply {
replaceAll() { key, value ->
val annotation = key.findAnnotation<NestedStructure>()
if (annotation != null && value.origin is ByteArray) {
val instance = annotation.serializer.objectInstance ?: annotation.serializer.createInstance()
val result = instance.cast<NestedStructureDesensitizer<ProtocolStruct, ProtocolStruct>>()
.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
}
}
}
override fun visitCollection(desc: CollectionValueDesc) {
for (element in desc.elements) {
element.accept(this)
}
}
override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc) {
if (desc.value is ByteArray) {
desc.value = desensitizer.desensitize(desc.value as ByteArray)
}
}
override fun <T : Any> visitClass(desc: ClassValueDesc<T>) {
super.visitClass(desc)
desc.properties.replaceAll() { key, value ->
val annotation = key.findAnnotation<NestedStructure>()
if (annotation != null && value.origin is ByteArray) {
val instance = annotation.serializer.objectInstance ?: annotation.serializer.createInstance()
val result = instance.cast<NestedStructureDesensitizer<ProtocolStruct, ProtocolStruct>>()
.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
}).let {
return super.visitClass(it, data)
}
}
}

View File

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

View File

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

View File

@ -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 <T : Any> generate(desc: ClassValueDesc<T>) {
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<ValueDesc>) {
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)
}
}
}

View File

@ -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 <reified T : ValueDesc> ValueDesc.findParent(): T? = parents.filterIsInstance<T>().firstOrNull()
sealed interface ArrayValueDesc : ValueDesc {
val value: Any
val arrayType: KType
val elementType: KType
val elements: MutableList<ValueDesc>
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<Int>())
is ByteArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Byte>())
is ShortArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Short>())
is CharArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Char>())
is LongArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Long>())
is FloatArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Float>())
is DoubleArray -> PrimitiveArrayValueDesc(
parent,
array,
arrayType = type,
elementType = typeOf<Double>()
)
is BooleanArray -> PrimitiveArrayValueDesc(
parent,
array,
arrayType = type,
elementType = typeOf<Boolean>()
)
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<ValueDesc> 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<ValueDesc> 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<Any?, Any?>,
override val origin: Map<Any?, Any?> = 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<ValueDesc, ValueDesc> 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<ValueDesc> 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<T : Any>(
override val parent: ValueDesc?,
override val origin: T,
val properties: MutableMap<KParameter, ValueDesc>,
) : ValueDesc {
val type: KClass<out T> by lazy { origin::class }
override fun accept(visitor: ValueDescVisitor) {
visitor.visitClass(this)
}
}

View File

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

View File

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

View File

@ -10,9 +10,6 @@
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
@ -21,8 +18,7 @@ import kotlin.reflect.KProperty1
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.jvm.javaField
// Kept for souvenir. Not used.
internal class StructureToStringLegacy : StructureToStringTransformer {
internal class StructureToStringTransformerLegacy : StructureToStringTransformer {
override fun transform(any: Any?): String = any._miraiContentToString()
private val indent: String = " ".repeat(4)

View File

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

View File

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

View File

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