From 415a002cad835480af411d214f877a8503cdfaf9 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 9 Dec 2021 19:19:17 +0100 Subject: [PATCH] Update SWCommand system to CommandPart System for better usages and extension possibilities --- .../src/de/steamwar/command/SWCommand.java | 3 +- .../de/steamwar/command/SWCommandUtils.java | 116 +++++++++- .../src/de/steamwar/command/SubCommand.java | 211 ++++-------------- .../steamwar/command/SimpleCommandTest.java | 1 + 4 files changed, 147 insertions(+), 184 deletions(-) diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java index 0558dad..98d742a 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java @@ -174,8 +174,7 @@ public abstract class SWCommand { if (compare != 0) { return compare; } else { - return Integer.compare(o1.varArgType != null ? Integer.MAX_VALUE : o1.arguments.length, - o2.varArgType != null ? Integer.MAX_VALUE : o2.arguments.length); + return Integer.compare(o1.comparableValue, o2.comparableValue); } }); commandHelpList.sort((o1, o2) -> { diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java index aa5d27b..fa6c3f3 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java @@ -29,6 +29,7 @@ import org.bukkit.entity.Player; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -43,15 +44,6 @@ public class SWCommandUtils { static final Map> MAPPER_FUNCTIONS = new HashMap<>(); static final Map GUARD_FUNCTIONS = new HashMap<>(); - static final TypeMapper ERROR_FUNCTION = createMapper(s -> { - throw new SecurityException(); - }, s -> Collections.emptyList()); - - static final BiFunction>, String, Enum> ENUM_MAPPER = (enumClass, s) -> { - Enum[] enums = enumClass.getEnumConstants(); - return Arrays.stream(enums).filter(e -> e.name().equalsIgnoreCase(s)).findFirst().orElse(null); - }; - static { addMapper(boolean.class, Boolean.class, createMapper(Boolean::parseBoolean, s -> Arrays.asList("true", "false"))); addMapper(float.class, Float.class, createMapper(numberMapper(Float::parseFloat), numberCompleter(Float::parseFloat))); @@ -93,6 +85,93 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } + static CommandPart generateCommandPart(boolean help, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map localGuardChecker) { + CommandPart current = null; + for (String s : subCommand) { + CommandPart commandPart = new CommandPart(createMapper(s), null, null, null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + } + for (int i = 1; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + TypeMapper typeMapper = getTypeMapper(parameter, localTypeMapper); + GuardChecker guardChecker = getGuardChecker(parameter, localGuardChecker); + Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; + SWCommand.OptionalValue optionalValue = parameter.getAnnotation(SWCommand.OptionalValue.class); + + CommandPart commandPart = new CommandPart(typeMapper, guardChecker, varArgType, optionalValue != null ? optionalValue.value() : null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + } + return current; + } + + static TypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); + } + + SWCommand.ClassMapper classMapper = clazz.getAnnotation(SWCommand.ClassMapper.class); + SWCommand.Mapper mapper = clazz.getAnnotation(SWCommand.Mapper.class); + if (clazz.isEnum() && classMapper == null && mapper == null && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { + return createEnumMapper((Class>) clazz); + } + + String name = clazz.getTypeName(); + if (classMapper != null) { + name = classMapper.value().getTypeName(); + } else if (mapper != null) { + name = mapper.value(); + } else { + SWCommand.StaticValue staticValue = parameter.getAnnotation(SWCommand.StaticValue.class); + if (staticValue != null && parameter.getType() == String.class) { + return createMapper(staticValue.value()); + } + } + TypeMapper typeMapper = localTypeMapper.getOrDefault(name, MAPPER_FUNCTIONS.getOrDefault(name, null)); + if (typeMapper == null) { + throw new IllegalArgumentException("No mapper found for " + name); + } + return typeMapper; + } + + static GuardChecker getGuardChecker(Parameter parameter, Map localGuardChecker) { + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); + } + + SWCommand.ClassGuard classGuard = clazz.getAnnotation(SWCommand.ClassGuard.class); + if (classGuard != null) { + if (classGuard.value() != null) { + return getGuardChecker(classGuard.value().getTypeName(), localGuardChecker); + } + return getGuardChecker(clazz.getTypeName(), localGuardChecker); + } + + SWCommand.Guard guard = clazz.getAnnotation(SWCommand.Guard.class); + if (guard != null) { + if (guard.value() != null) { + return getGuardChecker(guard.value(), localGuardChecker); + } + return getGuardChecker(clazz.getTypeName(), localGuardChecker); + } + return null; + } + + private static GuardChecker getGuardChecker(String s, Map localGuardChecker) { + GuardChecker guardChecker = localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null)); + if (guardChecker == null) { + throw new IllegalArgumentException("No guard found for " + s); + } + return guardChecker; + } + static Object[] generateArgumentArray(CommandSender commandSender, TypeMapper[] parameters, GuardChecker[] guards, String[] args, Class varArgType, String[] subCommand) throws CommandParseException { Object[] arguments = new Object[parameters.length + 1]; int index = 0; @@ -166,6 +245,25 @@ public class SWCommandUtils { }; } + public static TypeMapper> createEnumMapper(Class> enumClass) { + Enum[] enums = enumClass.getEnumConstants(); + List strings = Arrays.stream(enums).map(Enum::name).collect(Collectors.toList()); + return new TypeMapper>() { + @Override + public Enum map(CommandSender commandSender, String[] previousArguments, String s) { + for (Enum e : enums) { + if (e.name().equalsIgnoreCase(s)) return e; + } + return null; + } + + @Override + public List tabCompletes(CommandSender commandSender, String[] previousArguments, String s) { + return strings; + } + }; + } + private static Function numberMapper(Function mapper) { return s -> { try { diff --git a/SpigotCore_Main/src/de/steamwar/command/SubCommand.java b/SpigotCore_Main/src/de/steamwar/command/SubCommand.java index 07f05d8..3cbc445 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SubCommand.java +++ b/SpigotCore_Main/src/de/steamwar/command/SubCommand.java @@ -19,18 +19,16 @@ package de.steamwar.command; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.function.Predicate; -import java.util.logging.Level; - -import static de.steamwar.command.SWCommandUtils.*; class SubCommand { @@ -38,145 +36,62 @@ class SubCommand { Method method; String[] description; String[] subCommand; - TypeMapper[] arguments; - GuardChecker[] guards; private Predicate commandSenderPredicate; private Function commandSenderFunction; GuardChecker guardChecker; - Class varArgType = null; - private boolean help; boolean noTabComplete; + int comparableValue; + + private CommandPart commandPart; SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map> localTypeMapper, Map localGuardChecker, boolean help, String[] description, boolean noTabComplete) { this.swCommand = swCommand; this.method = method; - this.help = help; this.description = description; this.noTabComplete = noTabComplete; + this.subCommand = subCommand; Parameter[] parameters = method.getParameters(); + comparableValue = parameters[parameters.length - 1].isVarArgs() ? Integer.MAX_VALUE : subCommand.length; + + guardChecker = SWCommandUtils.getGuardChecker(parameters[0], localGuardChecker); + + commandPart = SWCommandUtils.generateCommandPart(help, subCommand, parameters, localTypeMapper, localGuardChecker); commandSenderPredicate = sender -> parameters[0].getType().isAssignableFrom(sender.getClass()); commandSenderFunction = sender -> parameters[0].getType().cast(sender); - this.subCommand = subCommand; - guardChecker = getGuardChecker(parameters[0], localGuardChecker); - - arguments = new TypeMapper[parameters.length - 1]; - guards = new GuardChecker[parameters.length - 1]; - for (int i = 1; i < parameters.length; i++) { - Parameter parameter = parameters[i]; - Class clazz = parameter.getType(); - if (parameter.isVarArgs()) { - clazz = clazz.getComponentType(); - varArgType = clazz; - } - - SWCommand.Mapper mapper = parameter.getAnnotation(SWCommand.Mapper.class); - SWCommand.ClassMapper classMapper = parameter.getAnnotation(SWCommand.ClassMapper.class); - if (clazz.isEnum() && mapper == null && classMapper == null && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { - Class> enumClass = (Class>) clazz; - List tabCompletes = new ArrayList<>(); - for (Enum enumConstant : enumClass.getEnumConstants()) { - tabCompletes.add(enumConstant.name().toLowerCase()); - } - arguments[i - 1] = SWCommandUtils.createMapper(s -> ENUM_MAPPER.apply(enumClass, s), s -> tabCompletes); - continue; - } - - String name = clazz.getTypeName(); - if (classMapper != null) { - name = classMapper.value().getTypeName(); - } else if (mapper != null) { - name = mapper.value(); - } else { - SWCommand.StaticValue staticValue = parameter.getAnnotation(SWCommand.StaticValue.class); - if (staticValue != null && parameter.getType() == String.class) { - arguments[i - 1] = SWCommandUtils.createMapper(staticValue.value()); - guards[i - 1] = getGuardChecker(parameter, localGuardChecker); - continue; - } - } - arguments[i - 1] = localTypeMapper.containsKey(name) - ? localTypeMapper.get(name) - : MAPPER_FUNCTIONS.getOrDefault(name, ERROR_FUNCTION); - guards[i - 1] = getGuardChecker(parameter, localGuardChecker); - } - } - - private GuardChecker getGuardChecker(Parameter parameter, Map localGuardChecker) { - SWCommand.ClassGuard classGuard = parameter.getAnnotation(SWCommand.ClassGuard.class); - if (classGuard != null) { - if (classGuard.value() == null) { - String s = parameter.getType().getTypeName(); - if (parameter.isVarArgs()) { - s = parameter.getType().getComponentType().getTypeName(); - } - return localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null)); - } - GuardChecker current = localGuardChecker.getOrDefault(classGuard.value().getTypeName(), GUARD_FUNCTIONS.getOrDefault(classGuard.value().getTypeName(), null)); - if (current == null) { - Bukkit.getLogger().log(Level.WARNING, () -> "The guard checker with name '" + classGuard.value().getTypeName() + "' is neither a local guard checker nor a global one"); - } - return current; - } - SWCommand.Guard guard = parameter.getAnnotation(SWCommand.Guard.class); - if (guard != null) { - if (guard.value() == null || guard.value().isEmpty()) { - String s = parameter.getType().getTypeName(); - if (parameter.isVarArgs()) { - s = parameter.getType().getComponentType().getTypeName(); - } - return localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null)); - } - GuardChecker current = localGuardChecker.getOrDefault(guard.value(), GUARD_FUNCTIONS.getOrDefault(guard.value(), null)); - if (current == null) { - Bukkit.getLogger().log(Level.WARNING, () -> "The guard checker with name '" + guard.value() + "' is neither a local guard checker nor a global one"); - } - return current; - } - return null; } boolean invoke(CommandSender commandSender, String[] args) { - if (args.length < arguments.length + subCommand.length - (varArgType != null ? 1 : 0)) { - return false; - } - if (varArgType == null && args.length > arguments.length + subCommand.length) { - return false; - } try { if (!commandSenderPredicate.test(commandSender)) { return false; } - Object[] objects = SWCommandUtils.generateArgumentArray(commandSender, arguments, guards, args, varArgType, subCommand); - objects[0] = commandSenderFunction.apply(commandSender); - for (int i = subCommand.length; i < args.length; i++) { - GuardChecker current; - if (i == subCommand.length) { - current = guardChecker; - } else { - if (i >= objects.length + subCommand.length) { - current = guards[guards.length - 1]; - } else { - current = guards[i - 1 - subCommand.length]; - } + + if (commandPart == null) { + if (args.length != 0) { + return false; } - if (current != null) { - GuardResult guardResult; - if (i == 0) { - guardResult = current.guard(commandSender, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, new String[0], null); - } else { - guardResult = current.guard(commandSender, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, Arrays.copyOf(args, i - 1), args[i - 1]); - } - if (guardResult != GuardResult.ALLOWED) { - if (guardResult == GuardResult.DENIED) { + method.setAccessible(true); + method.invoke(swCommand, commandSenderFunction.apply(commandSender)); + } else { + List objects = new ArrayList<>(); + commandPart.generateArgumentArray(objects, commandSender, args, 0); + if (guardChecker != null) { + GuardResult guardResult = guardChecker.guard(commandSender, GuardCheckType.COMMAND, new String[0], null); + switch (guardResult) { + case ALLOWED: + break; + case DENIED: throw new CommandNoHelpException(); - } - return false; + case DENIED_WITH_HELP: + default: + return true; } } + commandPart.guardCheck(commandSender, args, 0); + method.setAccessible(true); + method.invoke(swCommand, objects); } - method.setAccessible(true); - method.invoke(swCommand, objects); } catch (CommandNoHelpException e) { throw e; } catch (CommandParseException e) { @@ -188,64 +103,14 @@ class SubCommand { } List tabComplete(CommandSender commandSender, String[] args) { - if (varArgType == null && args.length > arguments.length + subCommand.length) { - return null; - } if (guardChecker != null && guardChecker.guard(commandSender, GuardCheckType.TAB_COMPLETE, new String[0], null) != GuardResult.ALLOWED) { return null; } - int index = 0; - List argsList = new LinkedList<>(Arrays.asList(args)); - for (String value : subCommand) { - String s = argsList.remove(0); - if (argsList.isEmpty()) return Collections.singletonList(value); - if (!value.equalsIgnoreCase(s)) return null; - index++; + if (commandPart == null) { + return null; } - int guardIndex = 0; - for (TypeMapper argument : arguments) { - String s = argsList.remove(0); - if (argsList.isEmpty()) { - if (guards[guardIndex] != null && guards[guardIndex].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, args.length - 1), s) != GuardResult.ALLOWED) { - return null; - } - return argument.tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s); - } - try { - if (guards[guardIndex] != null && guards[guardIndex].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, index), s) != GuardResult.ALLOWED) { - return null; - } - if (argument.map(commandSender, Arrays.copyOf(args, index), s) == null) { - return null; - } - } catch (Exception e) { - return null; - } - index++; - guardIndex++; - } - if (varArgType != null && !argsList.isEmpty()) { - while (!argsList.isEmpty()) { - String s = argsList.remove(0); - if (argsList.isEmpty()) { - if (guards[guards.length - 1] != null && guards[guards.length - 1].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, args.length - 1), s) != GuardResult.ALLOWED) { - return null; - } - return arguments[arguments.length - 1].tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s); - } - try { - if (guards[guards.length - 1] != null && guards[guards.length - 1].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, index), s) != GuardResult.ALLOWED) { - return null; - } - if (arguments[arguments.length - 1].map(commandSender, Arrays.copyOf(args, index), s) == null) { - return null; - } - } catch (Exception e) { - return null; - } - index++; - } - } - return null; + List list = new ArrayList<>(); + commandPart.generateTabComplete(list, commandSender, args, 0); + return list; } } diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java index f8ba72a..daa72fd 100644 --- a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java @@ -54,6 +54,7 @@ public class SimpleCommandTest { try { simpleCommand.execute(new TestCommandSender(), "", new String[]{"unknown"}); } catch (SecurityException securityException) { + securityException.printStackTrace(); assert false; } }