From 30aa9f960c35936aeeb449ae09ac59cd3434752b Mon Sep 17 00:00:00 2001 From: Hammy Date: Wed, 22 Sep 2021 01:39:05 +0100 Subject: [PATCH] Use Bean Introspection to get AnnotationMetaData and load SlashCommands --- .../goudham/command/SlashCommandLoader.java | 162 ++++++++---------- 1 file changed, 67 insertions(+), 95 deletions(-) diff --git a/src/main/java/me/goudham/command/SlashCommandLoader.java b/src/main/java/me/goudham/command/SlashCommandLoader.java index 0cc33bbe..210a033f 100644 --- a/src/main/java/me/goudham/command/SlashCommandLoader.java +++ b/src/main/java/me/goudham/command/SlashCommandLoader.java @@ -3,11 +3,12 @@ package me.goudham.command; import io.micronaut.context.ApplicationContext; import io.micronaut.context.BeanContext; import io.micronaut.core.annotation.AnnotationValue; -import io.micronaut.inject.BeanDefinition; -import io.micronaut.inject.qualifiers.Qualifiers; +import io.micronaut.core.beans.BeanIntrospection; +import io.micronaut.core.beans.BeanIntrospector; +import io.micronaut.core.beans.BeanMethod; +import io.micronaut.inject.ExecutableMethod; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -17,14 +18,12 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; -import java.util.stream.Collectors; import me.goudham.command.annotation.Choice; import me.goudham.command.annotation.Option; import me.goudham.command.annotation.SlashCommand; import me.goudham.command.annotation.SubCommand; import me.goudham.command.annotation.SubCommandGroup; import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; -import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.OptionData; @@ -36,58 +35,75 @@ import net.dv8tion.jda.internal.utils.tuple.Pair; @Singleton public class SlashCommandLoader implements CommandLoader { private final BeanContext beanContext; - private final ApplicationContext applicationContext; @Inject public SlashCommandLoader(BeanContext beanContext, ApplicationContext applicationContext) { this.beanContext = beanContext; - this.applicationContext = applicationContext; - } @Override - public List loadIntoMapAndReturnCommands(Map> commandMap) { - Collection> slashCommandDefinitions = beanContext.getBeanDefinitions(Qualifiers.byStereotype(SlashCommand.class)); - Collection> subCommandGroupDefinitions = beanContext.getBeanDefinitions(Qualifiers.byStereotype(SubCommandGroup.class)); - Collection> subCommandDefinitions = beanContext.getBeanDefinitions(Qualifiers.byStereotype(SubCommand.class)); + public List loadIntoMapAndReturnCommands(Map>> commandMap) { Map commandDataMap = new HashMap<>(); List commandDataList = new ArrayList<>(); + Collection> slashCommandIntrospections = BeanIntrospector.forClassLoader(ClassLoader.getSystemClassLoader()).findIntrospections(SlashCommand.class); + Collection> subCommandGroupIntrospections = BeanIntrospector.forClassLoader(ClassLoader.getSystemClassLoader()).findIntrospections(SubCommandGroup.class); + + for (BeanIntrospection slashCommandIntrospection : slashCommandIntrospections) { + AnnotationValue slashCommand = slashCommandIntrospection.getDeclaredAnnotation(SlashCommand.class); + Collection> subCommands = slashCommandIntrospection.getBeanMethods(); - for (BeanDefinition slashCommandDefinition : slashCommandDefinitions) { - AnnotationValue slashCommand = slashCommandDefinition.getDeclaredAnnotation(SlashCommand.class); if (slashCommand != null) { String name = slashCommand.stringValue("name").orElseThrow(); String description = slashCommand.stringValue("description").orElseThrow(); boolean isVisible = slashCommand.booleanValue("isVisible").orElseThrow(); String[] subCommandGroups = slashCommand.stringValues("subCommandGroups"); - String[] subCommands = slashCommand.stringValues("subCommands"); - CommandData commandData = new CommandData(name, description); - commandData.setDefaultEnabled(isVisible); + CommandData commandData = new CommandData(name, description).setDefaultEnabled(isVisible); + Arrays.stream(subCommandGroups) + .map(subCommandGroup -> name + "/" + subCommandGroup) + .forEach(subCommandGroupName -> commandDataMap.put(subCommandGroupName, commandData)); + + boolean noHandleMethod = slashCommandIntrospection.getBeanMethods().stream().noneMatch(method -> method.getName().equals("handle")); + if (subCommands.size() > 1 && !noHandleMethod) { + throw new RuntimeException("Cannot Have Main Command And Sub Commands In " + slashCommandIntrospection); + } - if (subCommandGroups.length == 0 && subCommands.length == 0) { + if (subCommandGroups.length < 1 && !noHandleMethod) { List optionData = loadOptions(slashCommand); if (optionData != null) commandData.addOptions(optionData); - storeIntoCommandMap(commandMap, slashCommandDefinition, name, "handle"); - commandDataMap.put(name, commandData); + storeIntoCommandMap(commandMap, slashCommandIntrospection, name, "handle"); } else { - Arrays.stream(subCommandGroups) - .map(subCommandGroup -> name + "/" + subCommandGroup) - .forEach(subCommandGroupName -> commandDataMap.put(subCommandGroupName, commandData)); - Arrays.stream(subCommands) - .map(subCommand -> name + "/" + subCommand) - .forEach(subCommandName -> commandDataMap.put(subCommandName, commandData)); - } + List subCommandList = new ArrayList<>(); + + for (BeanMethod subCommandMethod : subCommands) { + AnnotationValue subCommand = subCommandMethod.getDeclaredAnnotation(SubCommand.class); + if (subCommand != null) { + String subCommandName = subCommand.stringValue("name").orElseThrow(); + String subCommandDescription = subCommand.stringValue("description").orElseThrow(); + List optionDataList = loadOptions(subCommand); + + SubcommandData subcommandData = new SubcommandData(subCommandName, subCommandDescription); + if (optionDataList != null) subcommandData.addOptions(optionDataList); + subCommandList.add(subcommandData); + + String subCommandPath = name + "/" + subCommandName; + storeIntoCommandMap(commandMap, slashCommandIntrospection, subCommandPath, subCommandMethod.getName()); + } + } + commandData.addSubcommands(subCommandList); + } commandDataList.add(commandData); } else { - throw new RuntimeException("Slash Command Annotation For " + slashCommandDefinition + " Was Null"); + throw new RuntimeException("Slash Command Annotation For " + slashCommandIntrospection + " Was Null"); } } - for (BeanDefinition subCommandGroupDefinition : subCommandGroupDefinitions) { - AnnotationValue subCommandGroup = subCommandGroupDefinition.getDeclaredAnnotation(SubCommandGroup.class); + for (BeanIntrospection subCommandGroupIntrospection : subCommandGroupIntrospections) { + AnnotationValue subCommandGroup = subCommandGroupIntrospection.getDeclaredAnnotation(SubCommandGroup.class); + Collection> subCommands = subCommandGroupIntrospection.getBeanMethods(); + if (subCommandGroup != null) { String parent = subCommandGroup.stringValue("parent").orElseThrow(); String name = subCommandGroup.stringValue("name").orElseThrow(); @@ -97,74 +113,30 @@ public class SlashCommandLoader implements CommandLoader { CommandData commandData = commandDataMap.get(commandPath); SubcommandGroupData subcommandGroupData = new SubcommandGroupData(name, description); - Class beanType = subCommandGroupDefinition.getBeanType(); List subCommandList = new ArrayList<>(); - List subCommands = Arrays.stream(beanType.getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(SubCommand.class)) - .collect(Collectors.toList()); - - for (Method subCommand : subCommands) { - SubCommand annotation = subCommand.getDeclaredAnnotation(SubCommand.class); - String subCommandName = annotation.name(); - String subCommandDescription = annotation.description(); - List optionDataList = Arrays.stream(annotation.options()) - .map(option -> { - OptionData optionData = new OptionData(option.optionType(), option.name(), option.description(), option.isRequired()); - List choiceDataList = Arrays.stream(option.choices()) - .map(choice -> { - int intValue = (int) choice.intValue(); - double doubleValue = choice.doubleValue(); - String stringValue = choice.stringValue(); - - Command.Choice choiceData = null; - if (intValue != 0) { - choiceData = new Command.Choice(name, intValue); - } else if (!Double.isNaN(doubleValue)) { - choiceData = new Command.Choice(name, doubleValue); - } else if (!stringValue.isBlank()) { - choiceData = new Command.Choice(name, stringValue); - } - - return choiceData; - }).collect(Collectors.toList()); - return optionData.addChoices(choiceDataList); - }).collect(Collectors.toList()); - - SubcommandData subcommandData = new SubcommandData(subCommandName, subCommandDescription); - subcommandData.addOptions(optionDataList); - subCommandList.add(subcommandData); - - String subCommandPath = parent + "/" + name + "/" + subCommandName; - storeIntoCommandMap(commandMap, subCommandGroupDefinition, subCommandPath, subCommandName); - commandDataMap.put(subCommandPath, commandData); + for (BeanMethod subCommandMethod : subCommands) { + AnnotationValue subCommand = subCommandMethod.getDeclaredAnnotation(SubCommand.class); + if (subCommand != null) { + String subCommandName = subCommand.stringValue("name").orElseThrow(); + String subCommandDescription = subCommand.stringValue("description").orElseThrow(); + List optionDataList = loadOptions(subCommand); + + SubcommandData subcommandData = new SubcommandData(subCommandName, subCommandDescription); + if (optionDataList != null) subcommandData.addOptions(optionDataList); + subCommandList.add(subcommandData); + + String subCommandPath = parent + "/" + name + "/" + subCommandName; + storeIntoCommandMap(commandMap, subCommandGroupIntrospection, subCommandPath, subCommandMethod.getName()); + } } subcommandGroupData.addSubcommands(subCommandList); commandData.addSubcommandGroups(subcommandGroupData); } else { - throw new RuntimeException("Sub Command Annotation For " + subCommandGroupDefinition + " Was Null"); + throw new RuntimeException("SubCommandGroup Annotation For " + subCommandGroupIntrospection + " Was Null"); } } - for (BeanDefinition subCommandDefinition : subCommandDefinitions) { - AnnotationValue subCommand = subCommandDefinition.getDeclaredAnnotation(SubCommand.class); - if (subCommand != null) { - String commandParent = subCommand.stringValue("commandParent").orElseThrow(); - String name = subCommand.stringValue("name").orElseThrow(); - String description = subCommand.stringValue("description").orElseThrow(); - String commandPath = commandParent + "/" + name; - - CommandData commandData = commandDataMap.get(commandPath); - SubcommandData subcommandData = new SubcommandData(name, description); - List optionData = loadOptions(subCommand); - if (optionData != null) subcommandData.addOptions(optionData); - - storeIntoCommandMap(commandMap, subCommandDefinition, commandPath, "handle"); - commandData.addSubcommands(subcommandData); - } else { - throw new RuntimeException("Sub Command Annotation For " + subCommandDefinition + " Was Null"); - } - } return commandDataList; } @@ -236,17 +208,17 @@ public class SlashCommandLoader implements CommandLoader { return null; } - private void storeIntoCommandMap(Map> commandMap, BeanDefinition beanDefinition, String commandPath, String methodName) { - Class clazz = beanDefinition.getBeanType(); - Object bean = applicationContext.getBean(clazz); + private void storeIntoCommandMap(Map>> commandMap, BeanIntrospection beanIntrospection, String commandPath, String methodName) { + Class clazz = beanIntrospection.getBeanType(); + Object bean = beanContext.getBean(clazz); - Method method = null; + ExecutableMethod executableMethod = null; try { - method = clazz.getMethod(methodName, SlashCommandEvent.class); + executableMethod = beanContext.getExecutableMethod(clazz, methodName, SlashCommandEvent.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } - commandMap.put(commandPath, new ImmutablePair<>(bean, method)); + commandMap.put(commandPath, new ImmutablePair<>(bean, executableMethod)); } }