diff --git a/SpigotCore_Main/build.gradle b/SpigotCore_Main/build.gradle index 1eb2599..faeb028 100644 --- a/SpigotCore_Main/build.gradle +++ b/SpigotCore_Main/build.gradle @@ -40,6 +40,16 @@ sourceSets { exclude '**/*.java', '**/*.kt' } } + + test { + java { + srcDirs = ['testsrc'] + } + resources { + srcDirs = ['testsrc'] + exclude '**/*.java', '**/*.kt' + } + } } dependencies { @@ -54,6 +64,12 @@ dependencies { testCompileOnly 'org.projectlombok:lombok:1.18.22' annotationProcessor 'org.projectlombok:lombok:1.18.22' testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' + + testImplementation files("${project.rootDir}/lib/Spigot-1.15.jar") + testImplementation files("${project.rootDir}/lib/WorldEdit-1.12.jar") + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.hamcrest:hamcrest:2.2' } processResources { diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java b/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java index 41b487b..e3d476a 100644 --- a/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java +++ b/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java @@ -21,6 +21,5 @@ package de.steamwar.command; class CommandNoHelpException extends RuntimeException { - CommandNoHelpException() { - } + CommandNoHelpException() {} } diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java b/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java index 21e68bb..3d81ea6 100644 --- a/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java +++ b/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java @@ -19,7 +19,7 @@ package de.steamwar.command; -public class CommandParseException extends Exception { +public class CommandParseException extends RuntimeException { public CommandParseException() { } diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandPart.java b/SpigotCore_Main/src/de/steamwar/command/CommandPart.java new file mode 100644 index 0000000..65ff631 --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/command/CommandPart.java @@ -0,0 +1,213 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import lombok.AllArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.bukkit.command.CommandSender; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; + +@ToString +public class CommandPart { + private static final String[] EMPTY_ARRAY = new String[0]; + + @AllArgsConstructor + private static class CheckArgumentResult { + private final boolean success; + private final Object value; + } + + private TypeMapper typeMapper; + private GuardChecker guard; + private Class varArgType; + private String optional; + private GuardCheckType guardCheckType; + + private CommandPart next = null; + + @Setter + private boolean ignoreAsArgument = false; + + public CommandPart(TypeMapper typeMapper, GuardChecker guard, Class varArgType, String optional, GuardCheckType guardCheckType) { + this.typeMapper = typeMapper; + this.guard = guard; + this.varArgType = varArgType; + this.optional = optional; + this.guardCheckType = guardCheckType; + + validatePart(); + } + + public void setNext(CommandPart next) { + if (varArgType != null) { + throw new IllegalArgumentException("There can't be a next part if this is a vararg part!"); + } + this.next = next; + } + + private void validatePart() { + if (guardCheckType == GuardCheckType.TAB_COMPLETE) { + throw new IllegalArgumentException("Tab complete is not allowed as a guard check type!"); + } + if (optional != null && varArgType != null) { + throw new IllegalArgumentException("A vararg part can't have an optional part!"); + } + + if (optional != null) { + try { + typeMapper.map(null, EMPTY_ARRAY, optional); + } catch (Exception e) { + throw new IllegalArgumentException("The optional part is not valid!"); + } + } + } + + public void generateArgumentArray(List current, CommandSender commandSender, String[] args, int startIndex) { + if (varArgType != null) { + Object array = Array.newInstance(varArgType, args.length - startIndex); + for (int i = startIndex; i < args.length; i++) { + CheckArgumentResult validArgument = checkArgument(null, commandSender, args, i); + if (!validArgument.success) { + throw new CommandParseException(); + } + Array.set(array, i - startIndex, validArgument.value); + } + current.add(array); + return; + } + + CheckArgumentResult validArgument = checkArgument(null, commandSender, args, startIndex); + if (!validArgument.success && optional == null) { + throw new CommandParseException(); + } + if (!validArgument.success) { + if (!ignoreAsArgument) { + current.add(typeMapper.map(commandSender, EMPTY_ARRAY, optional)); + } + if (next != null) { + next.generateArgumentArray(current, commandSender, args, startIndex); + } + return; + } + if (!ignoreAsArgument) { + current.add(validArgument.value); + } + if (next != null) { + next.generateArgumentArray(current, commandSender, args, startIndex + 1); + } + } + + public boolean guardCheck(CommandSender commandSender, String[] args, int startIndex) { + if (varArgType != null) { + for (int i = startIndex; i < args.length; i++) { + GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, i); + if (guardResult == GuardResult.DENIED) { + throw new CommandNoHelpException(); + } + if (guardResult == GuardResult.DENIED_WITH_HELP) { + return false; + } + } + return true; + } + + GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, startIndex); + if (guardResult == GuardResult.DENIED) { + if (optional != null && next != null) { + return next.guardCheck(commandSender, args, startIndex); + } + throw new CommandNoHelpException(); + } + if (guardResult == GuardResult.DENIED_WITH_HELP) { + if (optional != null && next != null) { + return next.guardCheck(commandSender, args, startIndex); + } + return false; + } + if (next != null) { + return next.guardCheck(commandSender, args, startIndex + 1); + } + return true; + } + + public void generateTabComplete(List current, CommandSender commandSender, String[] args, int startIndex) { + if (varArgType != null) { + for (int i = startIndex; i < args.length - 1; i++) { + CheckArgumentResult validArgument = checkArgument(null, commandSender, args, i); + if (!validArgument.success) { + return; + } + } + List strings = typeMapper.tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), args[args.length - 1]); + if (strings != null) { + current.addAll(strings); + } + return; + } + + if (args.length - 1 > startIndex) { + CheckArgumentResult checkArgumentResult = checkArgument(GuardCheckType.TAB_COMPLETE, commandSender, args, startIndex); + if (checkArgumentResult.success && next != null) { + next.generateTabComplete(current, commandSender, args, startIndex + 1); + } + return; + } + + List strings = typeMapper.tabCompletes(commandSender, Arrays.copyOf(args, startIndex), args[startIndex]); + if (strings != null) { + current.addAll(strings); + } + if (optional != null && next != null) { + next.generateTabComplete(current, commandSender, args, startIndex); + } + } + + private CheckArgumentResult checkArgument(GuardCheckType guardCheckType, CommandSender commandSender, String[] args, int index) { + try { + Object value = typeMapper.map(commandSender, Arrays.copyOf(args, index), args[index]); + if (value == null) { + return new CheckArgumentResult(false, null); + } + GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, index); + switch (guardResult) { + case ALLOWED: + return new CheckArgumentResult(true, value); + case DENIED: + throw new CommandNoHelpException(); + case DENIED_WITH_HELP: + default: + return new CheckArgumentResult(false, null); + } + } catch (Exception e) { + return new CheckArgumentResult(false, null); + } + } + + private GuardResult checkGuard(GuardCheckType guardCheckType, CommandSender commandSender, String[] args, int index) { + if (guard != null && guardCheckType != null) { + return guard.guard(commandSender, guardCheckType, Arrays.copyOf(args, index), args[index]); + } + return GuardResult.ALLOWED; + } +} diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java b/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java new file mode 100644 index 0000000..143ea2a --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java @@ -0,0 +1,65 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; + +import java.lang.reflect.Field; +import java.util.Map; + +@UtilityClass +class CommandRegistering { + + private static final CommandMap commandMap; + private static final Map knownCommandMap; + + static { + try { + final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + commandMapField.setAccessible(true); + commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); + } catch (NoSuchFieldException | IllegalAccessException exception) { + Bukkit.shutdown(); + throw new SecurityException("Oh shit. Commands cannot be registered.", exception); + } + try { + final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + knownCommandsField.setAccessible(true); + knownCommandMap = (Map) knownCommandsField.get(commandMap); + } catch (NoSuchFieldException | IllegalAccessException exception) { + Bukkit.shutdown(); + throw new SecurityException("Oh shit. Commands cannot be registered.", exception); + } + } + + static void unregister(Command command) { + knownCommandMap.remove(command.getName()); + command.getAliases().forEach(knownCommandMap::remove); + command.unregister(commandMap); + } + + static void register(Command command) { + commandMap.register("steamwar", command); + } +} diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java index 1783303..a87dfd7 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java @@ -61,13 +61,7 @@ public abstract class SWCommand { if (!initialized) { createMapping(); } - try { - if (!commandList.stream().anyMatch(s -> s.invoke(sender, args))) { - commandHelpList.stream().anyMatch(s -> s.invoke(sender, args)); - } - } catch (CommandNoHelpException e) { - // Ignored - } + SWCommand.this.execute(sender, alias, args); return false; } @@ -76,21 +70,52 @@ public abstract class SWCommand { if (!initialized) { createMapping(); } - String string = args[args.length - 1].toLowerCase(); - return commandList.stream() - .filter(s -> !s.noTabComplete) - .map(s -> s.tabComplete(sender, args)) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .filter(s -> !s.isEmpty()) - .filter(s -> s.toLowerCase().startsWith(string)) - .collect(Collectors.toList()); + return SWCommand.this.tabComplete(sender, alias, args); } }; unregister(); register(); } + // This is used for the tests! + SWCommand(boolean noRegister, String command, String... aliases) { + this.command = new Command(command, "", "/" + command, Arrays.asList(aliases)) { + @Override + public boolean execute(CommandSender sender, String alias, String[] args) { + SWCommand.this.execute(sender, alias, args); + return false; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return SWCommand.this.tabComplete(sender, alias, args); + } + }; + createMapping(); + } + + void execute(CommandSender sender, String alias, String[] args) { + try { + if (!commandList.stream().anyMatch(s -> s.invoke(sender, args))) { + commandHelpList.stream().anyMatch(s -> s.invoke(sender, args)); + } + } catch (CommandNoHelpException e) { + // Ignored + } + } + + List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + String string = args[args.length - 1].toLowerCase(); + return commandList.stream() + .filter(s -> !s.noTabComplete) + .map(s -> s.tabComplete(sender, args)) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(s -> !s.isEmpty()) + .filter(s -> s.toLowerCase().startsWith(string)) + .collect(Collectors.toList()); + } + private synchronized void createMapping() { List methods = methods(); for (Method method : methods) { @@ -149,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) -> { @@ -219,13 +243,11 @@ public abstract class SWCommand { } public void unregister() { - SWCommandUtils.knownCommandMap.remove(command.getName()); - command.getAliases().forEach(SWCommandUtils.knownCommandMap::remove); - command.unregister(SWCommandUtils.commandMap); + CommandRegistering.unregister(command); } public void register() { - SWCommandUtils.commandMap.register("steamwar", this.command); + CommandRegistering.register(command); } @Register(help = true) @@ -332,6 +354,15 @@ public abstract class SWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) protected @interface StaticValue { - String[] value() default {}; + String[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface OptionalValue { + /** + * Will pe parsed against the TypeMapper specified by the parameter or annotation. + */ + String value(); } } diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java index dbe616a..265f874 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java @@ -19,20 +19,17 @@ package de.steamwar.command; +import de.steamwar.sql.BauweltMember; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; import org.bukkit.Bukkit; import org.bukkit.GameMode; -import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; -import org.bukkit.command.SimpleCommandMap; import org.bukkit.entity.Player; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -47,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))); @@ -75,13 +63,39 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(SteamwarUser.class.getTypeName(), createMapper(SteamwarUser::get, s -> Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()))); MAPPER_FUNCTIONS.put(SchematicNode.class.getTypeName(), new TypeMapper() { @Override - public List tabCompletes(CommandSender commandSender, String[] strings, String s) { - return SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); + public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) { + return SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); } @Override - public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) { - return SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); + public List tabCompletes(CommandSender commandSender, String[] strings, String s) { + return SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); + } + }); + MAPPER_FUNCTIONS.put(BauweltMember.class.getTypeName(), new TypeMapper() { + @Override + public BauweltMember map(CommandSender commandSender, String[] previousArguments, String s) { + if (!(commandSender instanceof Player)) { + return null; + } + Player player = (Player) commandSender; + return BauweltMember.getMembers(player.getUniqueId()) + .stream() + .filter(member -> SteamwarUser.get(member.getMemberID()).getUserName().equalsIgnoreCase(s)) + .findAny() + .orElse(null); + } + + @Override + public List tabCompletes(CommandSender commandSender, String[] previousArguments, String s) { + if (!(commandSender instanceof Player)) { + return new ArrayList<>(); + } + Player player = (Player) commandSender; + return BauweltMember.getMembers(player.getUniqueId()) + .stream() + .map(m -> SteamwarUser.get(m.getMemberID()).getUserName()) + .collect(Collectors.toList()); } }); } @@ -91,60 +105,99 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - static final CommandMap commandMap; - static final Map knownCommandMap; - - static { - try { - final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - commandMapField.setAccessible(true); - commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } - try { - final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); - knownCommandsField.setAccessible(true); - knownCommandMap = (Map) knownCommandsField.get(commandMap); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } - } - - 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; - while (index < subCommand.length) { - if (!args[index].equalsIgnoreCase(subCommand[index])) throw new CommandParseException(); - index++; - } - - int length = 0; - if (varArgType != null) { - length = args.length - parameters.length - subCommand.length + 1; - arguments[arguments.length - 1] = Array.newInstance(varArgType, length); - if (index > args.length - 1) return arguments; - } - - for (int i = 0; i < parameters.length - (varArgType != null ? 1 : 0); i++) { - arguments[i + 1] = parameters[i].map(commandSender, Arrays.copyOf(args, index), args[index]); - index++; - if (arguments[i + 1] == null) throw new CommandParseException(); - } - - if (varArgType != null) { - Object varArgument = arguments[arguments.length - 1]; - - for (int i = 0; i < length; i++) { - Object value = parameters[parameters.length - 1].map(commandSender, Arrays.copyOf(args, index), args[index]); - if (value == null) throw new CommandParseException(); - Array.set(varArgument, i, value); - index++; + static CommandPart generateCommandPart(boolean help, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map localGuardChecker) { + CommandPart first = null; + CommandPart current = null; + for (String s : subCommand) { + CommandPart commandPart = new CommandPart(createMapper(s), null, null, null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); + commandPart.setIgnoreAsArgument(true); + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + if (first == null) { + first = current; } } - return arguments; + 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; + if (first == null) { + first = current; + } + } + return first; + } + + static TypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); + } + + SWCommand.ClassMapper classMapper = parameter.getAnnotation(SWCommand.ClassMapper.class); + SWCommand.Mapper mapper = parameter.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 = parameter.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 = parameter.getAnnotation(SWCommand.Guard.class); + if (guard != null) { + if (guard.value() != null && !guard.value().isEmpty()) { + 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; } public static void addMapper(Class clazz, TypeMapper mapper) { @@ -186,10 +239,35 @@ public class SWCommandUtils { }; } + public static TypeMapper> createEnumMapper(Class> enumClass) { + Enum[] enums = enumClass.getEnumConstants(); + List strings = Arrays.stream(enums).map(Enum::name).map(String::toLowerCase).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 -> { + if (s.equalsIgnoreCase("nan")) return null; try { return mapper.apply(s); + } catch (NumberFormatException e) { + // Ignored + } + try { + return mapper.apply(s.replace(',', '.')); } catch (NumberFormatException e) { return null; } diff --git a/SpigotCore_Main/src/de/steamwar/command/SubCommand.java b/SpigotCore_Main/src/de/steamwar/command/SubCommand.java index 997cdea..af17e75 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,214 +36,82 @@ 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 : parameters.length) + 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); + objects.add(0, commandSenderFunction.apply(commandSender)); + method.setAccessible(true); + method.invoke(swCommand, objects.toArray()); } - method.setAccessible(true); - method.invoke(swCommand, objects); } catch (CommandNoHelpException e) { throw e; - } catch (IllegalAccessException | RuntimeException | InvocationTargetException e) { - throw new SecurityException(e.getMessage(), e); } catch (CommandParseException e) { return false; + } catch (IllegalAccessException | RuntimeException | InvocationTargetException e) { + throw new SecurityException(e.getMessage(), e); } return true; } 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/src/de/steamwar/command/TypeMapper.java b/SpigotCore_Main/src/de/steamwar/command/TypeMapper.java index dce1882..bc794fb 100644 --- a/SpigotCore_Main/src/de/steamwar/command/TypeMapper.java +++ b/SpigotCore_Main/src/de/steamwar/command/TypeMapper.java @@ -24,6 +24,9 @@ import org.bukkit.command.CommandSender; import java.util.List; public interface TypeMapper { + /** + * The CommandSender can be null! + */ default T map(CommandSender commandSender, String[] previousArguments, String s) { return map(previousArguments, s); } diff --git a/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java b/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java index 1a7ed6f..115367f 100644 --- a/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java +++ b/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java @@ -25,7 +25,9 @@ import org.bukkit.entity.Player; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; public class SteamwarUser { diff --git a/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java b/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java new file mode 100644 index 0000000..02760e4 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java @@ -0,0 +1,122 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar; + +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import java.util.Set; + +public class TestCommandSender implements CommandSender { + + @Override + public void sendMessage(String s) { + + } + + @Override + public void sendMessage(String[] strings) { + + } + + @Override + public Server getServer() { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public Spigot spigot() { + return null; + } + + @Override + public boolean isPermissionSet(String s) { + return false; + } + + @Override + public boolean isPermissionSet(Permission permission) { + return false; + } + + @Override + public boolean hasPermission(String s) { + return false; + } + + @Override + public boolean hasPermission(Permission permission) { + return false; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int i) { + return null; + } + + @Override + public void removeAttachment(PermissionAttachment permissionAttachment) { + + } + + @Override + public void recalculatePermissions() { + + } + + @Override + public Set getEffectivePermissions() { + return null; + } + + @Override + public boolean isOp() { + return false; + } + + @Override + public void setOp(boolean b) { + + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java b/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java new file mode 100644 index 0000000..e4ed591 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java @@ -0,0 +1,41 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +public class ExecutionIdentifier extends RuntimeException { + public ExecutionIdentifier() { + } + + public ExecutionIdentifier(String message) { + super(message); + } + + public ExecutionIdentifier(String message, Throwable cause) { + super(message, cause); + } + + public ExecutionIdentifier(Throwable cause) { + super(cause); + } + + public ExecutionIdentifier(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java new file mode 100644 index 0000000..79f2844 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import org.bukkit.command.CommandSender; + +public class SimpleCommand extends SWCommand { + + public SimpleCommand() { + super(true, "simple"); + } + + @Register + public void execute(CommandSender sender) { + throw new ExecutionIdentifier("Simple execute without any parameters"); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandPartTest.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandPartTest.java new file mode 100644 index 0000000..a9474a0 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandPartTest.java @@ -0,0 +1,243 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import de.steamwar.TestCommandSender; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class SimpleCommandPartTest { + + private CommandPart stringCommandPart; + private CommandPart intCommandPart; + private CommandPart chainedCommandPart; + private CommandPart varArgCommandPart; + + private CommandPart simpleGuardPart; + + private CommandPart optionalCommandPart; + + @Before + public void setUp() throws Exception { + stringCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, null, GuardCheckType.COMMAND); + intCommandPart = new CommandPart(SWCommandUtils.MAPPER_FUNCTIONS.get("int"), null, null, null, GuardCheckType.COMMAND); + + chainedCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, null, GuardCheckType.COMMAND); + chainedCommandPart.setNext(new CommandPart(SWCommandUtils.MAPPER_FUNCTIONS.get("int"), null, null, null, GuardCheckType.COMMAND)); + + varArgCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, String.class, null, GuardCheckType.COMMAND); + + simpleGuardPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), (commandSender, guardCheckType, previousArguments, s) -> s.equals("hello") ? GuardResult.DENIED : GuardResult.ALLOWED, null, null, GuardCheckType.COMMAND); + + optionalCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, "hello", GuardCheckType.COMMAND); + optionalCommandPart.setNext(new CommandPart(SWCommandUtils.createMapper("hello2", "world2"), null, null, null, GuardCheckType.COMMAND)); + } + + @Test + public void testCommandPartTabCompleteNoArguments() { + List tabComplete = new ArrayList<>(); + stringCommandPart.generateTabComplete(tabComplete, new TestCommandSender(), new String[]{""}, 0); + assertThat(tabComplete.size(), is(2)); + assertThat(tabComplete.get(0), is("hello")); + assertThat(tabComplete.get(1), is("world")); + } + + @Test(expected = CommandParseException.class) + public void testCommandExecuteInvalidArgument() { + stringCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{""}, 0); + } + + @Test + public void testCommandExecuteValidArgument() { + List argumentArray = new ArrayList<>(); + stringCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String.class)); + assertThat(argumentArray.get(0), is("hello")); + } + + @Test + public void testCommandExecuteValidOtherArgument() { + List argumentArray = new ArrayList<>(); + stringCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String.class)); + assertThat(argumentArray.get(0), is("world")); + } + + @Test(expected = CommandParseException.class) + public void testCommandExecuteNonNumberArgument() { + intCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{"world"}, 0); + } + + @Test + public void testCommandExecuteValidNumberArgument() { + List argumentArray = new ArrayList<>(); + intCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"0"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(int.class)); + assertThat(argumentArray.get(0), is(0)); + } + + @Test + public void testChainedCommandExecuteValidArgument() { + List argumentArray = new ArrayList<>(); + chainedCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello", "0"}, 0); + assertThat(argumentArray.size(), is(2)); + assertThat(argumentArray.get(0), instanceOf(String.class)); + assertThat(argumentArray.get(0), is("hello")); + assertThat(argumentArray.get(1), instanceOf(int.class)); + assertThat(argumentArray.get(1), is(0)); + } + + @Test + public void testChainedCommandTabComplete() { + List tabCompletes = new ArrayList<>(); + chainedCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{""}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + } + + @Test + public void testChainedCommandTabCompleteOther() { + List tabCompletes = new ArrayList<>(); + chainedCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", ""}, 0); + assertThat(tabCompletes.size(), is(0)); + } + + @Test + public void testVarArgsCommandTabComplete() { + List tabCompletes = new ArrayList<>(); + varArgCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + } + + @Test + public void testVarArgsCommandTabCompleteDeeper() { + List tabCompletes = new ArrayList<>(); + varArgCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", "world", "hello", "world"}, 0); + System.out.println(tabCompletes); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + } + + @Test + public void testVarArgsCommandArgumentParsing() { + List argumentArray = new ArrayList<>(); + varArgCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String[].class)); + assertThat((String[]) argumentArray.get(0), arrayWithSize(1)); + assertThat((String[]) argumentArray.get(0), is(new String[]{"hello"})); + } + + @Test + public void testVarArgsCommandArgumentParsingDeeper() { + List argumentArray = new ArrayList<>(); + varArgCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello", "world", "hello", "world"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String[].class)); + assertThat((String[]) argumentArray.get(0), arrayWithSize(4)); + assertThat((String[]) argumentArray.get(0), is(new String[]{"hello", "world", "hello", "world"})); + } + + @Test + public void testGuardCommandExecute() { + List argumentArray = new ArrayList<>(); + simpleGuardPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(argumentArray.size(), is(1)); + } + + @Test(expected = CommandNoHelpException.class) + public void testGuardGuardCheck() { + simpleGuardPart.guardCheck(new TestCommandSender(), new String[]{"hello"}, 0); + } + + @Test + public void testGuardCommandExecuteValid() { + List argumentArray = new ArrayList<>(); + simpleGuardPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world"}, 0); + assertThat(argumentArray.size(), is(1)); + } + + @Test + public void testGuardGuardCheckValid() { + boolean guardResult = simpleGuardPart.guardCheck(new TestCommandSender(), new String[]{"world"}, 0); + assertThat(guardResult, is(true)); + } + + @Test + public void testOptionalCommandPartTabComplete() { + List tabCompletes = new ArrayList<>(); + optionalCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{""}, 0); + assertThat(tabCompletes.size(), is(4)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + assertThat(tabCompletes.get(2), is("hello2")); + assertThat(tabCompletes.get(3), is("world2")); + } + + @Test + public void testOptionalCommandPartTabCompleteSecond() { + List tabCompletes = new ArrayList<>(); + optionalCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", ""}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello2")); + assertThat(tabCompletes.get(1), is("world2")); + } + + @Test(expected = CommandParseException.class) + public void testOptionalCommandPartExecution() { + optionalCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{""}, 0); + } + + @Test + public void testOptionalCommandPartExecutionValid() { + List argumentArray = new ArrayList<>(); + optionalCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello2"}, 0); + assertThat(argumentArray.size(), is(2)); + assertThat(argumentArray.get(0), is("hello")); + assertThat(argumentArray.get(1), is("hello2")); + } + + @Test(expected = CommandParseException.class) + public void testOptionalCommandPartExecutionInvalid() { + optionalCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{"hello"}, 0); + } + + @Test + public void testOptionalCommandPartExecutionFullyValid() { + List argumentArray = new ArrayList<>(); + optionalCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world", "hello2"}, 0); + assertThat(argumentArray.size(), is(2)); + assertThat(argumentArray.get(0), is("world")); + assertThat(argumentArray.get(1), is("hello2")); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java new file mode 100644 index 0000000..daa72fd --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java @@ -0,0 +1,61 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import de.steamwar.TestCommandSender; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SimpleCommandTest { + + private SimpleCommand simpleCommand; + + @Before + public void setUp() throws Exception { + simpleCommand = new SimpleCommand(); + } + + @Test + public void testCommandParsing() { + try { + simpleCommand.execute(new TestCommandSender(), "", new String[]{}); + } catch (SecurityException securityException) { + if (securityException.getCause().getCause() instanceof ExecutionIdentifier) { + ExecutionIdentifier executionIdentifier = (ExecutionIdentifier) securityException.getCause().getCause(); + assertThat(executionIdentifier.getMessage(), is("Simple execute without any parameters")); + return; + } + } + assert false; + } + + @Test + public void testUnknownCommandParsing() { + try { + simpleCommand.execute(new TestCommandSender(), "", new String[]{"unknown"}); + } catch (SecurityException securityException) { + securityException.printStackTrace(); + assert false; + } + } +} diff --git a/steamwarci.yml b/steamwarci.yml index d3e2564..e630fae 100644 --- a/steamwarci.yml +++ b/steamwarci.yml @@ -3,6 +3,7 @@ build: - "cp ~/gradle.properties ." - "chmod u+x build.gradle" - "./gradlew buildProject" + - "./gradlew test" artifacts: "/binarys/spigotcore.jar": "build/libs/spigotcore.jar"