All Downloads are FREE. Search and download functionalities are using the official Maven repository.

internal.command.CommandReflector.kt Maven / Gradle / Ivy

There is a newer version: 2.16.0
Show newest version
/*
 * Copyright 2019-2020 Mamoe Technologies and contributors.
 *
 * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
 * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
 *
 * https://github.com/mamoe/mirai/blob/master/LICENSE
 */

package net.mamoe.mirai.console.internal.command

import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.buildMessageChain
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.KVisibility
import kotlin.reflect.full.*


internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()

internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
    when (this@flattenCommandComponents) {
        is PlainText -> [email protected](' ').filterNot { it.isBlank() }
            .forEach { +PlainText(it) }
        is CharSequence -> [email protected](' ').filterNot { it.isBlank() }
            .forEach { +PlainText(it) }
        is SingleMessage -> add(this@flattenCommandComponents)
        is Array<*> -> [email protected] { if (it != null) addAll(it.flattenCommandComponents()) }
        is Iterable<*> -> [email protected] { if (it != null) addAll(it.flattenCommandComponents()) }
        else -> add([email protected]())
    }
}

@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
internal object CompositeCommandSubCommandAnnotationResolver :
    SubCommandAnnotationResolver {
    override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
        function.hasAnnotation()

    override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array {
        val annotated = function.findAnnotation()!!.value
        return if (annotated.isEmpty()) arrayOf(function.name)
        else annotated
    }

    override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
        parameter.findAnnotation()?.value

    override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
        function.findAnnotation()?.value
}

@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
internal object SimpleCommandSubCommandAnnotationResolver :
    SubCommandAnnotationResolver {
    override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
        function.hasAnnotation()

    override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array =
        emptyArray()

    override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
        parameter.findAnnotation()?.value

    override fun getDescription(ownerCommand: Command, function: KFunction<*>): String =
        ownerCommand.description
}

internal interface SubCommandAnnotationResolver {
    fun hasAnnotation(ownerCommand: Command, function: KFunction<*>): Boolean
    fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array
    fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String?
    fun getDescription(ownerCommand: Command, function: KFunction<*>): String?
}

@ConsoleExperimentalApi
public class IllegalCommandDeclarationException : Exception {
    public override val message: String?

    public constructor(
        ownerCommand: Command,
        correspondingFunction: KFunction<*>,
        message: String?,
    ) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") {
        this.message = message
    }

    public constructor(
        ownerCommand: Command,
        message: String?,
    ) : super("Illegal command declaration: ${ownerCommand::class.qualifiedName}") {
        this.message = message
    }
}

