diff --git a/CommandFramework/build.gradle.kts b/CommandFramework/build.gradle.kts new file mode 100644 index 00000000..880a4451 --- /dev/null +++ b/CommandFramework/build.gradle.kts @@ -0,0 +1,62 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 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 . + */ + +plugins { + id("base") + id("java") +} + +group = "de.steamwar" +version = "" + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_21 +} + +sourceSets { + main { + java { + srcDirs("src/") + } + resources { + srcDirs("src/") + exclude("**/*.java", "**/*.kt") + } + } + test { + java { + srcDirs("testsrc/") + } + resources { + srcDirs("testsrc/") + exclude("**/*.java", "**/*.kt") + } + } +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + testCompileOnly("org.projectlombok:lombok:1.18.32") + testAnnotationProcessor("org.projectlombok:lombok:1.18.32") + + testImplementation("junit:junit:4.13.2") + testImplementation("org.hamcrest:hamcrest:2.2") +} \ No newline at end of file diff --git a/CommandFramework/src/de/steamwar/command/AbstractSWCommand.java b/CommandFramework/src/de/steamwar/command/AbstractSWCommand.java new file mode 100644 index 00000000..65f6643c --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/AbstractSWCommand.java @@ -0,0 +1,711 @@ +/* + * 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 java.lang.annotation.*; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public abstract class AbstractSWCommand { + + private static final Map>, List>> dependencyMap = new HashMap<>(); + + private Class clazz; // This is used in createMappings() + + private boolean initialized = false; + protected final List> commandList = new ArrayList<>(); + protected final List> helpCommandList = new ArrayList<>(); + + private final Map> localTypeMapper = new HashMap<>(); + private final Map> localValidators = new HashMap<>(); + + protected AbstractSWCommand(Class clazz, String command) { + this(clazz, command, new String[0]); + } + + protected AbstractSWCommand(Class clazz, String command, String... aliases) { + this.clazz = clazz; + + PartOf partOf = this.getClass().getAnnotation(PartOf.class); + if (partOf != null) { + dependencyMap.computeIfAbsent((Class>) partOf.value(), k -> new ArrayList<>()).add(this); + return; + } + + createAndSafeCommand(command, aliases); + unregister(); + register(); + } + + protected abstract void createAndSafeCommand(String command, String[] aliases); + + public abstract void unregister(); + + public abstract void register(); + + protected void commandSystemError(T sender, CommandFrameworkException e) { + e.printStackTrace(); + } + + protected void commandSystemWarning(Supplier message) { + System.out.println(message.get()); + } + + protected void sendMessage(T sender, String message, Object[] args) { + } + + protected void initialisePartOf(AbstractSWCommand parent) { + } + + protected final void execute(T sender, String alias, String[] args) { + initialize(); + List errors = new ArrayList<>(); + try { + if (commandList.stream().noneMatch(s -> s.invoke(errors::add, sender, alias, args))) { + errors.forEach(Runnable::run); + } else return; + if (errors.isEmpty() && helpCommandList.stream().noneMatch(s -> s.invoke(errors::add, sender, alias, args))) { + errors.forEach(Runnable::run); + } + } catch (CommandFrameworkException e) { + commandSystemError(sender, e); + throw e; + } + } + + protected final List tabComplete(T sender, String alias, String[] args) throws IllegalArgumentException { + initialize(); + String string = args[args.length - 1].toLowerCase(); + return Stream.concat(commandList.stream(), helpCommandList.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) || string.startsWith(s.toLowerCase())) + .distinct() + .collect(Collectors.toList()); + } + + private synchronized void initialize() { + if (initialized) return; + List methods = methods().stream() + .filter(this::validateMethod) + .collect(Collectors.toList()); + for (Method method : methods) { + Cached cached = method.getAnnotation(Cached.class); + this.>add(Mapper.class, method, (anno, typeMapper) -> { + TabCompletionCache.add(typeMapper, cached); + (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value(), typeMapper); + }); + this.>add(ClassMapper.class, method, (anno, typeMapper) -> { + TabCompletionCache.add(typeMapper, cached); + (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value().getName(), typeMapper); + }); + this.>add(Validator.class, method, (anno, validator) -> { + (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value(), validator); + }); + this.>add(ClassValidator.class, method, (anno, validator) -> { + (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value().getName(), validator); + }); + } + for (Method method : methods) { + add(Register.class, method, true, (anno, parameters) -> { + for (int i = 1; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) clazz = clazz.getComponentType(); + Mapper mapper = parameter.getAnnotation(Mapper.class); + if (clazz.isEnum() && mapper == null && !SWCommandUtils.getMAPPER_FUNCTIONS().containsKey(clazz.getTypeName())) { + continue; + } + String name = mapper != null ? mapper.value() : clazz.getTypeName(); + if (!SWCommandUtils.getMAPPER_FUNCTIONS().containsKey(name) && !localTypeMapper.containsKey(name)) { + commandSystemWarning(() -> "The parameter '" + parameter.toString() + "' is using an unsupported Mapper of type '" + name + "'"); + return; + } + } + commandList.add(new SubCommand<>(this, method, anno.value(), localTypeMapper, localValidators, anno.description(), anno.noTabComplete())); + }); + } + + if (dependencyMap.containsKey(this.getClass())) { + dependencyMap.get(this.getClass()).forEach(abstractSWCommand -> { + abstractSWCommand.localTypeMapper.putAll((Map) localTypeMapper); + abstractSWCommand.localValidators.putAll((Map) localValidators); + abstractSWCommand.initialisePartOf(this); + abstractSWCommand.initialize(); + commandList.addAll((Collection) abstractSWCommand.commandList); + }); + } + + Collections.sort(commandList); + commandList.removeIf(subCommand -> { + if (subCommand.isHelp) { + helpCommandList.add(subCommand); + return true; + } else { + return false; + } + }); + initialized = true; + } + + private boolean validateMethod(Method method) { + if (!checkType(method.getAnnotations(), method.getReturnType(), false, annotation -> { + CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class); + if (methodMetaData == null) return (aClass, varArg) -> true; + if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) + return (aClass, varArg) -> false; + return (aClass, varArg) -> { + Class[] types = methodMetaData.value(); + if (types == null) return true; + for (Class type : types) { + if (type.isAssignableFrom(aClass)) return true; + } + return false; + }; + }, "The method '" + method + "'")) return false; + boolean valid = true; + for (Parameter parameter : method.getParameters()) { + if (!checkType(parameter.getAnnotations(), parameter.getType(), parameter.isVarArgs(), annotation -> { + CommandMetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Parameter.class); + if (parameterMetaData == null) return (aClass, varArg) -> true; + Class handler = parameterMetaData.handler(); + if (BiPredicate.class.isAssignableFrom(handler)) { + try { + return (BiPredicate, Boolean>) handler.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + } + } + return (aClass, varArg) -> { + if (varArg) aClass = aClass.getComponentType(); + Class[] types = parameterMetaData.value(); + if (types == null) return true; + for (Class current : types) { + if (current.isAssignableFrom(aClass)) return true; + } + return false; + }; + }, "The parameter '" + parameter + "'")) valid = false; + } + return valid; + } + + private boolean checkType(Annotation[] annotations, Class clazz, boolean varArg, Function, Boolean>> toApplicableTypes, String warning) { + boolean valid = true; + for (Annotation annotation : annotations) { + BiPredicate, Boolean> predicate = toApplicableTypes.apply(annotation); + if (!predicate.test(clazz, varArg)) { + commandSystemWarning(() -> warning + " is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); + valid = false; + } + } + return valid; + } + + private void add(Class annotation, Method method, boolean firstParameter, BiConsumer consumer) { + T[] anno = SWCommandUtils.getAnnotation(method, annotation); + if (anno == null || anno.length == 0) return; + + Parameter[] parameters = method.getParameters(); + if (firstParameter && !clazz.isAssignableFrom(parameters[0].getType())) { + commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the first parameter of type '" + clazz.getTypeName() + "'"); + return; + } + Arrays.stream(anno).forEach(t -> consumer.accept(t, parameters)); + } + + private void add(Class annotation, Method method, BiConsumer consumer) { + add(annotation, method, false, (anno, parameters) -> { + try { + method.setAccessible(true); + consumer.accept(anno, (K) method.invoke(this)); + } catch (Exception e) { + throw new SecurityException(e.getMessage(), e); + } + }); + } + + // TODO: Implement this when Message System is ready + /* + public void addDefaultHelpMessage(String message) { + defaultHelpMessages.add(message); + } + */ + + private List methods() { + List methods = new ArrayList<>(); + Class current = getClass(); + while (current != AbstractSWCommand.class) { + methods.addAll(Arrays.asList(current.getDeclaredMethods())); + current = current.getSuperclass(); + } + return methods; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE}) + public @interface PartOf { + Class value(); + } + + // --- Annotation for the command --- + + /** + * Annotation for registering a method as a command + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @Repeatable(Register.Registeres.class) + @CommandMetaData.Method(value = void.class, minParameterCount = 1) + protected @interface Register { + + /** + * Identifier of subcommand + */ + String[] value() default {}; + + @Deprecated + boolean help() default false; + + String[] description() default {}; + + boolean noTabComplete() default false; + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @CommandMetaData.Method(value = void.class, minParameterCount = 1) + @interface Registeres { + Register[] value(); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER, ElementType.METHOD}) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.ImplicitTypeMapper(handler = Mapper.Handler.class) + protected @interface Mapper { + String value(); + + boolean local() default false; + + class Handler implements AbstractTypeMapper { + + private AbstractTypeMapper inner; + + public Handler(AbstractSWCommand.Mapper mapper, Map> localTypeMapper) { + inner = (AbstractTypeMapper) SWCommandUtils.getTypeMapper(mapper.value(), localTypeMapper); + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + return inner.map(sender, previousArguments, s); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + return inner.validate(sender, value, messageSender); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + return inner.tabCompletes(sender, previousArguments, s); + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + protected @interface ClassMapper { + Class value(); + + boolean local() default false; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + protected @interface Cached { + long cacheDuration() default 5; + + TimeUnit timeUnit() default TimeUnit.SECONDS; + + boolean global() default false; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER, ElementType.METHOD}) + @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) + @CommandMetaData.ImplicitValidator(handler = Validator.Handler.class, order = 0) + protected @interface Validator { + String value() default ""; + + boolean local() default false; + + boolean invert() default false; + + class Handler implements AbstractValidator { + + private AbstractValidator inner; + private boolean invert; + + public Handler(AbstractSWCommand.Validator validator, Class clazz, Map> localValidator) { + inner = (AbstractValidator) SWCommandUtils.getValidator(validator, clazz, localValidator); + invert = validator.invert(); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + return inner.validate(sender, value, messageSender) ^ invert; + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) + protected @interface ClassValidator { + Class value(); + + boolean local() default false; + } + + // --- Implicit TypeMapper --- + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) + @CommandMetaData.ImplicitTypeMapper(handler = StaticValue.Handler.class) + protected @interface StaticValue { + String[] value(); + + /** + * This is the short form for 'allowImplicitSwitchExpressions' + * and can be set to true if you want to allow int as well as boolean as annotated parameter types. + * The value array needs to be at least 2 long for this flag to be considered. + * While using an int, the value will represent the index into the value array. + * While using a boolean, the {@link #falseValues()} defines which indices are + * considered {@code false} or {@code true}. + */ + boolean allowISE() default false; + + int[] falseValues() default {0}; + + class Handler implements AbstractTypeMapper { + + private AbstractTypeMapper inner; + + public Handler(StaticValue staticValue, Class clazz) { + if (clazz == String.class) { + inner = SWCommandUtils.createMapper(staticValue.value()); + return; + } + if (!staticValue.allowISE()) { + throw new IllegalArgumentException("The parameter type '" + clazz.getTypeName() + "' is not supported by the StaticValue annotation"); + } + if (clazz == boolean.class || clazz == Boolean.class) { + List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); + Set falseValues = new HashSet<>(); + for (int i : staticValue.falseValues()) falseValues.add(i); + inner = SWCommandUtils.createMapper(s -> { + int index = tabCompletes.indexOf(s); + return index == -1 ? null : !falseValues.contains(index); + }, (commandSender, s) -> tabCompletes); + } else if (clazz == int.class || clazz == Integer.class || clazz == long.class || clazz == Long.class) { + List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); + inner = SWCommandUtils.createMapper(s -> { + Number index = tabCompletes.indexOf(s); + return index.longValue() == -1 ? null : index; + }, (commandSender, s) -> tabCompletes); + } else { + throw new IllegalArgumentException("The parameter type '" + clazz.getTypeName() + "' is not supported by the StaticValue annotation"); + } + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + return inner.map(sender, previousArguments, s); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + return inner.tabCompletes(sender, previousArguments, s); + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface OptionalValue { + /** + * Will pe parsed against the TypeMapper specified by the parameter or annotation. + */ + String value(); + + /** + * The method name stands for 'onlyUseIfNoneIsGiven'. + */ + boolean onlyUINIG() default false; + } + + // --- Implicit Validator --- + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.ImplicitValidator(handler = ErrorMessage.Handler.class, order = Integer.MAX_VALUE) + protected @interface ErrorMessage { + /** + * Error message to be displayed when the parameter is invalid. + */ + String value(); + + /** + * This is the short form for 'allowEmptyArrays'. + */ + boolean allowEAs() default true; + + class Handler implements AbstractValidator { + + private AbstractSWCommand.ErrorMessage errorMessage; + + public Handler(AbstractSWCommand.ErrorMessage errorMessage) { + this.errorMessage = errorMessage; + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + if (value == null) messageSender.send(errorMessage.value()); + if (!errorMessage.allowEAs() && value != null && value.getClass().isArray() && Array.getLength(value) == 0) { + messageSender.send(errorMessage.value()); + return false; + } + return value != null; + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface AllowNull { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.ImplicitValidator(handler = Min.Handler.class, order = 2) + protected @interface Min { + int intValue() default Integer.MIN_VALUE; + + long longValue() default Long.MIN_VALUE; + + float floatValue() default Float.MIN_VALUE; + + double doubleValue() default Double.MIN_VALUE; + + boolean inclusive() default true; + + class Handler implements AbstractValidator { + + private int value; + private Function comparator; + + public Handler(AbstractSWCommand.Min min, Class clazz) { + this.value = min.inclusive() ? 0 : 1; + this.comparator = createComparator("Min", clazz, min.intValue(), min.longValue(), min.floatValue(), min.doubleValue()); + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + if (value == null) return true; + return (comparator.apply(value).intValue()) >= this.value; + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.ImplicitValidator(handler = Max.Handler.class, order = 2) + protected @interface Max { + int intValue() default Integer.MAX_VALUE; + + long longValue() default Long.MAX_VALUE; + + float floatValue() default Float.MAX_VALUE; + + double doubleValue() default Double.MAX_VALUE; + + boolean inclusive() default true; + + class Handler implements AbstractValidator { + + private int value; + private Function comparator; + + public Handler(AbstractSWCommand.Max max, Class clazz) { + this.value = max.inclusive() ? 0 : -1; + this.comparator = createComparator("Max", clazz, max.intValue(), max.longValue(), max.floatValue(), max.doubleValue()); + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + if (value == null) return true; + return (comparator.apply(value).intValue()) <= this.value; + } + } + } + + private static Function createComparator(String type, Class clazz, int iValue, long lValue, float fValue, double dValue) { + if (clazz == int.class || clazz == Integer.class) { + return number -> Integer.compare(number.intValue(), iValue); + } else if (clazz == long.class || clazz == Long.class) { + return number -> Long.compare(number.longValue(), lValue); + } else if (clazz == float.class || clazz == Float.class) { + return number -> Float.compare(number.floatValue(), fValue); + } else if (clazz == double.class || clazz == Double.class) { + return number -> Double.compare(number.doubleValue(), dValue); + } else { + throw new IllegalArgumentException(type + " annotation is not supported for " + clazz); + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.ImplicitTypeMapper(handler = Length.Handler.class) + protected @interface Length { + int min() default 0; + + int max() default Integer.MAX_VALUE; + + class Handler implements AbstractTypeMapper { + + private int min; + private int max; + private AbstractTypeMapper inner; + + public Handler(Length length, AbstractTypeMapper inner) { + this.min = length.min(); + this.max = length.max(); + this.inner = inner; + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + if (s.length() < min || s.length() > max) return null; + return inner.map(sender, previousArguments, s); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + List tabCompletes = inner.tabCompletes(sender, previousArguments, s) + .stream() + .filter(str -> str.length() >= min) + .map(str -> str.substring(0, Math.min(str.length(), max))) + .collect(Collectors.toList()); + if (s.length() < min) { + tabCompletes.add(0, s); + } + return tabCompletes; + } + + @Override + public String normalize(T sender, String s) { + return inner.normalize(sender, s); + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @CommandMetaData.Parameter(handler = ArrayLength.Type.class) + @CommandMetaData.ImplicitTypeMapper(handler = ArrayLength.HandlerTypeMapper.class) + @CommandMetaData.ImplicitValidator(handler = ArrayLength.HandlerValidator.class, order = 1) + protected @interface ArrayLength { + int min() default 0; + + int max() default Integer.MAX_VALUE; + + class Type implements BiPredicate, Boolean> { + @Override + public boolean test(Class clazz, Boolean isVarArgs) { + return clazz.isArray(); + } + } + + class HandlerTypeMapper implements AbstractTypeMapper { + + private int max; + private AbstractTypeMapper inner; + + public HandlerTypeMapper(ArrayLength arrayLength, AbstractTypeMapper inner) { + this.max = arrayLength.max(); + this.inner = inner; + } + + @Override + public Object map(T sender, PreviousArguments previousArguments, String s) { + return inner.map(sender, previousArguments, s); + } + + @Override + public Collection tabCompletes(T sender, PreviousArguments previousArguments, String s) { + Object[] mapped = previousArguments.getMappedArg(0); + if (mapped.length >= max) return Collections.emptyList(); + return inner.tabCompletes(sender, previousArguments, s); + } + + @Override + public String normalize(T sender, String s) { + return inner.normalize(sender, s); + } + } + + class HandlerValidator implements AbstractValidator { + + private int min; + private int max; + + public HandlerValidator(ArrayLength arrayLength) { + this.min = arrayLength.min(); + this.max = arrayLength.max(); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + if (value == null) return true; + int length = Array.getLength(value); + return length >= min && length <= max; + } + } + } +} diff --git a/CommandFramework/src/de/steamwar/command/AbstractTypeMapper.java b/CommandFramework/src/de/steamwar/command/AbstractTypeMapper.java new file mode 100644 index 00000000..040f3031 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/AbstractTypeMapper.java @@ -0,0 +1,62 @@ +/* + * 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 java.util.Collection; + +public interface AbstractTypeMapper extends AbstractValidator { + /** + * The CommandSender can be null! + */ + @Deprecated + default T map(K sender, String[] previousArguments, String s) { + throw new IllegalArgumentException("This method is deprecated and should not be used anymore!"); + } + + /** + * The CommandSender can be null! + */ + default T map(K sender, PreviousArguments previousArguments, String s) { + return map(sender, previousArguments.userArgs, s); + } + + @Override + default boolean validate(K sender, T value, MessageSender messageSender) { + return true; + } + + @Deprecated + default Collection tabCompletes(K sender, String[] previousArguments, String s) { + throw new IllegalArgumentException("This method is deprecated and should not be used anymore!"); + } + + default Collection tabCompletes(K sender, PreviousArguments previousArguments, String s) { + return tabCompletes(sender, previousArguments.userArgs, s); + } + + /** + * Normalize the cache key by sender and user provided argument.
+ * Note: The CommandSender can be null!
+ * Returning null and the empty string are equivalent. + */ + default String normalize(K sender, String s) { + return null; + } +} diff --git a/CommandFramework/src/de/steamwar/command/AbstractValidator.java b/CommandFramework/src/de/steamwar/command/AbstractValidator.java new file mode 100644 index 00000000..7dc0b72f --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/AbstractValidator.java @@ -0,0 +1,96 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.RequiredArgsConstructor; + +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Predicate; + +@FunctionalInterface +public interface AbstractValidator { + + /** + * Validates the given value. + * + * @param sender The sender of the command. + * @param value The value to validate or null if mapping returned null. + * @param messageSender The message sender to send messages to the player. Never send messages directly to the player. + * @return The result of the validation. + */ + boolean validate(K sender, T value, MessageSender messageSender); + + @Deprecated + default Validator validate(C value, MessageSender messageSender) { + return new Validator<>(value, messageSender); + } + + @Deprecated + @RequiredArgsConstructor + class Validator { + private final C value; + private final MessageSender messageSender; + + private boolean valid = true; + + public Validator map(Function mapper) { + return new Validator<>(mapper.apply(value), messageSender).set(valid); + } + + public Validator set(boolean value) { + this.valid = value; + return this; + } + + public Validator and(Predicate predicate) { + valid &= predicate.test(value); + return this; + } + + public Validator or(Predicate predicate) { + valid |= predicate.test(value); + return this; + } + + public Validator errorMessage(String s, Object... args) { + if (!valid) messageSender.send(s, args); + return this; + } + + public boolean result() { + return valid; + } + } + + @FunctionalInterface + interface MessageSender { + void send(String s, Object... args); + + default boolean send(boolean condition, String s, Object... args) { + if (condition) send(s, args); + return condition; + } + + default boolean send(BooleanSupplier condition, String s, Object... args) { + return send(condition.getAsBoolean(), s, args); + } + } +} diff --git a/CommandFramework/src/de/steamwar/command/CommandFrameworkException.java b/CommandFramework/src/de/steamwar/command/CommandFrameworkException.java new file mode 100644 index 00000000..ea3d8356 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/CommandFrameworkException.java @@ -0,0 +1,110 @@ +/* + * 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 java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.Executable; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.function.Function; +import java.util.stream.Stream; + +public class CommandFrameworkException extends RuntimeException { + + private Function causeMessage; + private Throwable cause; + private Function stackTraceExtractor; + private String extraStackTraces; + + private String message; + + static CommandFrameworkException commandPartExceptions(String type, Throwable cause, String current, Class clazzType, Executable executable, int index) { + return new CommandFrameworkException(e -> { + return CommandFrameworkException.class.getTypeName() + ": Error while " + type + " (" + current + ") to type " + clazzType.getTypeName() + " with parameter index " + index; + }, cause, exception -> { + StackTraceElement[] stackTraceElements = exception.getStackTrace(); + int last = 0; + for (int i = 0; i < stackTraceElements.length; i++) { + if (stackTraceElements[i].getClassName().equals(CommandPart.class.getTypeName())) { + last = i; + break; + } + } + return Arrays.stream(stackTraceElements).limit(last - 1); + }, executable.getDeclaringClass().getTypeName() + "." + executable.getName() + "(Unknown Source)"); + } + + CommandFrameworkException(InvocationTargetException invocationTargetException, String alias, String[] args) { + this(e -> { + StringBuilder st = new StringBuilder(); + st.append(e.getCause().getClass().getTypeName()); + if (e.getCause().getMessage() != null) { + st.append(": ").append(e.getCause().getMessage()); + } + if (alias != null && !alias.isEmpty()) { + st.append("\n").append("Performed command: " + alias + " " + String.join(" ", args)); + } + return st.toString(); + }, invocationTargetException, e -> { + StackTraceElement[] stackTraceElements = e.getCause().getStackTrace(); + return Arrays.stream(stackTraceElements).limit(stackTraceElements.length - e.getStackTrace().length); + }, null); + } + + private CommandFrameworkException(Function causeMessage, T cause, Function> stackTraceExtractor, String extraStackTraces) { + super(causeMessage.apply(cause), cause); + this.causeMessage = causeMessage; + this.cause = cause; + this.stackTraceExtractor = stackTraceExtractor; + this.extraStackTraces = extraStackTraces; + } + + public synchronized String getBuildStackTrace() { + if (message != null) { + return message; + } + StringBuilder st = new StringBuilder(); + st.append(causeMessage.apply(cause)).append("\n"); + ((Stream) stackTraceExtractor.apply(cause)).forEach(stackTraceElement -> { + st.append("\tat ").append(stackTraceElement.toString()).append("\n"); + }); + if (extraStackTraces != null) { + st.append("\tat ").append(extraStackTraces).append("\n"); + } + message = st.toString(); + return message; + } + + @Override + public void printStackTrace() { + printStackTrace(System.err); + } + + @Override + public void printStackTrace(PrintStream s) { + s.print(getBuildStackTrace()); + } + + @Override + public void printStackTrace(PrintWriter s) { + s.print(getBuildStackTrace()); + } +} diff --git a/CommandFramework/src/de/steamwar/command/CommandMetaData.java b/CommandFramework/src/de/steamwar/command/CommandMetaData.java new file mode 100644 index 00000000..33909ac7 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/CommandMetaData.java @@ -0,0 +1,92 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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 java.lang.annotation.*; + +public @interface CommandMetaData { + + /** + * This annotation is only for internal use. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface Method { + Class[] value(); + int minParameterCount() default 0; + int maxParameterCount() default Integer.MAX_VALUE; + } + + /** + * This annotation denotes what types are allowed as parameter types the annotation annotated with can use. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface Parameter { + Class[] value() default {}; + Class handler() default void.class; + } + + /** + * This annotation can be used in conjunction with a class that implements {@link AbstractTypeMapper} to + * create a custom type mapper for a parameter. The class must have one of two constructors with the following + * types: + *
    + *
  • Annotation this annotation annotates
  • + *
  • {@link Class}
  • + *
  • {@link AbstractTypeMapper}, optional, if not present only one per parameter
  • + *
  • {@link java.util.Map} with types {@link String} and {@link AbstractValidator}
  • + *
+ */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface ImplicitTypeMapper { + /** + * The validator class that should be used. + */ + Class handler(); + } + + /** + * This annotation can be used in conjunction with a class that implements {@link AbstractValidator} to + * create a custom validator short hands for commands. The validator class must have one constructor with + * one of the following types: + *
    + *
  • Annotation this annotation annotates
  • + *
  • {@link Class}
  • + *
  • {@link java.util.Map} with types {@link String} and {@link AbstractValidator}
  • + *
+ */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface ImplicitValidator { + /** + * The validator class that should be used. + */ + Class handler(); + + /** + * Defines when this validator should be processed. Negative numbers denote that this will be + * processed before {@link AbstractSWCommand.Validator} and positive numbers + * denote that this will be processed after {@link AbstractSWCommand.Validator}. + */ + int order(); + } +} diff --git a/CommandFramework/src/de/steamwar/command/CommandParseException.java b/CommandFramework/src/de/steamwar/command/CommandParseException.java new file mode 100644 index 00000000..3d81ea69 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/CommandParseException.java @@ -0,0 +1,26 @@ +/* + * 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 CommandParseException extends RuntimeException { + + public CommandParseException() { + } +} diff --git a/CommandFramework/src/de/steamwar/command/CommandPart.java b/CommandFramework/src/de/steamwar/command/CommandPart.java new file mode 100644 index 00000000..02991b40 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/CommandPart.java @@ -0,0 +1,226 @@ +/* + * 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 java.lang.reflect.Array; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +class CommandPart { + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + @AllArgsConstructor + private static class CheckArgumentResult { + private final boolean success; + private final Object value; + } + + private AbstractSWCommand command; + private AbstractTypeMapper typeMapper; + private List> validators = new ArrayList<>(); + private Class varArgType; + private String optional; + + private CommandPart next = null; + + @Setter + private boolean ignoreAsArgument = false; + + @Setter + private boolean onlyUseIfNoneIsGiven = false; + + private Parameter parameter; + private int parameterIndex; + + public CommandPart(AbstractSWCommand command, AbstractTypeMapper typeMapper, Class varArgType, String optional, Parameter parameter, int parameterIndex) { + this.command = command; + this.typeMapper = typeMapper; + this.varArgType = varArgType; + this.optional = optional; + this.parameter = parameter; + this.parameterIndex = parameterIndex; + + if (optional != null && varArgType != null) { + throw new IllegalArgumentException("A vararg part can't have an optional part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); + } + } + + void addValidator(AbstractValidator validator) { + if (validator == null) return; + validators.add(validator); + } + + public void setNext(CommandPart next) { + if (varArgType != null) { + throw new IllegalArgumentException("There can't be a next part if this is a vararg part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); + } + this.next = next; + } + + public boolean isHelp() { + if (next == null) { + if (varArgType == null) { + return false; + } + if (varArgType != String.class) { + return false; + } + return typeMapper == SWCommandUtils.STRING_MAPPER; + } else { + return next.isHelp(); + } + } + + public void generateArgumentArray(Consumer errors, List current, T sender, 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, sender, args, current, i); + if (!validArgument.success) throw new CommandParseException(); + Array.set(array, i - startIndex, validArgument.value); + } + for (AbstractValidator validator : validators) { + if (!validator.validate(sender, array, (s, objects) -> { + errors.accept(() -> command.sendMessage(sender, s, objects)); + })) throw new CommandParseException(); + } + current.add(array); + return; + } + + CheckArgumentResult validArgument = checkArgument(errors, sender, args, current, startIndex); + if (!validArgument.success && optional == null) { + throw new CommandParseException(); + } + if (!validArgument.success) { + if (!ignoreAsArgument) { + if (!onlyUseIfNoneIsGiven) { + current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); + } else if (startIndex >= args.length) { + current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); + } else { + throw new CommandParseException(); + } + } + if (next != null) { + next.generateArgumentArray(errors, current, sender, args, startIndex); + } + return; + } + if (!ignoreAsArgument) { + current.add(validArgument.value); + } + if (next != null) { + next.generateArgumentArray(errors, current, sender, args, startIndex + 1); + } else if (startIndex + 1 < args.length) { + throw new CommandParseException(); + } + } + + public void generateTabComplete(List current, T sender, String[] args, List mappedArgs, int startIndex) { + if (varArgType != null) { + List currentArgs = new ArrayList<>(mappedArgs); + List varArgs = new ArrayList<>(); + for (int i = startIndex; i < args.length - 1; i++) { + CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, mappedArgs, i); + if (!validArgument.success) return; + varArgs.add(validArgument.value); + } + + currentArgs.add(varArgs.toArray()); + Collection strings = tabCompletes(sender, args, currentArgs, args.length - 1); + if (strings != null) { + current.addAll(strings); + } + return; + } + + if (args.length - 1 > startIndex) { + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, mappedArgs, startIndex); + if (checkArgumentResult.success && next != null) { + if (!ignoreAsArgument) { + mappedArgs.add(checkArgumentResult.value); + } + next.generateTabComplete(current, sender, args, mappedArgs, startIndex + 1); + return; + } + if (optional != null && next != null) { + next.generateTabComplete(current, sender, args, mappedArgs, startIndex); + } + return; + } + + Collection strings = tabCompletes(sender, args, mappedArgs, startIndex); + if (strings != null) { + current.addAll(strings); + } + if (optional != null && next != null) { + next.generateTabComplete(current, sender, args, mappedArgs, startIndex); + } + } + + private Collection tabCompletes(T sender, String[] args, List mappedArgs, int startIndex) { + return TabCompletionCache.tabComplete(sender, args[startIndex], (AbstractTypeMapper) typeMapper, () -> { + try { + return typeMapper.tabCompletes(sender, new PreviousArguments(Arrays.copyOf(args, startIndex), mappedArgs.toArray()), args[startIndex]); + } catch (Throwable e) { + throw CommandFrameworkException.commandPartExceptions("tabcompleting", e, args[startIndex], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); + } + }); + } + + private CheckArgumentResult checkArgument(Consumer errors, T sender, String[] args, List mappedArgs, int index) { + Object value; + try { + value = typeMapper.map(sender, new PreviousArguments(Arrays.copyOf(args, index), mappedArgs.toArray()), args[index]); + } catch (Exception e) { + return new CheckArgumentResult(false, null); + } + boolean success = true; + for (AbstractValidator validator : validators) { + try { + if (!validator.validate(sender, value, (s, objects) -> { + errors.accept(() -> { + command.sendMessage(sender, s, objects); + }); + })) { + success = false; + value = null; + } + } catch (Throwable e) { + throw CommandFrameworkException.commandPartExceptions("validating", e, args[index], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); + } + } + return new CheckArgumentResult(success, value); + } + + public Class getType() { + return varArgType != null ? varArgType : parameter.getType(); + } +} diff --git a/CommandFramework/src/de/steamwar/command/PreviousArguments.java b/CommandFramework/src/de/steamwar/command/PreviousArguments.java new file mode 100644 index 00000000..e9851c07 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/PreviousArguments.java @@ -0,0 +1,62 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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 java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class PreviousArguments { + + public final String[] userArgs; + public final Object[] mappedArgs; + + public PreviousArguments(String[] userArgs, Object[] mappedArgs) { + this.userArgs = userArgs; + this.mappedArgs = mappedArgs; + } + + public String getUserArg(int index) { + return userArgs[userArgs.length - index - 1]; + } + + public T getMappedArg(int index) { + return (T) mappedArgs[mappedArgs.length - index - 1]; + } + + public Optional getFirst(Class clazz) { + for (Object o : mappedArgs) { + if (clazz.isInstance(o)) { + return Optional.of((T) o); + } + } + return Optional.empty(); + } + + public List getAll(Class clazz) { + List list = new ArrayList<>(); + for (Object o : mappedArgs) { + if (clazz.isInstance(o)) { + list.add((T) o); + } + } + return list; + } +} diff --git a/CommandFramework/src/de/steamwar/command/SWCommandUtils.java b/CommandFramework/src/de/steamwar/command/SWCommandUtils.java new file mode 100644 index 00000000..ed16eab8 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/SWCommandUtils.java @@ -0,0 +1,185 @@ +/* + * 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.Getter; +import lombok.experimental.UtilityClass; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +@UtilityClass +public class SWCommandUtils { + + @Getter + private final Map> MAPPER_FUNCTIONS = new HashMap<>(); + + @Getter + private final Map> VALIDATOR_FUNCTIONS = new HashMap<>(); + + private SWTypeMapperCreator swTypeMapperCreator = (mapper, tabCompleter) -> new AbstractTypeMapper() { + @Override + public Object map(Object sender, PreviousArguments previousArguments, String s) { + return mapper.apply(s); + } + + @Override + public Collection tabCompletes(Object sender, PreviousArguments previousArguments, String s) { + return ((BiFunction>) tabCompleter).apply(sender, s); + } + }; + + static final AbstractTypeMapper STRING_MAPPER = createMapper(s -> s, Collections::singletonList); + + static { + addMapper(boolean.class, Boolean.class, createMapper(s -> { + if (s.equalsIgnoreCase("true")) return true; + if (s.equalsIgnoreCase("false")) return false; + return null; + }, s -> Arrays.asList("true", "false"))); + addMapper(float.class, Float.class, createMapper(numberMapper(Float::parseFloat), numberCompleter(Float::parseFloat, true))); + addMapper(double.class, Double.class, createMapper(numberMapper(Double::parseDouble), numberCompleter(Double::parseDouble, true))); + addMapper(int.class, Integer.class, createMapper(numberMapper(Integer::parseInt), numberCompleter(Integer::parseInt, false))); + addMapper(long.class, Long.class, createMapper(numberMapper(Long::parseLong), numberCompleter(Long::parseLong, false))); + MAPPER_FUNCTIONS.put(String.class.getTypeName(), STRING_MAPPER); + } + + public static , K, V> void init(SWTypeMapperCreator swTypeMapperCreator) { + SWCommandUtils.swTypeMapperCreator = swTypeMapperCreator; + } + + private static void addMapper(Class clazz, Class alternativeClazz, AbstractTypeMapper mapper) { + MAPPER_FUNCTIONS.put(clazz.getTypeName(), mapper); + MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); + } + + public static AbstractTypeMapper getTypeMapper(String name, Map> localTypeMapper) { + AbstractTypeMapper typeMapper = localTypeMapper.getOrDefault(name, (AbstractTypeMapper) MAPPER_FUNCTIONS.getOrDefault(name, null)); + if (typeMapper == null) { + throw new IllegalArgumentException("No mapper found for " + name); + } + return typeMapper; + } + + public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); + } + if (clazz.isEnum() && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { + return createEnumMapper((Class>) clazz); + } + return getTypeMapper(clazz.getTypeName(), localTypeMapper); + } + + public static AbstractValidator getValidator(AbstractSWCommand.Validator validator, Class type, Map> localValidator) { + String s = validator.value() != null && !validator.value().isEmpty() ? validator.value() : type.getTypeName(); + AbstractValidator concreteValidator = localValidator.getOrDefault(s, (AbstractValidator) VALIDATOR_FUNCTIONS.getOrDefault(s, null)); + if (concreteValidator == null) { + throw new IllegalArgumentException("No validator found for " + s); + } + return concreteValidator; + } + + public static void addMapper(Class clazz, AbstractTypeMapper mapper) { + addMapper(clazz.getTypeName(), mapper); + } + + public static void addMapper(String name, AbstractTypeMapper mapper) { + MAPPER_FUNCTIONS.putIfAbsent(name, mapper); + } + + public static void addValidator(Class clazz, AbstractValidator validator) { + addValidator(clazz.getTypeName(), validator); + } + + public static void addValidator(String name, AbstractValidator validator) { + VALIDATOR_FUNCTIONS.putIfAbsent(name, validator); + } + + public static , K> T createMapper(String... values) { + List strings = Arrays.stream(values).map(String::toLowerCase).collect(Collectors.toList()); + List tabCompletes = Arrays.asList(values); + return createMapper(s -> strings.contains(s.toLowerCase()) ? s : null, s -> tabCompletes); + } + + public static , K, V> T createMapper(Function mapper, Function> tabCompleter) { + return createMapper(mapper, (commandSender, s) -> tabCompleter.apply(s)); + } + + public static , K, V> T createMapper(Function mapper, BiFunction> tabCompleter) { + return (T) swTypeMapperCreator.createTypeMapper(mapper, tabCompleter); + } + + public static >, K> T createEnumMapper(Class> enumClass) { + Map> enumMap = new HashMap<>(); + for (Enum e : enumClass.getEnumConstants()) { + enumMap.put(e.name().toLowerCase(), e); + } + return createMapper(s -> enumMap.get(s.toLowerCase()), (k, s) -> enumMap.keySet()); + } + + 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; + } + }; + } + + private static Function> numberCompleter(Function mapper, boolean comma) { + return s -> { + if (numberMapper(mapper).apply(s) == null) { + return Collections.emptyList(); + } + List strings = new ArrayList<>(); + if (s.length() == 0) { + strings.add("-"); + } else { + strings.add(s); + } + for (int i = 0; i < 10; i++) { + strings.add(s + i); + } + if (comma && (!s.contains(".") || !s.contains(","))) { + strings.add(s + "."); + } + return strings; + }; + } + + static T[] getAnnotation(Method method, Class annotation) { + if (method.getAnnotations().length == 0) return null; + return method.getDeclaredAnnotationsByType(annotation); + } +} diff --git a/CommandFramework/src/de/steamwar/command/SWTypeMapperCreator.java b/CommandFramework/src/de/steamwar/command/SWTypeMapperCreator.java new file mode 100644 index 00000000..f21b0a82 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/SWTypeMapperCreator.java @@ -0,0 +1,28 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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 java.util.Collection; +import java.util.function.BiFunction; +import java.util.function.Function; + +public interface SWTypeMapperCreator, K, V> { + T createTypeMapper(Function mapper, BiFunction> tabCompleter); +} diff --git a/CommandFramework/src/de/steamwar/command/SubCommand.java b/CommandFramework/src/de/steamwar/command/SubCommand.java new file mode 100644 index 00000000..368a8178 --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/SubCommand.java @@ -0,0 +1,292 @@ +/* + * 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 java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +public class SubCommand implements Comparable> { + + private AbstractSWCommand abstractSWCommand; + Method method; + String[] description; + String[] subCommand; + private Predicate senderPredicate; + private Function senderFunction; + AbstractValidator validator; + boolean noTabComplete; + + private Parameter[] parameters; + + private CommandPart commandPart; + + boolean isHelp = false; + + SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localValidator, String[] description, boolean noTabComplete) { + this.abstractSWCommand = abstractSWCommand; + this.method = method; + try { + this.method.setAccessible(true); + } catch (SecurityException e) { + throw new SecurityException(e.getMessage(), e); + } + this.subCommand = subCommand; + this.description = description; + this.noTabComplete = noTabComplete; + + parameters = method.getParameters(); + AbstractSWCommand.Validator validator = parameters[0].getAnnotation(AbstractSWCommand.Validator.class); + if (validator != null) { + this.validator = (AbstractValidator) SWCommandUtils.getValidator(validator, parameters[0].getType(), localValidator); + if (validator.invert()) { + AbstractValidator current = this.validator; + this.validator = (sender, value, messageSender) -> !current.validate(sender, value, messageSender); + } + } + + commandPart = generateCommandPart(abstractSWCommand, subCommand, parameters, localTypeMapper, localValidator); + if (commandPart != null) isHelp = commandPart.isHelp(); + + senderPredicate = t -> parameters[0].getType().isAssignableFrom(t.getClass()); + senderFunction = t -> parameters[0].getType().cast(t); + } + + @Override + public int compareTo(SubCommand o) { + int tLength = parameters.length + subCommand.length; + int oLength = o.parameters.length + o.subCommand.length; + + boolean tVarArgs = parameters[parameters.length - 1].isVarArgs(); + boolean oVarArgs = o.parameters[o.parameters.length - 1].isVarArgs(); + + if (tVarArgs) tLength *= -1; + if (oVarArgs) oLength *= -1; + + if (tVarArgs && oVarArgs) return Integer.compare(tLength, oLength); + + return -Integer.compare(tLength, oLength); + } + + boolean invoke(Consumer errors, T sender, String alias, String[] args) { + try { + if (!senderPredicate.test(sender)) { + return false; + } + + if (commandPart == null) { + if (args.length != 0) { + return false; + } + if (validator != null) { + if (!validator.validate(sender, sender, (s, objectArgs) -> { + abstractSWCommand.sendMessage(sender, s, objectArgs); + })) { + return false; + } + } + method.invoke(abstractSWCommand, senderFunction.apply(sender)); + } else { + List objects = new ArrayList<>(); + commandPart.generateArgumentArray(errors, objects, sender, args, 0); + if (validator != null) { + if (!validator.validate(sender, sender, (s, objectArgs) -> { + abstractSWCommand.sendMessage(sender, s, objectArgs); + })) { + return false; + } + } + objects.add(0, senderFunction.apply(sender)); + method.invoke(abstractSWCommand, objects.toArray()); + } + } catch (CommandFrameworkException e) { + throw e; + } catch (CommandParseException e) { + return false; + } catch (InvocationTargetException e) { + throw new CommandFrameworkException(e, alias, args); + } catch (IllegalAccessException | RuntimeException e) { + throw new SecurityException(e.getMessage(), e); + } + return true; + } + + List tabComplete(T sender, String[] args) { + if (validator != null && !validator.validate(sender, sender, (s, objects) -> {})) { + return null; + } + if (commandPart == null) { + return null; + } + List list = new ArrayList<>(); + commandPart.generateTabComplete(list, sender, args, new ArrayList<>(), 0); + return list; + } + + private static CommandPart generateCommandPart(AbstractSWCommand command, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map> localValidator) { + CommandPart first = null; + CommandPart current = null; + for (String s : subCommand) { + CommandPart commandPart = new CommandPart(command, SWCommandUtils.createMapper(s), null, null, null, -1); + commandPart.addValidator(NULL_FILTER); + commandPart.setIgnoreAsArgument(true); + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + if (first == null) { + first = current; + } + } + for (int i = 1; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + AbstractTypeMapper typeMapper = handleImplicitTypeMapper(parameter, localTypeMapper); + Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; + AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); + + CommandPart commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); + commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); + handleImplicitTypeValidator(parameter, commandPart, localValidator); + if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { + commandPart.addValidator((AbstractValidator) NULL_FILTER); + } + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + if (first == null) { + first = current; + } + } + return first; + } + + private static AbstractTypeMapper handleImplicitTypeMapper(Parameter parameter, Map> localTypeMapper) { + Class type = parameter.getType(); + if (parameter.isVarArgs()) { + type = type.getComponentType(); + } + + Annotation[] annotations = parameter.getAnnotations(); + Constructor sourceConstructor = null; + Annotation sourceAnnotation = null; + List> parentConstructors = new ArrayList<>(); + List parentAnnotations = new ArrayList<>(); + for (Annotation annotation : annotations) { + CommandMetaData.ImplicitTypeMapper implicitTypeMapper = annotation.annotationType().getAnnotation(CommandMetaData.ImplicitTypeMapper.class); + if (implicitTypeMapper == null) continue; + Class clazz = implicitTypeMapper.handler(); + if (!AbstractTypeMapper.class.isAssignableFrom(clazz)) continue; + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) continue; + Constructor constructor = constructors[0]; + if (needsTypeMapper(constructor)) { + parentConstructors.add(constructor); + parentAnnotations.add(annotation); + } else { + if (sourceAnnotation != null) { + throw new IllegalArgumentException("Multiple source type mappers found for parameter " + parameter); + } + sourceConstructor = constructor; + sourceAnnotation = annotation; + } + } + + AbstractTypeMapper current; + if (sourceAnnotation != null) { + current = createInstance(sourceConstructor, sourceAnnotation, type, localTypeMapper); + } else { + current = (AbstractTypeMapper) SWCommandUtils.getTypeMapper(parameter, localTypeMapper); + } + for (int i = 0; i < parentConstructors.size(); i++) { + Constructor constructor = parentConstructors.get(i); + Annotation annotation = parentAnnotations.get(i); + current = createInstance(constructor, annotation, type, localTypeMapper, current); + } + return current; + } + + private static boolean needsTypeMapper(Constructor constructor) { + Class[] parameterTypes = constructor.getParameterTypes(); + for (Class parameterType : parameterTypes) { + if (AbstractTypeMapper.class.isAssignableFrom(parameterType)) { + return true; + } + } + return false; + } + + private static void handleImplicitTypeValidator(Parameter parameter, CommandPart commandPart, Map> localValidator) { + Annotation[] annotations = parameter.getAnnotations(); + Map>> validators = new HashMap<>(); + for (Annotation annotation : annotations) { + CommandMetaData.ImplicitValidator implicitValidator = annotation.annotationType().getAnnotation(CommandMetaData.ImplicitValidator.class); + if (implicitValidator == null) continue; + Class clazz = implicitValidator.handler(); + if (!AbstractValidator.class.isAssignableFrom(clazz)) continue; + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) continue; + AbstractValidator validator = createInstance(constructors[0], annotation, commandPart.getType(), localValidator); + validators.computeIfAbsent(implicitValidator.order(), integer -> new ArrayList<>()).add(validator); + } + List keys = new ArrayList<>(validators.keySet()); + keys.sort(Integer::compareTo); + for (Integer key : keys) { + List> list = validators.get(key); + for (AbstractValidator validator : list) { + commandPart.addValidator(validator); + } + } + } + + private static T createInstance(Constructor constructor, Object... parameter) { + Class[] types = constructor.getParameterTypes(); + List objects = new ArrayList<>(); + for (Class clazz : types) { + boolean found = false; + for (Object o : parameter) { + if (clazz.isAssignableFrom(o.getClass())) { + objects.add(o); + found = true; + break; + } + } + if (!found) { + throw new RuntimeException("Could not find type " + clazz + " for constructor " + constructor); + } + } + try { + return (T) constructor.newInstance(objects.toArray()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private static final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; +} diff --git a/CommandFramework/src/de/steamwar/command/TabCompletionCache.java b/CommandFramework/src/de/steamwar/command/TabCompletionCache.java new file mode 100644 index 00000000..551ce19f --- /dev/null +++ b/CommandFramework/src/de/steamwar/command/TabCompletionCache.java @@ -0,0 +1,93 @@ +/* + * 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.EqualsAndHashCode; +import lombok.experimental.UtilityClass; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +@UtilityClass +public class TabCompletionCache { + + private Map tabCompletionCache = new HashMap<>(); + Set> cached = new HashSet<>(); + Set> global = new HashSet<>(); + Map, Long> cacheDuration = new HashMap<>(); + + void add(AbstractTypeMapper typeMapper, AbstractSWCommand.Cached cached) { + if (cached != null) { + add(typeMapper, cached.global(), cached.cacheDuration(), cached.timeUnit()); + } + } + + public void add(AbstractTypeMapper typeMapper, boolean global, long cacheDuration, TimeUnit timeUnit) { + TabCompletionCache.cached.add(typeMapper); + if (global) TabCompletionCache.global.add(typeMapper); + TabCompletionCache.cacheDuration.put(typeMapper, timeUnit.toMillis(cacheDuration)); + } + + @EqualsAndHashCode + @AllArgsConstructor + private static class Key { + private Object sender; + private String arg; + private AbstractTypeMapper typeMapper; + } + + @AllArgsConstructor + private static class TabCompletions { + private long timestamp; + private Collection tabCompletions; + } + + Collection tabComplete(Object sender, String arg, AbstractTypeMapper typeMapper, Supplier> tabCompleteSupplier) { + if (!cached.contains(typeMapper)) return tabCompleteSupplier.get(); + + String normalizedArg = typeMapper.normalize(sender, arg); + if (normalizedArg == null) normalizedArg = ""; + Key key = new Key(global.contains(typeMapper) ? null : sender, normalizedArg, typeMapper); + + TabCompletions tabCompletions = tabCompletionCache.computeIfAbsent(key, ignore -> { + return new TabCompletions(System.currentTimeMillis(), tabCompleteSupplier.get()); + }); + + if (System.currentTimeMillis() - tabCompletions.timestamp > cacheDuration.get(typeMapper)) { + tabCompletions.tabCompletions = tabCompleteSupplier.get(); + } + tabCompletions.timestamp = System.currentTimeMillis(); + return tabCompletions.tabCompletions; + } + + public void invalidateOldEntries() { + Set toRemove = new HashSet<>(); + for (Map.Entry tabCompletionsEntry : tabCompletionCache.entrySet()) { + if (System.currentTimeMillis() - tabCompletionsEntry.getValue().timestamp > cacheDuration.get(tabCompletionsEntry.getKey().typeMapper)) { + toRemove.add(tabCompletionsEntry.getKey()); + } + } + for (Key key : toRemove) { + tabCompletionCache.remove(key); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/AssertionUtils.java b/CommandFramework/testsrc/de/steamwar/AssertionUtils.java new file mode 100644 index 00000000..ed8c8ca0 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/AssertionUtils.java @@ -0,0 +1,45 @@ +/* + * 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 de.steamwar.command.CommandFrameworkException; +import lombok.experimental.UtilityClass; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@UtilityClass +public class AssertionUtils { + + public static void assertCMDFramework(Exception e, Class clazz, String message) { + assertThat(e, is(instanceOf(CommandFrameworkException.class))); + assertThat(e.getCause().getCause(), is(instanceOf(clazz))); + assertThat(e.getCause().getCause().getMessage(), is(message)); + } + + public static void assertTabCompletes(List list, T... elements) { + assertThat(list.size(), is(elements.length)); + if (elements.length > 0) { + assertThat(list, containsInAnyOrder(elements)); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/ArgumentCommand.java b/CommandFramework/testsrc/de/steamwar/command/ArgumentCommand.java new file mode 100644 index 00000000..41f59c75 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/ArgumentCommand.java @@ -0,0 +1,74 @@ +/* + * 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; + +public class ArgumentCommand extends TestSWCommand { + + public ArgumentCommand() { + super("argument"); + } + + @Register + public void argument(String sender, boolean b, boolean b2) { + throw new ExecutionIdentifier("RunArgument with Boolean"); + } + + @Register + public void argument(String sender, float f, float f2, float f3) { + throw new ExecutionIdentifier("RunArgument with Float"); + } + + @Register + public void argument(String sender, double d, double d2, double d3, double d4) { + throw new ExecutionIdentifier("RunArgument with Double"); + } + + @Register + public void argument(String sender, int i) { + throw new ExecutionIdentifier("RunArgument with Integer"); + } + + @Register + public void argument(String sender, long l, long l2) { + throw new ExecutionIdentifier("RunArgument with Long"); + } + + @Register + public void argument(String sender, String arg) { + throw new ExecutionIdentifier("RunArgument with String"); + } + + @Register + public void minLengthArgument(String sender, @Length(min = 3) @StaticValue({"he", "hello"}) String arg) { + throw new ExecutionIdentifier("RunLengthArgument with String"); + } + + @Register + public void minAndMaxLengthArgument(String sender, @Length(min = 3, max = 3) @StaticValue({"wo", "world"}) String arg) { + throw new ExecutionIdentifier("RunLengthArgument with String"); + } + + public void arrayLengthArgument(String sender, @ArrayLength(max = 3) @StaticValue({"one", "two", "three"}) String... args) { + throw new ExecutionIdentifier("RunArrayLengthArgument with String"); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/ArgumentCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/ArgumentCommandTest.java new file mode 100644 index 00000000..b46c1844 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/ArgumentCommandTest.java @@ -0,0 +1,128 @@ +/* + * 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import java.util.List; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static de.steamwar.AssertionUtils.assertTabCompletes; + +public class ArgumentCommandTest { + + @Test + public void testNoArgs() { + ArgumentCommand cmd = new ArgumentCommand(); + try { + cmd.execute("test", "", new String[0]); + } catch (Exception e) { + throw new AssertionError("No exception expected"); + } + } + + @Test + public void testBoolean() { + ArgumentCommand cmd = new ArgumentCommand(); + try { + cmd.execute("test", "", new String[]{"true", "false"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Boolean"); + } + } + + @Test + public void testFloat() { + ArgumentCommand cmd = new ArgumentCommand(); + try { + cmd.execute("test", "", new String[]{"0.0", "0.0", "0.0"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Float"); + } + } + + @Test + public void testDouble() { + ArgumentCommand cmd = new ArgumentCommand(); + try { + cmd.execute("test", "", new String[]{"0.0", "0.0", "0.0", "0.0"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Double"); + } + } + + @Test + public void testInt() { + ArgumentCommand cmd = new ArgumentCommand(); + try { + cmd.execute("test", "", new String[]{"0"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Integer"); + } + } + + @Test + public void testLong() { + ArgumentCommand cmd = new ArgumentCommand(); + try { + cmd.execute("test", "", new String[]{"0", "0"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunArgument with Long"); + } + } + + @Test + public void testTabComplete() { + ArgumentCommand cmd = new ArgumentCommand(); + List strings = cmd.tabComplete("test", "", new String[]{""}); + assertTabCompletes(strings, "true", "false", "hello", "wor"); + } + + @Test + public void testPartialTabComplete() { + ArgumentCommand cmd = new ArgumentCommand(); + List strings = cmd.tabComplete("test", "", new String[]{"t"}); + assertTabCompletes(strings, "true", "t"); + + strings = cmd.tabComplete("test", "", new String[]{"h"}); + assertTabCompletes(strings, "h", "hello"); + + strings = cmd.tabComplete("test", "", new String[]{"hel"}); + assertTabCompletes(strings, "hel", "hello"); + + strings = cmd.tabComplete("test", "", new String[]{"w"}); + assertTabCompletes(strings, "w", "wor"); + + strings = cmd.tabComplete("test", "", new String[]{"wor"}); + assertTabCompletes(strings, "wor"); + + strings = cmd.tabComplete("test", "", new String[]{"worl"}); + assertTabCompletes(strings, "wor", "worl"); + + strings = cmd.tabComplete("test", "", new String[]{"one", "two", "three", "one"}); + assertTabCompletes(strings); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/BetterExceptionCommand.java b/CommandFramework/testsrc/de/steamwar/command/BetterExceptionCommand.java new file mode 100644 index 00000000..feb47fc6 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/BetterExceptionCommand.java @@ -0,0 +1,58 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Collection; + +public class BetterExceptionCommand extends TestSWCommand { + + public BetterExceptionCommand() { + super("betterexception"); + } + + @Register + public void exceptionOnTabComplete(String s, @Mapper("exception") @Validator("exception") String s1) { + } + + @Mapper("exception") + @Validator("exception") + public TestTypeMapper tabCompleteException() { + return new TestTypeMapper() { + @Override + public String map(String sender, PreviousArguments previousArguments, String s) { + return null; + } + + @Override + public boolean validate(String sender, String value, MessageSender messageSender) { + System.out.println("Validate: " + value); + throw new SecurityException(); + } + + @Override + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { + throw new SecurityException(); + } + }; + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/BetterExceptionCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/BetterExceptionCommandTest.java new file mode 100644 index 00000000..170ad60b --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/BetterExceptionCommandTest.java @@ -0,0 +1,53 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +public class BetterExceptionCommandTest { + + @Test + public void testTabCompleteException() { + BetterExceptionCommand cmd = new BetterExceptionCommand(); + try { + cmd.tabComplete("test", "", new String[]{""}); + assert false; + } catch (Exception e) { + assertThat(e, is(instanceOf(CommandFrameworkException.class))); + assertThat(e.getMessage(), is("de.steamwar.command.CommandFrameworkException: Error while tabcompleting () to type java.lang.String with parameter index 1")); + } + } + + @Test + public void testValidationException() { + BetterExceptionCommand cmd = new BetterExceptionCommand(); + try { + cmd.execute("test", "", new String[]{""}); + assert false; + } catch (Exception e) { + assertThat(e, is(instanceOf(CommandFrameworkException.class))); + assertThat(e.getMessage(), is("de.steamwar.command.CommandFrameworkException: Error while validating () to type java.lang.String with parameter index 1")); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/CacheCommand.java b/CommandFramework/testsrc/de/steamwar/command/CacheCommand.java new file mode 100644 index 00000000..7cfdb726 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/CacheCommand.java @@ -0,0 +1,56 @@ +/* + * 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.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; + +public class CacheCommand extends TestSWCommand { + + public CacheCommand() { + super("typemapper"); + } + + @Register + public void test(String sender, int tabCompleteTest) { + } + + private AtomicInteger count = new AtomicInteger(); + + @Cached + @Mapper(value = "int", local = true) + public AbstractTypeMapper typeMapper() { + return new TestTypeMapper() { + @Override + public Integer map(String sender, PreviousArguments previousArguments, String s) { + return Integer.parseInt(s); + } + + @Override + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { + return Arrays.asList(count.getAndIncrement() + ""); + } + }; + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/CacheCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/CacheCommandTest.java new file mode 100644 index 00000000..4a6e183f --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/CacheCommandTest.java @@ -0,0 +1,54 @@ +/* + * 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.junit.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class CacheCommandTest { + + @Test + public void testCaching() { + CacheCommand cmd = new CacheCommand(); + List tabCompletions1 = cmd.tabComplete("test", "", new String[]{""}); + List tabCompletions2 = cmd.tabComplete("test", "", new String[]{""}); + assertThat(tabCompletions1, is(equalTo(tabCompletions2))); + } + + @Test + public void testCachingWithDifferentMessages() { + CacheCommand cmd = new CacheCommand(); + List tabCompletions1 = cmd.tabComplete("test", "", new String[]{""}); + List tabCompletions2 = cmd.tabComplete("test", "", new String[]{"0"}); + assertThat(tabCompletions1, is(equalTo(tabCompletions2))); + } + + @Test + public void testCachingWithDifferentSenders() { + CacheCommand cmd = new CacheCommand(); + List tabCompletions1 = cmd.tabComplete("test", "", new String[]{""}); + List tabCompletions2 = cmd.tabComplete("test2", "", new String[]{""}); + assertThat(tabCompletions1, is(not(equalTo(tabCompletions2)))); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/NullMapperCommand.java b/CommandFramework/testsrc/de/steamwar/command/NullMapperCommand.java new file mode 100644 index 00000000..30f4061a --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/NullMapperCommand.java @@ -0,0 +1,59 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Collection; + +public class NullMapperCommand extends TestSWCommand { + + public NullMapperCommand() { + super("nullmapper"); + } + + @Register + public void test(String sender, @AllowNull String arg) { + if (arg == null) { + throw new ExecutionIdentifier("null"); + } + throw new ExecutionIdentifier("notnull"); + } + + @ClassMapper(value = String.class, local = true) + public TestTypeMapper typeMapper() { + return new TestTypeMapper() { + @Override + public String map(String sender, PreviousArguments previousArguments, String s) { + if (s.equals("Hello World")) { + return null; + } + return s; + } + + @Override + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { + return null; + } + }; + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/NullMapperCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/NullMapperCommandTest.java new file mode 100644 index 00000000..1e19d133 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/NullMapperCommandTest.java @@ -0,0 +1,52 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class NullMapperCommandTest { + + @Test + public void testNull() { + NullMapperCommand command = new NullMapperCommand(); + try { + command.execute("test", "", new String[] {"Hello World"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "null"); + } + } + + @Test + public void testNonNull() { + NullMapperCommand command = new NullMapperCommand(); + try { + command.execute("test", "", new String[] {"Hello"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "notnull"); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/NumberValidatorCommand.java b/CommandFramework/testsrc/de/steamwar/command/NumberValidatorCommand.java new file mode 100644 index 00000000..9ba53a64 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/NumberValidatorCommand.java @@ -0,0 +1,35 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; + +public class NumberValidatorCommand extends TestSWCommand { + + public NumberValidatorCommand() { + super("numberValidator"); + } + + @Register + public void test(String sender, @Min(intValue = 0) @Max(intValue = 10) int i) { + throw new ExecutionIdentifier("RunNumberValidator with int"); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/NumberValidatorCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/NumberValidatorCommandTest.java new file mode 100644 index 00000000..c45b4a86 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/NumberValidatorCommandTest.java @@ -0,0 +1,51 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; + +public class NumberValidatorCommandTest { + + @Test + public void testMinValue() { + NumberValidatorCommand command = new NumberValidatorCommand(); + command.execute("sender", "", new String[]{"-1"}); + } + + @Test + public void testMaxValue() { + NumberValidatorCommand command = new NumberValidatorCommand(); + command.execute("sender", "", new String[]{"11"}); + } + + @Test + public void testValidValue() { + try { + NumberValidatorCommand command = new NumberValidatorCommand(); + command.execute("sender", "", new String[]{"2"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunNumberValidator with int"); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/PartOfCommand.java b/CommandFramework/testsrc/de/steamwar/command/PartOfCommand.java new file mode 100644 index 00000000..9c66a4b6 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/PartOfCommand.java @@ -0,0 +1,70 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Arrays; +import java.util.Collection; + +public class PartOfCommand { + + public static class ParentCommand extends TestSWCommand { + + public ParentCommand() { + super("parent"); + } + + @Register + public void execute(String s, String... args) { + throw new ExecutionIdentifier("ParentCommand with String..."); + } + + @Mapper("test") + public TestTypeMapper typeMapper() { + return new TestTypeMapper() { + @Override + public Integer map(String sender, PreviousArguments previousArguments, String s) { + return -1; + } + + @Override + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { + return Arrays.asList(s); + } + }; + } + } + + @AbstractSWCommand.PartOf(ParentCommand.class) + public static class SubCommand extends TestSWCommand { + + public SubCommand() { + super(null); + } + + @Register + public void execute(String s, @Mapper("test") int i) { + throw new ExecutionIdentifier("SubCommand with int " + i); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/PartOfCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/PartOfCommandTest.java new file mode 100644 index 00000000..726384e1 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/PartOfCommandTest.java @@ -0,0 +1,42 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class PartOfCommandTest { + + @Test + public void testMerging() { + PartOfCommand.ParentCommand command = new PartOfCommand.ParentCommand(); + new PartOfCommand.SubCommand(); + try { + command.execute("test", "", new String[]{"0"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "SubCommand with int -1"); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/PreviousArgumentCommand.java b/CommandFramework/testsrc/de/steamwar/command/PreviousArgumentCommand.java new file mode 100644 index 00000000..454283c9 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/PreviousArgumentCommand.java @@ -0,0 +1,54 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Arrays; +import java.util.Collection; + +public class PreviousArgumentCommand extends TestSWCommand { + + public PreviousArgumentCommand() { + super("previous"); + } + + @Register + public void genericCommand(String s, int i, String l) { + throw new ExecutionIdentifier(l); + } + + @ClassMapper(value = String.class, local = true) + public TestTypeMapper typeMapper() { + return new TestTypeMapper() { + @Override + public String map(String sender, PreviousArguments previousArguments, String s) { + return "RunTypeMapper_" + previousArguments.getMappedArg(0) + "_" + previousArguments.getMappedArg(0).getClass().getSimpleName(); + } + + @Override + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { + return Arrays.asList(previousArguments.getMappedArg(0) + ""); + } + }; + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java new file mode 100644 index 00000000..941b1c3e --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java @@ -0,0 +1,56 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import java.util.List; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static de.steamwar.AssertionUtils.assertTabCompletes; + +public class PreviousArgumentCommandTest { + + @Test + public void testPrevArg1() { + PreviousArgumentCommand command = new PreviousArgumentCommand(); + List strings = command.tabComplete("", "", new String[]{"1", ""}); + assertTabCompletes(strings, "1"); + } + + @Test + public void testPrevArg2() { + PreviousArgumentCommand command = new PreviousArgumentCommand(); + List strings = command.tabComplete("", "", new String[]{"2", ""}); + assertTabCompletes(strings, "2"); + } + + @Test + public void testPrevArgExecute() { + PreviousArgumentCommand command = new PreviousArgumentCommand(); + try { + command.execute("", "", new String[]{"2", "2"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunTypeMapper_2_Integer"); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/SimpleCommand.java b/CommandFramework/testsrc/de/steamwar/command/SimpleCommand.java new file mode 100644 index 00000000..7388633c --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/SimpleCommand.java @@ -0,0 +1,40 @@ +/* + * 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; + +public class SimpleCommand extends TestSWCommand { + + public SimpleCommand() { + super("simple"); + } + + @Register(value = "a", noTabComplete = true) + public void test(String s, String... varargs) { + throw new ExecutionIdentifier("RunSimple with Varargs"); + } + + @Register + public void simple(String s) { + throw new ExecutionIdentifier("RunSimple with noArgs"); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/SimpleCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/SimpleCommandTest.java new file mode 100644 index 00000000..aebd904f --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/SimpleCommandTest.java @@ -0,0 +1,68 @@ +/* + * 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import java.util.List; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static de.steamwar.AssertionUtils.assertTabCompletes; + +public class SimpleCommandTest { + + @Test + public void testSimpleParsing() { + SimpleCommand cmd = new SimpleCommand(); + try { + cmd.execute("test", "", new String[0]); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunSimple with noArgs"); + } + } + + @Test + public void testVarArgs() { + SimpleCommand cmd = new SimpleCommand(); + try { + cmd.execute("test", "", new String[] {"a", "b", "c"}); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunSimple with Varargs"); + } + } + + @Test + public void testSimpleParsingNoResult() { + SimpleCommand cmd = new SimpleCommand(); + try { + cmd.execute("test", "", new String[]{"Hello"}); + } catch (CommandFrameworkException e) { + throw new AssertionError("No exception expected"); + } + } + + @Test + public void testSimpleTabComplete() { + SimpleCommand cmd = new SimpleCommand(); + List strings = cmd.tabComplete("test", "", new String[]{""}); + assertTabCompletes(strings); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/StaticValueCommand.java b/CommandFramework/testsrc/de/steamwar/command/StaticValueCommand.java new file mode 100644 index 00000000..0eca60da --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/StaticValueCommand.java @@ -0,0 +1,55 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; + +public class StaticValueCommand extends TestSWCommand { + + public StaticValueCommand() { + super("staticvalue"); + } + + @Register + public void defaultStaticValue(String s, @StaticValue({"hello", "world"}) String staticValue) { + throw new ExecutionIdentifier("RunStaticValue with " + staticValue); + } + + @Register + public void booleanStaticValue(String s, @StaticValue(value = {"-a", "-b", "-c"}, allowISE = true) boolean staticValue) { + throw new ExecutionIdentifier("RunStaticValue with " + staticValue); + } + + @Register + public void booleanStaticValueOtherFalseValue(String s, @StaticValue(value = {"-d", "-e", "-f"}, allowISE = true, falseValues = { 1 }) boolean staticValue) { + throw new ExecutionIdentifier("RunStaticValue with " + staticValue); + } + + @Register + public void intStaticValue(String s, @StaticValue(value = {"-g", "-h", "-i"}, allowISE = true) int staticValue) { + throw new ExecutionIdentifier("RunStaticValue with int " + staticValue); + } + + @Register + public void longStaticValue(String s, @StaticValue(value = {"-j", "-k", "-l"}, allowISE = true) long staticValue) { + throw new ExecutionIdentifier("RunStaticValue with long " + staticValue); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/StaticValueCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/StaticValueCommandTest.java new file mode 100644 index 00000000..75a7dfd6 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/StaticValueCommandTest.java @@ -0,0 +1,147 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import java.util.List; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static de.steamwar.AssertionUtils.assertTabCompletes; + +public class StaticValueCommandTest { + + @Test + public void tabCompletionTest() { + StaticValueCommand cmd = new StaticValueCommand(); + List strings = cmd.tabComplete("", "", new String[]{""}); + assertTabCompletes(strings, "hello", "world", "-a", "-b", "-c", "-d", "-e", "-f", "-g", "-h", "-i", "-j", "-k", "-l"); + } + + @Test + public void defaultTest() { + StaticValueCommand cmd = new StaticValueCommand(); + try { + cmd.execute("", "", new String[] {"hello"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with hello"); + } + try { + cmd.execute("", "", new String[] {"world"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with world"); + } + } + + @Test + public void booleanTest() { + StaticValueCommand cmd = new StaticValueCommand(); + try { + cmd.execute("", "", new String[] {"-a"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with false"); + } + try { + cmd.execute("", "", new String[] {"-b"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); + } + try { + cmd.execute("", "", new String[] {"-c"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); + } + } + + @Test + public void booleanOtherFalseTest() { + StaticValueCommand cmd = new StaticValueCommand(); + try { + cmd.execute("", "", new String[] {"-d"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); + } + try { + cmd.execute("", "", new String[] {"-e"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with false"); + } + try { + cmd.execute("", "", new String[] {"-f"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with true"); + } + } + + @Test + public void intTest() { + StaticValueCommand cmd = new StaticValueCommand(); + try { + cmd.execute("", "", new String[] {"-g"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with int 0"); + } + try { + cmd.execute("", "", new String[] {"-h"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with int 1"); + } + try { + cmd.execute("", "", new String[] {"-i"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with int 2"); + } + } + + @Test + public void longTest() { + StaticValueCommand cmd = new StaticValueCommand(); + try { + cmd.execute("", "", new String[] {"-j"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with long 0"); + } + try { + cmd.execute("", "", new String[] {"-k"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with long 1"); + } + try { + cmd.execute("", "", new String[] {"-l"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunStaticValue with long 2"); + } + } +} \ No newline at end of file diff --git a/CommandFramework/testsrc/de/steamwar/command/SubCMDSortingCommand.java b/CommandFramework/testsrc/de/steamwar/command/SubCMDSortingCommand.java new file mode 100644 index 00000000..8b287608 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/SubCMDSortingCommand.java @@ -0,0 +1,58 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestTypeMapper; + +import java.util.Collections; + +public class SubCMDSortingCommand extends TestSWCommand { + + public SubCMDSortingCommand() { + super("subcmdsorting"); + } + + @Register + public void test(String s) { + throw new ExecutionIdentifier("Command with 0 parameters"); + } + + @Register + public void test(String s, String args) { + throw new ExecutionIdentifier("Command with 1 parameter"); + } + + @Register + public void test(String s, String i1, String i2, String i3, String... args) { + throw new ExecutionIdentifier("Command with 3+n parameters"); + } + + @Register + public void test(String s, String... args) { + throw new ExecutionIdentifier("Command with n parameters"); + } + + @ClassMapper(value = String.class, local = true) + public AbstractTypeMapper stringMapper() { + return SWCommandUtils.createMapper(s -> s, Collections::singletonList); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java new file mode 100644 index 00000000..1f3203c6 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/SubCMDSortingCommandTest.java @@ -0,0 +1,83 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; + +public class SubCMDSortingCommandTest { + + @Test + public void testNoArgs() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 0 parameters"); + } + } + + @Test + public void testOneArgs() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 1 parameter"); + } + } + + @Test + public void testOneArgsVarArg() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello", "World"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with n parameters"); + } + } + + @Test + public void testThreeArgsVarArg() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello", "World", "YoyoNow", "Hugo"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 3+n parameters"); + } + } + + @Test + public void testThreeArgsVarArg2() { + SubCMDSortingCommand cmd = new SubCMDSortingCommand(); + try { + cmd.execute("", "", new String[]{"Hello", "World", "YoyoNow"}); + assert false; + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "Command with 3+n parameters"); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/TypeMapperCommand.java b/CommandFramework/testsrc/de/steamwar/command/TypeMapperCommand.java new file mode 100644 index 00000000..a81500f5 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/TypeMapperCommand.java @@ -0,0 +1,40 @@ +/* + * 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; + +public class TypeMapperCommand extends TestSWCommand { + + public TypeMapperCommand() { + super("typemapper"); + } + + @Register + public void test(String sender, String s) { + throw new ExecutionIdentifier("RunTypeMapper with CustomMapper"); + } + + @ClassMapper(value = String.class, local = true) + public AbstractTypeMapper getTypeMapper() { + return SWCommandUtils.createMapper("1", "2", "3", "4", "5"); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/TypeMapperCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/TypeMapperCommandTest.java new file mode 100644 index 00000000..32b5b47e --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/TypeMapperCommandTest.java @@ -0,0 +1,36 @@ +/* + * 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.junit.Test; + +import java.util.List; + +import static de.steamwar.AssertionUtils.assertTabCompletes; + +public class TypeMapperCommandTest { + + @Test + public void testTabComplete() { + TypeMapperCommand cmd = new TypeMapperCommand(); + List strings = cmd.tabComplete("test", "", new String[]{""}); + assertTabCompletes(strings, "1", "2", "3", "4", "5"); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/ValidatorCommand.java b/CommandFramework/testsrc/de/steamwar/command/ValidatorCommand.java new file mode 100644 index 00000000..b0e4a363 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/ValidatorCommand.java @@ -0,0 +1,62 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestValidator; + +public class ValidatorCommand extends TestSWCommand { + + public ValidatorCommand() { + super("testvalidator"); + } + + @Register + public void test(@Validator String sender) { + throw new ExecutionIdentifier("RunTest"); + } + + @Override + protected void sendMessage(String sender, String message, Object[] args) { + if (message.equals("Hello World")) { + throw new ExecutionIdentifier("RunSendMessageWithHelloWorldParameter"); + } + } + + @Register + public void onError(String sender, @ErrorMessage("Hello World") int error) { + System.out.println("onError: " + sender + " " + error); + throw new ExecutionIdentifier("RunOnError"); + } + + @Register + public void onError(String sender, double error) { + System.out.println("onError: " + sender + " " + error); + throw new ExecutionIdentifier("RunOnErrorDouble"); + } + + @ClassValidator(value = String.class, local = true) + public TestValidator validator() { + return (sender, value, messageSender) -> { + throw new ExecutionIdentifier("RunValidator"); + }; + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/ValidatorCommandTest.java b/CommandFramework/testsrc/de/steamwar/command/ValidatorCommandTest.java new file mode 100644 index 00000000..142d518e --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/ValidatorCommandTest.java @@ -0,0 +1,85 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class ValidatorCommandTest { + + @Test + public void testValidator() { + ValidatorCommand cmd = new ValidatorCommand(); + try { + cmd.execute("test", "", new String[0]); + assertThat(true, is(false)); + } catch (Exception e) { + assertThat(e.getMessage(), is("RunValidator")); + } + } + + @Test + public void testErrorMessage() { + ValidatorCommand cmd = new ValidatorCommand(); + try { + cmd.execute("test", "", new String[]{"Hello"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertThat(e.getMessage(), is("RunSendMessageWithHelloWorldParameter")); + } + } + + @Test + public void testErrorNoMessage() { + ValidatorCommand cmd = new ValidatorCommand(); + try { + cmd.execute("test", "", new String[]{"0.0"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunOnErrorDouble"); + } + } + + @Test + public void testInvert() { + ValidatorInvertCommand cmd = new ValidatorInvertCommand(); + try { + cmd.execute("test", "", new String[]{"false", "true"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunTestInvert"); + } + } + + @Test + public void testInvertOther() { + ValidatorInvertCommand cmd = new ValidatorInvertCommand(); + try { + cmd.execute("test", "", new String[]{"Hello", "0"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunTestInvert2"); + } + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/ValidatorInvertCommand.java b/CommandFramework/testsrc/de/steamwar/command/ValidatorInvertCommand.java new file mode 100644 index 00000000..90076973 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/ValidatorInvertCommand.java @@ -0,0 +1,51 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestValidator; + +public class ValidatorInvertCommand extends TestSWCommand { + + public ValidatorInvertCommand() { + super("testvalidator"); + } + + @Register + public void test(@Validator(value = "Text", invert = true) String sender, boolean b, boolean b2) { + System.out.println("test: " + sender + " " + b + " " + b2); + throw new ExecutionIdentifier("RunTestInvert"); + } + + @Register + public void test(String sender, @Validator(value = "Text", invert = true) String h, int i) { + System.out.println("test: " + sender + " " + h + " " + i); + throw new ExecutionIdentifier("RunTestInvert2"); + } + + @Validator(value = "Text", local = true) + public TestValidator testValidator() { + return (sender, value, messageSender) -> { + System.out.println("testValidator: " + sender + " " + value + " " + messageSender); + return false; + }; + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/dto/ExecutionIdentifier.java b/CommandFramework/testsrc/de/steamwar/command/dto/ExecutionIdentifier.java new file mode 100644 index 00000000..d03b78f7 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/dto/ExecutionIdentifier.java @@ -0,0 +1,26 @@ +/* + * 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.dto; + +public class ExecutionIdentifier extends RuntimeException { + public ExecutionIdentifier(String message) { + super(message); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/dto/TestSWCommand.java b/CommandFramework/testsrc/de/steamwar/command/dto/TestSWCommand.java new file mode 100644 index 00000000..e0ac34b0 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/dto/TestSWCommand.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.dto; + +import de.steamwar.command.AbstractSWCommand; +import de.steamwar.command.CommandFrameworkException; + +import java.util.function.Supplier; + +public class TestSWCommand extends AbstractSWCommand { + + protected TestSWCommand(String command) { + super(String.class, command); + } + + protected TestSWCommand(String command, String[] aliases) { + super(String.class, command, aliases); + } + + @Override + protected void createAndSafeCommand(String command, String[] aliases) { + + } + + @Override + public void unregister() { + + } + + @Override + public void register() { + + } + + @Override + protected void commandSystemError(String sender, CommandFrameworkException e) { + System.out.println("CommandSystemError: " + e.getMessage()); + } + + @Override + protected void commandSystemWarning(Supplier message) { + System.out.println("CommandSystemWarning: " + message.get()); + } +} diff --git a/CommandFramework/testsrc/de/steamwar/command/dto/TestTypeMapper.java b/CommandFramework/testsrc/de/steamwar/command/dto/TestTypeMapper.java new file mode 100644 index 00000000..96c4c6c2 --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/dto/TestTypeMapper.java @@ -0,0 +1,25 @@ +/* + * 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.dto; + +import de.steamwar.command.AbstractTypeMapper; + +public interface TestTypeMapper extends AbstractTypeMapper { +} diff --git a/CommandFramework/testsrc/de/steamwar/command/dto/TestValidator.java b/CommandFramework/testsrc/de/steamwar/command/dto/TestValidator.java new file mode 100644 index 00000000..8cf0495c --- /dev/null +++ b/CommandFramework/testsrc/de/steamwar/command/dto/TestValidator.java @@ -0,0 +1,25 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 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.dto; + +import de.steamwar.command.AbstractValidator; + +public interface TestValidator extends AbstractValidator { +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9aae35e5..263416d1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,4 +19,5 @@ rootProject.name = "SteamWar" +include("CommandFramework") include("CommonCore")