From 3a2663104b3b18eff88c83f4ab8a9b7335830209 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Mon, 20 Dec 2021 19:07:50 +0000 Subject: [PATCH] Rewrite and generalize ConstructorCallCodegen for multipurpose usage. --- .../network/protocol/packet/login/WtLogin.kt | 8 +- .../kotlin/utils/contentToString.kt | 4 +- .../kotlin/utils/io/serialization/utils.kt | 4 +- .../test/RecordingNoticeProcessorTest.kt | 2 +- .../codegen/RemoveDefaultValuesVisitor.kt} | 114 ++------ .../codegen/ValueDescAnalyzer.kt} | 54 ++-- .../codegen/descriptors/ClassValueDesc.kt | 46 +++ .../descriptors/CollectionLikeValueDesc.kt | 67 +++++ .../descriptors/CollectionValueDesc.kt | 42 +++ .../codegen/descriptors/MapValueDesc.kt | 56 ++++ .../descriptors/ObjectArrayValueDesc.kt | 43 +++ .../codegen/descriptors/PlainValueDesc.kt | 33 +++ .../descriptors/PrimitiveArrayValueDesc.kt | 49 ++++ .../codegen/descriptors/ValueDesc.kt | 43 +++ .../codegen/test/IndenterTest.kt | 23 ++ ...mizeByteArrayAsHexStringTransformerTest.kt | 50 ++++ .../test/visitors/ValueDescAnalyzerTest.kt | 261 ++++++++++++++++++ .../visitors/ValueDescToStringRendererTest.kt | 198 +++++++++++++ .../codegen/visitor/ValueDescTransformer.kt | 100 +++++++ .../codegen/visitor/ValueDescVisitor.kt | 44 +++ .../codegen/visitor/ValueDescVisitorUnit.kt | 17 ++ .../AnalyzeDefaultValuesMappingVisitor.kt | 68 +++++ .../codegen/visitors/ClassFormatter.kt | 34 +++ .../codegen/visitors/Indenter.kt | 29 ++ ...OptimizeByteArrayAsHexStringTransformer.kt | 88 ++++++ .../visitors/ValueDescToStringRenderer.kt | 138 +++++++++ .../desensitizer}/Desensitizer.kt | 124 +++++---- .../notice/RecordingNoticeHandler.kt | 8 +- .../kotlin/testFramework/package.kt | 10 + .../kotlin/utils/codegen/ValueCodegen.kt | 136 --------- .../kotlin/utils/codegen/ValueDesc.kt | 181 ------------ .../test/ConstructorCallCodegenTest.kt | 112 -------- .../jvmTest/kotlin/bootstrap/RunRecorder.kt | 4 +- ... => StructureToStringTransformerLegacy.kt} | 6 +- .../utils/StructureToStringTransformerNew.kt | 29 ++ .../StructureToStringTransformerNewTest.kt | 34 +++ ...nternal.utils.StructureToStringTransformer | 10 + 37 files changed, 1641 insertions(+), 628 deletions(-) rename mirai-core/src/commonTest/kotlin/{utils/codegen/ValueDescVisitor.kt => testFramework/codegen/RemoveDefaultValuesVisitor.kt} (52%) rename mirai-core/src/commonTest/kotlin/{utils/codegen/ConstructorCallCodegenFacade.kt => testFramework/codegen/ValueDescAnalyzer.kt} (64%) create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/test/IndenterTest.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/Indenter.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt rename mirai-core/src/commonTest/kotlin/{notice => testFramework/desensitizer}/Desensitizer.kt (62%) rename mirai-core/src/commonTest/kotlin/{ => testFramework}/notice/RecordingNoticeHandler.kt (82%) create mode 100644 mirai-core/src/commonTest/kotlin/testFramework/package.kt delete mode 100644 mirai-core/src/commonTest/kotlin/utils/codegen/ValueCodegen.kt delete mode 100644 mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt delete mode 100644 mirai-core/src/commonTest/kotlin/utils/codegen/test/ConstructorCallCodegenTest.kt rename mirai-core/src/jvmTest/kotlin/utils/{StructureToStringLegacy.kt => StructureToStringTransformerLegacy.kt} (97%) create mode 100644 mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerNew.kt create mode 100644 mirai-core/src/jvmTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt create mode 100644 mirai-core/src/jvmTest/resources/META-INF/services/net.mamoe.mirai.internal.utils.StructureToStringTransformer diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt index 04d5d30df..1a3c6d8d5 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt @@ -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? diff --git a/mirai-core/src/commonMain/kotlin/utils/contentToString.kt b/mirai-core/src/commonMain/kotlin/utils/contentToString.kt index eb720d2e1..a70a13dbb 100644 --- a/mirai-core/src/commonMain/kotlin/utils/contentToString.kt +++ b/mirai-core/src/commonMain/kotlin/utils/contentToString.kt @@ -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()}" } } diff --git a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt index 10a25cafc..d25ff113d 100644 --- a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt +++ b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt @@ -24,7 +24,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars -import net.mamoe.mirai.internal.utils.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) } diff --git a/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt b/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt index 1fe94c5d3..d8ad24a96 100644 --- a/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt +++ b/mirai-core/src/commonTest/kotlin/notice/test/RecordingNoticeProcessorTest.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.internal.notice.test import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.protobuf.ProtoNumber -import net.mamoe.mirai.internal.notice.Desensitizer +import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.yamlkt.Yaml diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDescVisitor.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/RemoveDefaultValuesVisitor.kt similarity index 52% rename from mirai-core/src/commonTest/kotlin/utils/codegen/ValueDescVisitor.kt rename to mirai-core/src/commonTest/kotlin/testFramework/codegen/RemoveDefaultValuesVisitor.kt index 7c9a00832..60f2e93b3 100644 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDescVisitor.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/RemoveDefaultValuesVisitor.kt @@ -7,107 +7,33 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.utils.codegen +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 } } -} +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/ValueDescAnalyzer.kt similarity index 64% rename from mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt rename to mirai-core/src/commonTest/kotlin/testFramework/codegen/ValueDescAnalyzer.kt index 60bdbdd6d..2bea3b924 100644 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ConstructorCallCodegenFacade.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/ValueDescAnalyzer.kt @@ -7,9 +7,12 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.utils.codegen +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen import kotlinx.serialization.Serializable +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* import net.mamoe.mirai.utils.cast import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 @@ -20,7 +23,10 @@ import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.valueParameters import kotlin.reflect.typeOf -object ConstructorCallCodegenFacade { +object ValueDescAnalyzer { + private val anyType = typeOf<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) -} - +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt new file mode 100644 index 000000000..a5693b90a --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KClass +import kotlin.reflect.KParameter + +class ClassValueDesc<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) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt new file mode 100644 index 000000000..b0eb95a35 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.typeOf + +sealed interface CollectionLikeValueDesc : ValueDesc { + val value: Any + + val arrayType: KType + val elementType: KType + val elements: MutableList<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) +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt new file mode 100644 index 000000000..3baae13ad --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +class CollectionValueDesc( + override val parent: ValueDesc?, + value: Collection<*>, + override val origin: Collection<*> = value, + override val arrayType: KType, + override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType() +) : CollectionLikeValueDesc { + + override var value: Collection<*> = value + set(value) { + field = value + elements.clear() + elements.addAll(initializeElements(value)) + } + + override val elements: MutableList<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) +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt new file mode 100644 index 000000000..e928b808b --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +class MapValueDesc( + override val parent: ValueDesc?, + var value: Map<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) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt new file mode 100644 index 000000000..9b93a7b34 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType +import kotlin.reflect.full.createType + +class ObjectArrayValueDesc( + override val parent: ValueDesc?, + value: Array<*>, + override val origin: Array<*> = value, + override val arrayType: KType, + override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType(), +) : CollectionLikeValueDesc { + + override var value: Array<*> = value + set(value) { + field = value + elements.clear() + elements.addAll(initializeElements(value)) + } + + override val elements: MutableList<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) + +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt new file mode 100644 index 000000000..b9578d7a8 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor + +class PlainValueDesc( + override val parent: ValueDesc?, + var value: String, + override val origin: Any? +) : ValueDesc { + init { + require(value.isNotBlank()) + } + + override fun <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) { + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt new file mode 100644 index 000000000..90e3df809 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor +import kotlin.reflect.KType + +class PrimitiveArrayValueDesc( + override val parent: ValueDesc?, + value: Any, + override val origin: Any = value, + override val arrayType: KType, + override val elementType: KType +) : CollectionLikeValueDesc { + override var value: Any = value + set(value) { + field = value + elements.clear() + elements.addAll(initializeElements(value)) + } + + override val elements: MutableList<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) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt new file mode 100644 index 000000000..aa18185d3 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.descriptors + +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor + +sealed interface ValueDesc { + val origin: Any? + val parent: ValueDesc? + + fun <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() diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/IndenterTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/IndenterTest.kt new file mode 100644 index 000000000..824bbfb12 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/IndenterTest.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.test + +import net.mamoe.mirai.internal.test.AbstractTest +import net.mamoe.mirai.internal.testFramework.codegen.visitors.WordingIndenter +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class IndenterTest : AbstractTest() { + + @Test + fun `can indentize`() { + assertEquals(" test", WordingIndenter.spacing(4).indentize("test")) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt new file mode 100644 index 000000000..0e7c8f061 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.test + +import net.mamoe.mirai.internal.test.AbstractTest +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.transform +import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.ValueDescToStringRenderer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class OptimizeByteArrayAsHexStringTransformerTest : AbstractTest() { + + private inline fun <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)) + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt new file mode 100644 index 000000000..7b88fda1a --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt @@ -0,0 +1,261 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.test.visitors + +import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg +import net.mamoe.mirai.internal.testFramework.codegen.RemoveDefaultValuesVisitor +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.accept +import net.mamoe.mirai.internal.testFramework.codegen.visitors.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class ValueDescAnalyzerTest { + + private inline fun <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()) + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt new file mode 100644 index 000000000..e97a28feb --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt @@ -0,0 +1,198 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen.test.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.visitors.ValueDescToStringRenderer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class ValueDescToStringRendererTest { + private val renderer = ValueDescToStringRenderer() + + @Test + fun `plain value`() { + assertEquals("\"str\"", ValueDescAnalyzer.analyze("str").renderToString(renderer)) + assertEquals("1", ValueDescAnalyzer.analyze(1).renderToString(renderer)) + } + + @Test + fun `object array value`() { + assertEquals( + "arrayOf(\"str\", \"obj\")", + ValueDescAnalyzer.analyze(arrayOf("str", "obj")).renderToString(renderer) + ) + assertEquals("arrayOf(1, 2)", ValueDescAnalyzer.analyze(arrayOf(1, 2)).renderToString(renderer)) + } + + @Test + fun `object array value nested`() { + assertEquals( + """ + arrayOf( + arrayOf("str", "obj"), + arrayOf("str", "obj"), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(arrayOf(arrayOf("str", "obj"), arrayOf("str", "obj"))).renderToString(renderer) + ) + assertEquals( + """ + arrayOf( + arrayOf(1, 2), + arrayOf(1, 2), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(arrayOf(arrayOf(1, 2), arrayOf(1, 2))).renderToString(renderer) + ) + } + + @Test + fun `primitive array value`() { + assertEquals("intArrayOf(1, 2)", ValueDescAnalyzer.analyze(intArrayOf(1, 2)).renderToString(renderer)) + } + + @Test + fun `collection value`() { + assertEquals("mutableListOf(1, 2)", ValueDescAnalyzer.analyze(listOf(1, 2)).renderToString(renderer)) + assertEquals("mutableSetOf(1, 2)", ValueDescAnalyzer.analyze(setOf(1, 2)).renderToString(renderer)) + } + + @Test + fun `collection value nested`() { + assertEquals( + """ + mutableListOf( + mutableListOf(1, 2), + mutableListOf(1, 2), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(listOf(listOf(1, 2), listOf(1, 2))).renderToString(renderer) + ) + assertEquals( + """ + mutableSetOf( + mutableListOf(1, 2), + mutableListOf(2, 2), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(setOf(listOf(1, 2), listOf(2, 2))).renderToString(renderer) + ) + } + + @Test + fun `map value`() { + assertEquals( + """ + mutableMapOf( + 1 to 2, + 3 to 2, + ) + """.trimIndent(), ValueDescAnalyzer.analyze(mapOf(1 to 2, 3 to 2)).renderToString(renderer) + ) + } + + @Test + fun `map value nested`() { + assertEquals( + """ + |mutableMapOf( + | 1 to 2, + | 5 to mutableMapOf( + | 1 to 2, + | 3 to 2, + | ), + |) + """.trimMargin(), + ValueDescAnalyzer.analyze(mapOf(1 to 2, 5 to mapOf(1 to 2, 3 to 2))) + .renderToString(renderer) + ) + } + + data class MyClass( + val str: String, + val int: Int, + val self: MyClass?, + ) + + @Test + fun `class value`() { + data class MyClass2( + val str: String, + val int: Int, + val self: MyClass2?, + ) + + assertEquals( + """ + ${MyClass::class.qualifiedName}( + str = "str", + int = 1, + self = null, + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass("str", 1, null)).renderToString(renderer) + ) + + assertEquals( + """ + `${MyClass2::class.java.name}`( + str = "str", + int = 1, + self = null, + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass2("str", 1, null)).renderToString(renderer) + ) + } + + @Test + fun `class value nested`() { + data class MyClass2( + val str: String, + val int: Int, + val self: MyClass2?, + ) + + assertEquals( + """ + ${MyClass::class.qualifiedName}( + str = "str", + int = 1, + self = ${MyClass::class.qualifiedName}( + str = "str", + int = 1, + self = null, + ), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass("str", 1, MyClass("str", 1, null))).renderToString(renderer) + ) + + assertEquals( + """ + `${MyClass2::class.java.name}`( + str = "str", + int = 1, + self = `${MyClass2::class.java.name}`( + str = "str", + int = 1, + self = null, + ), + ) + """.trimIndent(), + ValueDescAnalyzer.analyze(MyClass2("str", 1, MyClass2("str", 1, null))).renderToString(renderer) + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt new file mode 100644 index 000000000..32d4707ed --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen.visitor + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import kotlin.reflect.KParameter + +abstract class ValueDescTransformer<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() + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt new file mode 100644 index 000000000..655eb8978 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.visitor + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* + +interface ValueDescVisitor<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) + } +} diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt new file mode 100644 index 000000000..b7831ce73 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.visitor + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc + +interface ValueDescVisitorUnit : ValueDescVisitor<Nothing?, Unit> { + override fun visitValue(desc: ValueDesc, data: Nothing?) { + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt new file mode 100644 index 000000000..8e9b6740a --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitorUnit +import net.mamoe.mirai.utils.cast +import kotlin.reflect.KClass +import kotlin.reflect.KParameter +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor + +class DefaultValuesMapping( + val forClass: KClass<*>, + val mapping: MutableMap<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 }) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt new file mode 100644 index 000000000..068258dfd --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc + +data class ClassFormatterContext( + val desc: ClassValueDesc<*>, + val visitor: ValueDescToStringRenderer, + val rendererContext: RendererContext, +) + +open class ClassFormatter { + open fun formatClassName(context: ClassFormatterContext): String { + val name = context.desc.type.qualifiedName ?: context.desc.type.java.name + return wrapBacktickIfNecessary(name) + } + + open fun formatClassProperty(context: ClassFormatterContext, propertyString: String?, valueString: String): String = + "$propertyString = $valueString" + + companion object { + protected fun wrapBacktickIfNecessary(name: String) = if (name.contains(' ') || name.contains('$')) { + "`$name`" + } else name + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/Indenter.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/Indenter.kt new file mode 100644 index 000000000..e99c8f963 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/Indenter.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.visitors + +fun interface Indenter { + fun indentize(string: String): String + + object NoIndent : Indenter { + override fun indentize(string: String): String = string + } +} + +class WordingIndenter constructor( + private val separator: String, +) : Indenter { + + override fun indentize(string: String): String = "$separator$string" + + companion object { + fun spacing(count: Int = 4): Indenter = WordingIndenter(" ".repeat(count)) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt new file mode 100644 index 000000000..e15243f40 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +@file:OptIn(ExperimentalStdlibApi::class) + +package net.mamoe.mirai.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull +import net.mamoe.mirai.utils.toUHexString +import kotlin.reflect.typeOf + +/** + * If the byte array was from [String.toByteArray], transform it to the [String]. + * Otherwise, transform it as hex string. + */ +open class OptimizeByteArrayAsHexStringTransformer : ValueDescTransformerNotNull<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) + } + } + } + + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt new file mode 100644 index 000000000..a8a9c128c --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework.codegen.visitors + +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor + +class RendererContext( + val indenter: Indenter, + val classFormatter: ClassFormatter = ClassFormatter() +) + +fun ValueDesc.renderToString( + renderer: ValueDescToStringRenderer = ValueDescToStringRenderer(), + rendererContext: RendererContext = RendererContext(WordingIndenter.spacing(4)) +): String { + return accept( + renderer, + rendererContext + ) +} + +open class ValueDescToStringRenderer : ValueDescVisitor<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 + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt b/mirai-core/src/commonTest/kotlin/testFramework/desensitizer/Desensitizer.kt similarity index 62% rename from mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt rename to mirai-core/src/commonTest/kotlin/testFramework/desensitizer/Desensitizer.kt index d06cc07d2..938768e40 100644 --- a/mirai-core/src/commonTest/kotlin/notice/Desensitizer.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/desensitizer/Desensitizer.kt @@ -7,12 +7,17 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.notice +package net.mamoe.mirai.internal.testFramework.desensitizer import kotlinx.serialization.decodeFromString import net.mamoe.mirai.Mirai -import net.mamoe.mirai.internal.notice.Desensitizer.Companion.generateAndDesensitize -import net.mamoe.mirai.internal.utils.codegen.* +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* +import net.mamoe.mirai.internal.testFramework.codegen.removeDefaultValues +import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull +import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.ProtocolStruct @@ -74,19 +79,20 @@ internal class Desensitizer private constructor( fun desensitize(string: String): String = instance.desensitize(string) - fun ConstructorCallCodegenFacade.generateAndDesensitize( + fun ValueDescAnalyzer.generateAndDesensitize( value: Any?, type: KType, desensitizer: Desensitizer = instance, ): String { - val a = analyze(value, type).apply { - accept(DesensitizationVisitor(desensitizer)) - } - return generate(a) + return analyze(value, type) + .transform(OptimizeByteArrayAsHexStringTransformer()) + .removeDefaultValues() + .transform(DesensitizationVisitor(desensitizer)) + .renderToString() } @OptIn(ExperimentalStdlibApi::class) - inline fun <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) } } } \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt b/mirai-core/src/commonTest/kotlin/testFramework/notice/RecordingNoticeHandler.kt similarity index 82% rename from mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt rename to mirai-core/src/commonTest/kotlin/testFramework/notice/RecordingNoticeHandler.kt index b3e529944..fde34f062 100644 --- a/mirai-core/src/commonTest/kotlin/notice/RecordingNoticeHandler.kt +++ b/mirai-core/src/commonTest/kotlin/testFramework/notice/RecordingNoticeHandler.kt @@ -7,15 +7,15 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ -package net.mamoe.mirai.internal.notice +package net.mamoe.mirai.internal.testFramework.notice import kotlinx.atomicfu.atomic import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor -import net.mamoe.mirai.internal.notice.Desensitizer.Companion.generateAndDesensitize -import net.mamoe.mirai.internal.utils.codegen.ConstructorCallCodegenFacade +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer.Companion.generateAndDesensitize import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info @@ -42,7 +42,7 @@ internal class RecordingNoticeProcessor : SimpleNoticeProcessor<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" } } } } diff --git a/mirai-core/src/commonTest/kotlin/testFramework/package.kt b/mirai-core/src/commonTest/kotlin/testFramework/package.kt new file mode 100644 index 000000000..9d69d364d --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/testFramework/package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.testFramework \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueCodegen.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/ValueCodegen.kt deleted file mode 100644 index 6954d9120..000000000 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueCodegen.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2019-2021 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/dev/LICENSE - */ - -package net.mamoe.mirai.internal.utils.codegen - -import net.mamoe.mirai.utils.toUHexString - -class ValueCodegen( - val context: CodegenContext -) { - fun generate(desc: ValueDesc) { - when (desc) { - is PlainValueDesc -> generate(desc) - is ObjectArrayValueDesc -> generate(desc) - is PrimitiveArrayValueDesc -> generate(desc) - is CollectionValueDesc -> generate(desc) - is ClassValueDesc<*> -> generate(desc) - is MapValueDesc -> generate(desc) - } - } - - fun generate(desc: PlainValueDesc) { - context.append(desc.value) - } - - fun generate(desc: MapValueDesc) { - context.run { - appendLine("mutableMapOf(") - for ((key, value) in desc.elements) { - generate(key) - append(" to ") - generate(value) - appendLine(",") - } - append(")") - } - } - - fun <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) - } - } -} - diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt deleted file mode 100644 index 767cf6926..000000000 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/ValueDesc.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2019-2021 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/dev/LICENSE - */ - -package net.mamoe.mirai.internal.utils.codegen - -import kotlin.reflect.KClass -import kotlin.reflect.KParameter -import kotlin.reflect.KType -import kotlin.reflect.full.createType -import kotlin.reflect.typeOf - -sealed interface ValueDesc { - val origin: Any? - val parent: ValueDesc? - - fun accept(visitor: ValueDescVisitor) -} - -val ValueDesc.parents - get() = sequence { - var parent = parent - do { - parent ?: return@sequence - yield(parent) - parent = parent.parent - } while (true); - } - -inline fun <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) - } -} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/utils/codegen/test/ConstructorCallCodegenTest.kt b/mirai-core/src/commonTest/kotlin/utils/codegen/test/ConstructorCallCodegenTest.kt deleted file mode 100644 index fec8533cf..000000000 --- a/mirai-core/src/commonTest/kotlin/utils/codegen/test/ConstructorCallCodegenTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2019-2021 Mamoe Technologies and contributors. - * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. - * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. - * - * https://github.com/mamoe/mirai/blob/dev/LICENSE - */ - -package net.mamoe.mirai.internal.utils.codegen.test - -import net.mamoe.mirai.internal.utils.codegen.ConstructorCallCodegenFacade -import net.mamoe.mirai.internal.utils.codegen.analyzeAndGenerate -import kotlin.test.Test -import kotlin.test.assertEquals - -class ConstructorCallCodegenTest { - - @Test - fun `test plain`() { - assertEquals( - "\"test\"", - ConstructorCallCodegenFacade.analyzeAndGenerate("test") - ) - assertEquals( - "1", - ConstructorCallCodegenFacade.analyzeAndGenerate(1) - ) - assertEquals( - "1.0", - ConstructorCallCodegenFacade.analyzeAndGenerate(1.0) - ) - } - - @Test - fun `test array`() { - assertEquals( - "arrayOf(1, 2)", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf(1, 2)) - ) - assertEquals( - "arrayOf(5.0)", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf(5.0)) - ) - assertEquals( - "arrayOf(\"1\")", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf("1")) - ) - assertEquals( - "arrayOf(arrayOf(1))", - ConstructorCallCodegenFacade.analyzeAndGenerate(arrayOf(arrayOf(1))) - ) - } - - data class TestClass( - val value: String - ) - - data class TestClass2( - val value: Any - ) - - @Test - fun `test class`() { - assertEquals( - """ - ${TestClass::class.qualifiedName!!}( - value="test", - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestClass("test")) - ) - assertEquals( - """ - ${TestClass2::class.qualifiedName!!}( - value="test", - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestClass2("test")) - ) - assertEquals( - """ - ${TestClass2::class.qualifiedName!!}( - value=1, - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestClass2(1)) - ) - } - - data class TestNesting( - val nested: Nested - ) { - data class Nested( - val value: String - ) - } - - @Test - fun `test nesting`() { - assertEquals( - """ - net.mamoe.mirai.internal.utils.codegen.test.ConstructorCallCodegenTest.TestNesting( - nested=net.mamoe.mirai.internal.utils.codegen.test.ConstructorCallCodegenTest.TestNesting.Nested( - value="test", - ), - ) - """.trimIndent(), - ConstructorCallCodegenFacade.analyzeAndGenerate(TestNesting(TestNesting.Nested("test"))) - ) - } -} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt b/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt index abd154fca..ebffce213 100644 --- a/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt +++ b/mirai-core/src/jvmTest/kotlin/bootstrap/RunRecorder.kt @@ -14,8 +14,8 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline -import net.mamoe.mirai.internal.notice.Desensitizer -import net.mamoe.mirai.internal.notice.RecordingNoticeProcessor +import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer +import net.mamoe.mirai.internal.testFramework.notice.RecordingNoticeProcessor import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.readResource import net.mamoe.yamlkt.Yaml diff --git a/mirai-core/src/jvmTest/kotlin/utils/StructureToStringLegacy.kt b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerLegacy.kt similarity index 97% rename from mirai-core/src/jvmTest/kotlin/utils/StructureToStringLegacy.kt rename to mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerLegacy.kt index 175e76545..e5c289e6a 100644 --- a/mirai-core/src/jvmTest/kotlin/utils/StructureToStringLegacy.kt +++ b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerLegacy.kt @@ -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) diff --git a/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerNew.kt b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerNew.kt new file mode 100644 index 000000000..744b86ad4 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/utils/StructureToStringTransformerNew.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.utils + +import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer +import net.mamoe.mirai.internal.testFramework.codegen.analyze +import net.mamoe.mirai.internal.testFramework.codegen.descriptors.transform +import net.mamoe.mirai.internal.testFramework.codegen.removeDefaultValues +import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer +import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString + +internal class StructureToStringTransformerNew : StructureToStringTransformer { + private val legacy = StructureToStringTransformerLegacy() + + override fun transform(any: Any?): String = + kotlin.runCatching { + ValueDescAnalyzer.analyze(any) + .transform(OptimizeByteArrayAsHexStringTransformer()) + ?.removeDefaultValues() + ?.renderToString() + }.getOrNull() ?: legacy.transform(any) +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt b/mirai-core/src/jvmTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt new file mode 100644 index 000000000..1b0f37dcf --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2019-2021 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.internal.utils.test + +import net.mamoe.mirai.internal.test.AbstractTest +import net.mamoe.mirai.internal.utils.structureToString +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class StructureToStringTransformerNewTest : AbstractTest() { + + data class MyClass( + val value: String + ) + + @Test + fun `can load service`() { + assertEquals( + """ + ${MyClass::class.qualifiedName}( + value = "1", + ) + """.trimIndent(), + MyClass("1").structureToString() + ) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/resources/META-INF/services/net.mamoe.mirai.internal.utils.StructureToStringTransformer b/mirai-core/src/jvmTest/resources/META-INF/services/net.mamoe.mirai.internal.utils.StructureToStringTransformer new file mode 100644 index 000000000..1b0ca2479 --- /dev/null +++ b/mirai-core/src/jvmTest/resources/META-INF/services/net.mamoe.mirai.internal.utils.StructureToStringTransformer @@ -0,0 +1,10 @@ +# +# Copyright 2019-2021 Mamoe Technologies and contributors. +# +# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. +# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. +# +# https://github.com/mamoe/mirai/blob/dev/LICENSE +# + +net.mamoe.mirai.internal.utils.StructureToStringTransformerNew \ No newline at end of file