@OptIn(ExperimentalCommandDescriptors::class)
internal class CommandReflector(
    val command: Command,
    val annotationResolver: SubCommandAnnotationResolver,
) {

    @Suppress("NOTHING_TO_INLINE")
    private inline fun KFunction<*>.illegalDeclaration(
        message: String,
    ): Nothing {
        throw IllegalCommandDeclarationException(command, this, message)
    }

    private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this)
    private fun KFunction<*>.checkExtensionReceiver() {
        this.extensionReceiverParameter?.let { receiver ->
            if (receiver.type.classifierAsKClassOrNull()?.isSubclassOf(CommandSender::class) != true) {
                illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender.")
            }
        }
    }

    private fun KFunction<*>.checkNames() {
        val names = annotationResolver.getSubCommandNames(command, this)
        for (name in names) {
            ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let {
                illegalDeclaration("'$it' is forbidden in command name.")
            }
        }
    }

    private fun KFunction<*>.checkModifiers() {
        if (isInline) illegalDeclaration("Command function cannot be inline")
        if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.")
        if (this.hasAnnotation()) illegalDeclaration("Command function must not be static.")

        // should we allow abstract?

        // if (isAbstract) illegalDeclaration("Command function cannot be abstract")
    }

    fun generateUsage(overloads: Iterable): String {
        return generateUsage(command, annotationResolver, overloads)
    }

    companion object {
        fun generateUsage(command: Command, annotationResolver: SubCommandAnnotationResolver?, overloads: Iterable): String {
            return overloads.joinToString("\n") { subcommand ->
                buildString {
                    if (command.prefixOptional) {
                        append("(")
                        append(CommandManager.commandPrefix)
                        append(")")
                    } else {
                        append(CommandManager.commandPrefix)
                    }
                    //if (command is CompositeCommand) {
                    append(command.primaryName)
                    append(" ")
                    //}
                    append(subcommand.valueParameters.joinToString(" ") { it.render() })
                    if (annotationResolver != null && subcommand is CommandSignatureFromKFunction) {
                        annotationResolver.getDescription(command, subcommand.originFunction)?.let { description ->
                            append("    # ")
                            append(description)
                        }
                    }
                }
            }
        }

        private fun  AbstractCommandValueParameter.render(): String {
            return when (this) {
                is AbstractCommandValueParameter.Extended,
                is AbstractCommandValueParameter.UserDefinedType<*>,
                -> {
                    val nameToRender = this.name ?: this.type.classifierAsKClass().simpleName
                    if (isOptional) "[$nameToRender]" else "<$nameToRender>"
                }
                is AbstractCommandValueParameter.StringConstant -> {
                    this.expectingValue
                }
            }
        }
    }

    fun validate(signatures: List) {

        data class ErasedParameterInfo(
            val index: Int,
            val name: String?,
            val type: KType, // ignore nullability
            val additional: String?,
        )

        data class ErasedVariantInfo(
            val receiver: ErasedParameterInfo?,
            val valueParameters: List,
        )

        fun CommandParameter<*>.toErasedParameterInfo(index: Int): ErasedParameterInfo {
            return ErasedParameterInfo(
                index,
                this.name,
                this.type.withNullability(false),
                if (this is AbstractCommandValueParameter.StringConstant) this.expectingValue else null
            )
        }

        val candidates = signatures.map { variant ->
            variant to ErasedVariantInfo(
                variant.receiverParameter?.toErasedParameterInfo(0),
                variant.valueParameters.mapIndexed { index, parameter -> parameter.toErasedParameterInfo(index) }
            )
        }

        val groups = candidates.groupBy { it.second }

        val clashes = groups.entries.find { (_, value) ->
            value.size > 1
        } ?: return

        throw CommandDeclarationClashException(command, clashes.value.map { it.first })
    }

    @Throws(IllegalCommandDeclarationException::class)
    fun findSubCommands(): List {
        return command::class.functions // exclude static later
            .asSequence()
            .filter { it.isSubCommandFunction() }
            .onEach { it.checkExtensionReceiver() }
            .onEach { it.checkModifiers() }
            .onEach { it.checkNames() }
            .flatMap { function ->
                val names = annotationResolver.getSubCommandNames(command, function)
                if (names.isEmpty()) sequenceOf(createMapEntry(null, function))
                else names.associateWith { function }.asSequence()
            }
            .map { (name, function) ->

                val functionNameAsValueParameter =
                    name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
                        .orEmpty()

                val valueParameters = function.valueParameters.toMutableList()
                var receiverParameter = function.extensionReceiverParameter
                if (receiverParameter == null && valueParameters.isNotEmpty()) {
                    val valueFirstParameter = valueParameters[0]
                    if (valueFirstParameter.type.classifierAsKClassOrNull()?.isSubclassOf(CommandSender::class) == true) {
                        receiverParameter = valueFirstParameter
                        valueParameters.removeAt(0)
                    }
                }

                val functionValueParameters =
                    valueParameters.associateBy { it.toUserDefinedCommandParameter() }

                CommandSignatureFromKFunctionImpl(
                    receiverParameter = receiverParameter?.toCommandReceiverParameter(),
                    valueParameters = functionNameAsValueParameter + functionValueParameters.keys,
                    originFunction = function
                ) { call ->
                    val args = LinkedHashMap()

                    for ((commandParameter, value) in call.resolvedValueArguments) {
                        if (commandParameter is AbstractCommandValueParameter.StringConstant) {
                            continue
                        }
                        val functionParameter =
                            functionValueParameters[commandParameter] ?: error("Could not find a corresponding function parameter '${commandParameter.name}'")
                        args[functionParameter] = value
                    }

                    val instanceParameter = function.instanceParameter
                    if (instanceParameter != null) {
                        check(instanceParameter.type.classifierAsKClass().isInstance(command)) {
                            "Bad command call resolved. " +
                                "Function expects instance parameter ${instanceParameter.type} whereas actual instance is ${command::class}."
                        }
                        args[instanceParameter] = command
                    }

                    if (receiverParameter != null) {
                        check(receiverParameter.type.classifierAsKClass().isInstance(call.caller)) {
                            "Bad command call resolved. " +
                                "Function expects receiver parameter ${receiverParameter.type} whereas actual is ${call.caller::class}."
                        }
                        args[receiverParameter] = call.caller
                    }
                    function.callSuspendBy(args)
                }
            }.toList()
    }

    private fun  createMapEntry(key: K, value: V) = object : Map.Entry {
        override val key: K get() = key
        override val value: V get() = value
    }

    private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter {
        check(!this.isVararg) { "Receiver cannot be vararg." }
        check(this.type.classifierAsKClass().isSubclassOf(CommandSender::class)) { "Receiver must be subclass of CommandSender" }

        return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
    }

    private fun createStringConstantParameterForName(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant {
        return AbstractCommandValueParameter.StringConstant("#$index", expectingValue, true)
    }

    private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> {
        return AbstractCommandValueParameter.UserDefinedType(nameForCommandParameter(), this.isOptional, this.isVararg, this.type) // Any? is erased
    }

    private fun KParameter.nameForCommandParameter(): String? = annotationResolver.getAnnotatedName(command, this) ?: this.name
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy