From fe7007db4dd929c5138258a75fa1b41f9b0cfb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Berke=20Ak=C3=A7en?= Date: Fri, 20 Sep 2024 21:29:20 +0300 Subject: [PATCH] Added Flag and Option annotations for parsing options via arguments --- .../commandframework/CommandArguments.java | 32 +++++- .../commandframework/CommandFramework.java | 11 +- .../commandframework/CommandHandler.java | 23 ++-- .../commandframework/annotations/Flag.java | 29 +++++ .../commandframework/annotations/Option.java | 35 ++++++ .../cooldown/CooldownManager.java | 1 + .../exceptions/CommandException.java | 6 +- .../exceptions/CooldownException.java | 5 +- .../exceptions/package-info.java | 9 ++ .../commandframework/parser/OptionParser.java | 102 ++++++++++++++++++ 10 files changed, 229 insertions(+), 24 deletions(-) create mode 100644 src/main/java/me/despical/commandframework/annotations/Flag.java create mode 100644 src/main/java/me/despical/commandframework/annotations/Option.java create mode 100644 src/main/java/me/despical/commandframework/exceptions/package-info.java create mode 100644 src/main/java/me/despical/commandframework/parser/OptionParser.java diff --git a/src/main/java/me/despical/commandframework/CommandArguments.java b/src/main/java/me/despical/commandframework/CommandArguments.java index 03ae5fe..87d9246 100644 --- a/src/main/java/me/despical/commandframework/CommandArguments.java +++ b/src/main/java/me/despical/commandframework/CommandArguments.java @@ -29,7 +29,10 @@ import java.text.MessageFormat; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; /** * A utility class to use command arguments without external @@ -41,10 +44,14 @@ */ public final class CommandArguments { - final me.despical.commandframework.annotations.Command command; + private Set parsedFlags; + private Map> parsedArguments; + + private final me.despical.commandframework.annotations.Command command; private final CommandSender commandSender; private final Command bukkitCommand; - private final String label, arguments[]; + private final String label; + private final String [] arguments; CommandArguments(CommandSender commandSender, Command bukkitCommand, @@ -447,4 +454,25 @@ public boolean isFloatingDecimal(String string) { public boolean checkCooldown() { return CommandFramework.instance.getCooldownManager().hasCooldown(this); } + + void setParsedArguments(Map> parsedArguments) { + this.parsedArguments = parsedArguments; + } + + @Nullable + public List getOption(final @NotNull String option) { + return this.parsedArguments.get(option); + } + + public Optional> findOption(final @NotNull String option) { + return Optional.ofNullable(this.getOption(option)); + } + + void setParsedFlags(Set parsedFlags) { + this.parsedFlags = parsedFlags; + } + + public boolean isFlagPresent(final @NotNull String flag) { + return this.parsedFlags != null && this.parsedFlags.contains(flag); + } } \ No newline at end of file diff --git a/src/main/java/me/despical/commandframework/CommandFramework.java b/src/main/java/me/despical/commandframework/CommandFramework.java index 483f9a9..7cfbacd 100644 --- a/src/main/java/me/despical/commandframework/CommandFramework.java +++ b/src/main/java/me/despical/commandframework/CommandFramework.java @@ -55,7 +55,6 @@ public class CommandFramework extends CommandHandler { protected final Plugin plugin; private final OptionManager optionManager; - private final ParameterHandler parameterHandler; private final CommandRegistry registry; public CommandFramework(@NotNull Plugin plugin) { @@ -63,16 +62,13 @@ public CommandFramework(@NotNull Plugin plugin) { this.checkIsAlreadyInitialized(); this.plugin = plugin; - this.optionManager = new OptionManager(); this.registry = new CommandRegistry(); - this.parameterHandler = new ParameterHandler(); + this.optionManager = new OptionManager(); this.initializeLogger(); super.setRegistry(this); } private void checkRelocation() { - if (this.isOptionEnabled(Option.DEBUG)) return; - String suppressRelocation = System.getProperty("commandframework.suppressrelocation"); if ("true".equals(suppressRelocation)) return; @@ -205,11 +201,6 @@ final CommandRegistry getRegistry() { return registry; } - @ApiStatus.Internal - final ParameterHandler getParameterHandler() { - return parameterHandler; - } - @ApiStatus.Internal final boolean checkConfirmation(CommandSender sender, final Command command, final Method method) { if (!isOptionEnabled(Option.CONFIRMATIONS)) return false; diff --git a/src/main/java/me/despical/commandframework/CommandHandler.java b/src/main/java/me/despical/commandframework/CommandHandler.java index ed09303..13c7e9f 100644 --- a/src/main/java/me/despical/commandframework/CommandHandler.java +++ b/src/main/java/me/despical/commandframework/CommandHandler.java @@ -1,9 +1,10 @@ package me.despical.commandframework; +import me.despical.commandframework.annotations.Option; import me.despical.commandframework.annotations.Command; import me.despical.commandframework.annotations.Completer; import me.despical.commandframework.exceptions.CooldownException; -import me.despical.commandframework.options.Option; +import me.despical.commandframework.parser.OptionParser; import me.despical.commandframework.utils.Utils; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -31,14 +32,14 @@ */ @ApiStatus.Internal @ApiStatus.NonExtendable -public abstract class CommandHandler implements CommandExecutor, TabCompleter { +abstract class CommandHandler implements CommandExecutor, TabCompleter { private CommandRegistry registry; - private CommandFramework commandFramework; + protected ParameterHandler parameterHandler; void setRegistry(CommandFramework commandFramework) { - this.commandFramework = commandFramework; this.registry = commandFramework.getRegistry(); + this.parameterHandler = new ParameterHandler(); } @Override @@ -82,20 +83,28 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull org.bukkit.comm } final Method method = entry.getValue().getKey(); + final CommandFramework commandFramework = CommandFramework.getInstance(); if (commandFramework.checkConfirmation(sender, command, method)) { return true; } - if (!this.commandFramework.isOptionEnabled(Option.CUSTOM_COOLDOWN_CHECKER) && commandFramework.getCooldownManager().hasCooldown(arguments, command, method)) { + if (commandFramework.getCooldownManager().hasCooldown(arguments, command, method)) { return true; } + if (method.getAnnotationsByType(Option.class).length > 0) { + OptionParser optionParser = new OptionParser(newArgs, method); + + arguments.setParsedArguments(optionParser.parseOptions()); + arguments.setParsedFlags(optionParser.parseFlags()); + } + final Runnable invocation = () -> { try { final Object instance = entry.getValue().getValue(); - method.invoke(instance, commandFramework.getParameterHandler().getParameterArray(method, arguments)); + method.invoke(instance, parameterHandler.getParameterArray(method, arguments)); } catch (Exception exception) { if (exception.getCause() instanceof CooldownException) { return; @@ -133,7 +142,7 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull org.bu final Object instance = entry.getValue().getValue(); final String[] splitName = entry.getKey().name().split("\\."); final String[] newArgs = Arrays.copyOfRange(args, splitName.length - 1, args.length); - final Object completer = method.invoke(instance, commandFramework.getParameterHandler().getParameterArray(method, new CommandArguments(sender, cmd, null, label, newArgs))); + final Object completer = method.invoke(instance, parameterHandler.getParameterArray(method, new CommandArguments(sender, cmd, null, label, newArgs))); return (List) completer; } catch (Exception exception) { diff --git a/src/main/java/me/despical/commandframework/annotations/Flag.java b/src/main/java/me/despical/commandframework/annotations/Flag.java new file mode 100644 index 0000000..c5f231c --- /dev/null +++ b/src/main/java/me/despical/commandframework/annotations/Flag.java @@ -0,0 +1,29 @@ +package me.despical.commandframework.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Despical + *

+ * Created at 20.09.2024 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Flag.FlagContainer.class) +public @interface Flag { + + String[] value(); + + String prefix() default "--"; + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface FlagContainer { + + Flag[] value(); + } +} \ No newline at end of file diff --git a/src/main/java/me/despical/commandframework/annotations/Option.java b/src/main/java/me/despical/commandframework/annotations/Option.java new file mode 100644 index 0000000..4dd1971 --- /dev/null +++ b/src/main/java/me/despical/commandframework/annotations/Option.java @@ -0,0 +1,35 @@ +package me.despical.commandframework.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Despical + *

+ * Created at 20.09.2024 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Option.OptionContainer.class) +public @interface Option { + + String name(); + + String prefix() default "--"; + + String valueSeparator() default ","; + + String keySeparator() default "="; + + boolean allowSeparating() default true; + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface OptionContainer { + + Option[] value(); + } +} \ No newline at end of file diff --git a/src/main/java/me/despical/commandframework/cooldown/CooldownManager.java b/src/main/java/me/despical/commandframework/cooldown/CooldownManager.java index 396240d..1532f8c 100644 --- a/src/main/java/me/despical/commandframework/cooldown/CooldownManager.java +++ b/src/main/java/me/despical/commandframework/cooldown/CooldownManager.java @@ -61,6 +61,7 @@ public boolean hasCooldown(CommandArguments arguments) { } public boolean hasCooldown(final CommandArguments arguments, final Command command, final Method method) { + if (commandFramework.isOptionEnabled(Option.CUSTOM_COOLDOWN_CHECKER)) return false; if (method == null) return false; if (!method.isAnnotationPresent(Cooldown.class)) return false; diff --git a/src/main/java/me/despical/commandframework/exceptions/CommandException.java b/src/main/java/me/despical/commandframework/exceptions/CommandException.java index be913c4..f41b2d7 100644 --- a/src/main/java/me/despical/commandframework/exceptions/CommandException.java +++ b/src/main/java/me/despical/commandframework/exceptions/CommandException.java @@ -28,7 +28,11 @@ * Created at 23.01.2024 */ @ApiStatus.Internal -public final class CommandException extends RuntimeException { +public class CommandException extends RuntimeException { + + public CommandException() { + super(); + } public CommandException(final String message) { super(message); diff --git a/src/main/java/me/despical/commandframework/exceptions/CooldownException.java b/src/main/java/me/despical/commandframework/exceptions/CooldownException.java index 71bcbc0..d9ddbf3 100644 --- a/src/main/java/me/despical/commandframework/exceptions/CooldownException.java +++ b/src/main/java/me/despical/commandframework/exceptions/CooldownException.java @@ -1,12 +1,9 @@ package me.despical.commandframework.exceptions; -import org.jetbrains.annotations.ApiStatus; - /** * @author Despical *

* Created at 18.07.2024 */ -@ApiStatus.Internal -public final class CooldownException extends RuntimeException { +public class CooldownException extends CommandException { } \ No newline at end of file diff --git a/src/main/java/me/despical/commandframework/exceptions/package-info.java b/src/main/java/me/despical/commandframework/exceptions/package-info.java new file mode 100644 index 0000000..adb95b2 --- /dev/null +++ b/src/main/java/me/despical/commandframework/exceptions/package-info.java @@ -0,0 +1,9 @@ +/** + * @author Despical + *

+ * Created at 20.09.2024 + */ +@ApiStatus.Internal +package me.despical.commandframework.exceptions; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/src/main/java/me/despical/commandframework/parser/OptionParser.java b/src/main/java/me/despical/commandframework/parser/OptionParser.java new file mode 100644 index 0000000..da36021 --- /dev/null +++ b/src/main/java/me/despical/commandframework/parser/OptionParser.java @@ -0,0 +1,102 @@ +package me.despical.commandframework.parser; + +import me.despical.commandframework.annotations.Flag; +import me.despical.commandframework.annotations.Option; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.regex.Pattern; + +/** + * @author Despical + *

+ * Created at 20.09.2024 + */ +public class OptionParser { + + private final Flag[] flags; + private final Option[] options; + private final Set arguments; + private final Set parsedFlags; + private final Map> parsedOptions; + + public OptionParser(String[] arguments, Method method) { + this.flags = method.getAnnotationsByType(Flag.class); + this.options = method.getAnnotationsByType(Option.class); + this.arguments = new HashSet<>(Arrays.asList(arguments)); + this.parsedFlags = new HashSet<>(); + this.parsedOptions = new HashMap<>(); + } + + public Map> parseOptions() { + for (Option option : options) { + this.parseOption(option); + } + + return this.parsedOptions; + } + + public Set parseFlags() { + for (Flag flag : flags) { + this.parseFlag(flag); + } + + return this.parsedFlags; + } + + private void parseOption(Option option) { + String prefix = option.prefix(); + String keySeparator = Pattern.quote(option.keySeparator()); + String valueSeparator = Pattern.quote(option.valueSeparator()); + Iterator iterator = arguments.iterator(); + + while (iterator.hasNext()) { + String argument = iterator.next(); + + if (!argument.startsWith(prefix)) { + continue; + } + + if (option.allowSeparating() && !argument.contains(keySeparator)) { + continue; + } + + String[] options = argument.substring(prefix.length()).split(keySeparator); + + if (!option.allowSeparating()) { + String value = options.length <= 1 ? "" : options[1]; + this.parsedOptions.put(option.name(), Collections.singletonList(value)); + + iterator.remove(); + continue; + } + + String[] values = options[1].split(valueSeparator); + this.parsedOptions.put(option.name(), Arrays.asList(values)); + + iterator.remove(); + } + } + + private void parseFlag(Flag flag) { + String prefix = flag.prefix(); + + outer: + for (String argument : this.arguments) { + if (!argument.startsWith(prefix)) { + continue; + } + + String foundFlag = argument.substring(prefix.length()); + + for (String flagName : flag.value()) { + if (!flagName.equals(foundFlag)) { + continue; + } + + this.parsedFlags.add(flagName); + continue outer; + } + } + } +} \ No newline at end of file