From 4b8310d4f855d90de26ab4630e9e26e371557b31 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 17:14:01 +0100 Subject: [PATCH 01/12] Add ApplicableTypes for better type safety Add AbstractSWCommand.Min Add AbstractSWCommand.Max --- .../steamwar/command/AbstractSWCommand.java | 107 ++++++++++------- .../steamwar/command/AbstractValidator.java | 2 + ...elpException.java => ApplicableTypes.java} | 14 ++- src/de/steamwar/command/CommandPart.java | 40 +++---- src/de/steamwar/command/SWCommandUtils.java | 38 ------ src/de/steamwar/command/SubCommand.java | 112 +++++++++++++++++- .../command/BetterExceptionCommand.java | 1 + .../command/NumberValidatorCommand.java | 35 ++++++ .../command/NumberValidatorCommandTest.java | 51 ++++++++ .../de/steamwar/command/SimpleCommand.java | 3 +- .../steamwar/command/dto/TestSWCommand.java | 2 + 11 files changed, 294 insertions(+), 111 deletions(-) rename src/de/steamwar/command/{CommandNoHelpException.java => ApplicableTypes.java} (69%) create mode 100644 testsrc/de/steamwar/command/NumberValidatorCommand.java create mode 100644 testsrc/de/steamwar/command/NumberValidatorCommandTest.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 7e76d62..9ff2e28 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -35,7 +35,6 @@ public abstract class AbstractSWCommand { private boolean initialized = false; protected final List> commandList = new ArrayList<>(); - protected final List> commandHelpList = new ArrayList<>(); private final Map> localTypeMapper = new HashMap<>(); private final Map> localValidators = new HashMap<>(); @@ -109,15 +108,8 @@ public abstract class AbstractSWCommand { List errors = new ArrayList<>(); try { if (!commandList.stream().anyMatch(s -> s.invoke(errors::add, sender, alias, finalArgs))) { - if (!errors.isEmpty()) { - errors.forEach(Runnable::run); - return; - } - commandHelpList.stream().anyMatch(s -> s.invoke((ignore) -> { - }, sender, alias, finalArgs)); + errors.forEach(Runnable::run); } - } catch (CommandNoHelpException e) { - // Ignored } catch (CommandFrameworkException e) { commandSystemError(sender, e); throw e; @@ -139,12 +131,8 @@ public abstract class AbstractSWCommand { .collect(Collectors.toList()); } - private void initialize() { + private synchronized void initialize() { if (initialized) return; - createMapping(); - } - - private synchronized void createMapping() { List methods = methods(); for (Method method : methods) { Cached cached = method.getAnnotation(Cached.class); @@ -181,32 +169,13 @@ public abstract class AbstractSWCommand { } for (Method method : methods) { add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> { - if (!anno.help()) return; - boolean error = false; - if (parameters.length != 2) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking parameters or has too many"); - error = true; - } - if (!parameters[parameters.length - 1].isVarArgs()) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the varArgs parameters as last Argument"); - error = true; - } - if (parameters[parameters.length - 1].getType().getComponentType() != String.class) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the varArgs parameters of type '" + String.class.getTypeName() + "' as last Argument"); - error = true; - } - if (error) return; - commandHelpList.add(new SubCommand<>(this, method, anno.value(), new HashMap<>(), new HashMap<>(), true, null, anno.noTabComplete())); - }); - - add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> { - if (anno.help()) return; for (int i = 1; i < parameters.length; i++) { Parameter parameter = parameters[i]; Class clazz = parameter.getType(); if (parameter.isVarArgs() && i == parameters.length - 1) { clazz = parameter.getType().getComponentType(); } + checkAnnotationApplicability(parameter, clazz); Mapper mapper = parameter.getAnnotation(Mapper.class); if (clazz.isEnum() && mapper == null && !SWCommandUtils.getMAPPER_FUNCTIONS().containsKey(clazz.getTypeName())) { continue; @@ -217,7 +186,7 @@ public abstract class AbstractSWCommand { return; } } - commandList.add(new SubCommand<>(this, method, anno.value(), localTypeMapper, localValidators, false, anno.description(), anno.noTabComplete())); + commandList.add(new SubCommand<>(this, method, anno.value(), localTypeMapper, localValidators, anno.description(), anno.noTabComplete())); }); } @@ -229,18 +198,28 @@ public abstract class AbstractSWCommand { return Integer.compare(o1.comparableValue, o2.comparableValue); } }); - commandHelpList.sort((o1, o2) -> { - int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length); - if (compare != 0) { - return compare; - } else { - return Integer.compare(o1.method.getDeclaringClass() == AbstractSWCommand.class ? 1 : 0, - o2.method.getDeclaringClass() == AbstractSWCommand.class ? 1 : 0); - } - }); initialized = true; } + private void checkAnnotationApplicability(Parameter parameter, Class clazz) { + Annotation[] annotations = parameter.getAnnotations(); + for (Annotation annotation : annotations) { + ApplicableTypes applicableTypes = annotation.annotationType().getAnnotation(ApplicableTypes.class); + if (applicableTypes == null) continue; + Class[] types = applicableTypes.value(); + boolean applicable = false; + for (Class type : types) { + if (type.isAssignableFrom(clazz)) { + applicable = true; + break; + } + } + if (!applicable) { + commandSystemWarning(() -> "The parameter '" + parameter.toString() + "' is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); + } + } + } + private void add(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer consumer) { T[] anno = SWCommandUtils.getAnnotation(method, annotation); if (anno == null || anno.length == 0) return; @@ -300,12 +279,22 @@ public abstract class AbstractSWCommand { return methods; } + // --- Annotation for the command --- + + /** + * Annotation for registering a method as a command + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) protected @interface Register { + + /** + * Identifier of subcommand + */ String[] value() default {}; + @Deprecated boolean help() default false; String[] description() default {}; @@ -359,8 +348,11 @@ public abstract class AbstractSWCommand { boolean local() default false; } + // --- Implicit TypeMapper --- + @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) + @ApplicableTypes({String.class, int.class, Integer.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -391,6 +383,8 @@ public abstract class AbstractSWCommand { boolean onlyUINIG() default false; } + // --- Implicit Validator --- + @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) protected @interface ErrorMessage { @@ -415,6 +409,31 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) + @ApplicableTypes({String.class}) protected @interface Quotable { } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + 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; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + 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; + } } diff --git a/src/de/steamwar/command/AbstractValidator.java b/src/de/steamwar/command/AbstractValidator.java index aab4876..7dc0b72 100644 --- a/src/de/steamwar/command/AbstractValidator.java +++ b/src/de/steamwar/command/AbstractValidator.java @@ -38,10 +38,12 @@ public interface AbstractValidator { */ 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; diff --git a/src/de/steamwar/command/CommandNoHelpException.java b/src/de/steamwar/command/ApplicableTypes.java similarity index 69% rename from src/de/steamwar/command/CommandNoHelpException.java rename to src/de/steamwar/command/ApplicableTypes.java index e3d476a..2385b43 100644 --- a/src/de/steamwar/command/CommandNoHelpException.java +++ b/src/de/steamwar/command/ApplicableTypes.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2020 SteamWar.de-Serverteam + * 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 @@ -19,7 +19,13 @@ package de.steamwar.command; -class CommandNoHelpException extends RuntimeException { - - CommandNoHelpException() {} +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface ApplicableTypes { + Class[] value(); } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index a81c646..bb39119 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -24,6 +24,7 @@ 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; @@ -41,7 +42,7 @@ class CommandPart { private AbstractSWCommand command; private AbstractTypeMapper typeMapper; - private AbstractValidator validator; + private List> validators = new ArrayList<>(); private Class varArgType; private String optional; @@ -53,19 +54,12 @@ class CommandPart { @Setter private boolean onlyUseIfNoneIsGiven = false; - @Setter - private boolean allowNullValues = false; - - @Setter - private boolean quotable = false; - private Parameter parameter; private int parameterIndex; - public CommandPart(AbstractSWCommand command, AbstractTypeMapper typeMapper, AbstractValidator validator, Class varArgType, String optional, Parameter parameter, int parameterIndex) { + public CommandPart(AbstractSWCommand command, AbstractTypeMapper typeMapper, Class varArgType, String optional, Parameter parameter, int parameterIndex) { this.command = command; this.typeMapper = typeMapper; - this.validator = validator; this.varArgType = varArgType; this.optional = optional; this.parameter = parameter; @@ -74,6 +68,11 @@ class CommandPart { validatePart(); } + 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); @@ -97,10 +96,12 @@ class CommandPart { } Array.set(array, i - startIndex, validArgument.value); } - if (validator != null && !validator.validate(sender, array, (s, objects) -> { - errors.accept(() -> command.sendMessage(sender, s, objects)); - })) { - throw new CommandParseException(); + 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; @@ -136,7 +137,8 @@ class CommandPart { public void generateTabComplete(List current, T sender, String[] args, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length - 1; i++) { - CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, i); + CheckArgumentResult validArgument = checkArgument((ignore) -> { + }, sender, args, i); if (!validArgument.success) { return; } @@ -149,7 +151,8 @@ class CommandPart { } if (args.length - 1 > startIndex) { - CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, startIndex); + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> { + }, sender, args, startIndex); if (checkArgumentResult.success && next != null) { next.generateTabComplete(current, sender, args, startIndex + 1); return; @@ -186,10 +189,7 @@ class CommandPart { } catch (Exception e) { return new CheckArgumentResult(false, null); } - if (value instanceof String && !quotable && ((String) value).contains(" ")) { - return new CheckArgumentResult(false, null); - } - if (validator != null && errors != null) { + for (AbstractValidator validator : validators) { try { if (!validator.validate(sender, value, (s, objects) -> { errors.accept(() -> { @@ -202,6 +202,6 @@ class CommandPart { throw CommandFrameworkException.commandPartExceptions("validating", e, args[index], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); } } - return new CheckArgumentResult(allowNullValues || value != null, value); + return new CheckArgumentResult(true, value); } } diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 871439a..203bdd8 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -82,44 +82,6 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - static CommandPart generateCommandPart(AbstractSWCommand command, boolean help, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map> localValidator) { - CommandPart first = null; - CommandPart current = null; - for (String s : subCommand) { - CommandPart commandPart = new CommandPart(command, createMapper(s), null, null, null, null, -1); - 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 = getTypeMapper(parameter, localTypeMapper); - AbstractValidator validator = (AbstractValidator) getValidator(parameter, localValidator); - Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; - AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); - AbstractSWCommand.AllowNull allowNull = parameter.getAnnotation(AbstractSWCommand.AllowNull.class); - AbstractSWCommand.Quotable quotable = parameter.getAnnotation(AbstractSWCommand.Quotable.class); - - CommandPart commandPart = new CommandPart<>(command, typeMapper, validator, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); - commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); - commandPart.setAllowNullValues(allowNull != null); - commandPart.setQuotable(quotable != null); - if (current != null) { - current.setNext(commandPart); - } - current = commandPart; - if (first == null) { - first = current; - } - } - return first; - } - public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { Class clazz = parameter.getType(); if (parameter.isVarArgs()) { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 9eb6e4f..bf11674 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -43,7 +43,7 @@ public class SubCommand { private CommandPart commandPart; - SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localValidator, boolean help, String[] description, boolean noTabComplete) { + SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localValidator, String[] description, boolean noTabComplete) { this.abstractSWCommand = abstractSWCommand; this.method = method; try { @@ -60,7 +60,7 @@ public class SubCommand { validator = (AbstractValidator) SWCommandUtils.getValidator(parameters[0], localValidator); - commandPart = SWCommandUtils.generateCommandPart(abstractSWCommand, help, subCommand, parameters, localTypeMapper, localValidator); + commandPart = generateCommandPart(abstractSWCommand, subCommand, parameters, localTypeMapper, localValidator); senderPredicate = t -> parameters[0].getType().isAssignableFrom(t.getClass()); senderFunction = t -> parameters[0].getType().cast(t); @@ -97,7 +97,7 @@ public class SubCommand { objects.add(0, senderFunction.apply(sender)); method.invoke(abstractSWCommand, objects.toArray()); } - } catch (CommandNoHelpException | CommandFrameworkException e) { + } catch (CommandFrameworkException e) { throw e; } catch (CommandParseException e) { return false; @@ -124,4 +124,110 @@ public class SubCommand { commandPart.generateTabComplete(list, sender, args, 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 = SWCommandUtils.getTypeMapper(parameter, localTypeMapper); + AbstractValidator validator = (AbstractValidator) SWCommandUtils.getValidator(parameter, localValidator); + Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; + AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); + AbstractSWCommand.Min min = parameter.getAnnotation(AbstractSWCommand.Min.class); + AbstractSWCommand.Max max = parameter.getAnnotation(AbstractSWCommand.Max.class); + + CommandPart commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); + commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); + if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { + commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); + } + commandPart.addValidator(validator); + if (min != null) { + commandPart.addValidator((AbstractValidator) createMinValidator(varArgType != null ? varArgType : parameter.getType(), min)); + } + if (max != null) { + commandPart.addValidator((AbstractValidator) createMaxValidator(varArgType != null ? varArgType : parameter.getType(), max)); + } + 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 final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; + + private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { + if (!(value instanceof String)) return true; + String s = (String) value; + return !s.contains(" "); + }; + + private static AbstractValidator createMinValidator(Class clazz, AbstractSWCommand.Min min) { + Function comparator; + if (clazz == int.class || clazz == Integer.class) { + int minValue = min.intValue(); + comparator = number -> Integer.compare(number.intValue(), minValue); + } else if (clazz == long.class || clazz == Long.class) { + long minValue = min.longValue(); + comparator = number -> Long.compare(number.longValue(), minValue); + } else if (clazz == float.class || clazz == Float.class) { + float minValue = min.floatValue(); + comparator = number -> Float.compare(number.floatValue(), minValue); + } else if (clazz == double.class || clazz == Double.class) { + double minValue = min.doubleValue(); + comparator = number -> Double.compare(number.doubleValue(), minValue); + } else { + throw new IllegalArgumentException("Min annotation is not supported for " + clazz); + } + if (min.inclusive()) { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= 0; + } else { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() > 0; + } + } + + private static AbstractValidator createMaxValidator(Class clazz, AbstractSWCommand.Max max) { + Function comparator; + if (clazz == int.class || clazz == Integer.class) { + int minValue = max.intValue(); + comparator = number -> Integer.compare(number.intValue(), minValue); + } else if (clazz == long.class || clazz == Long.class) { + long minValue = max.longValue(); + comparator = number -> Long.compare(number.longValue(), minValue); + } else if (clazz == float.class || clazz == Float.class) { + float minValue = max.floatValue(); + comparator = number -> Float.compare(number.floatValue(), minValue); + } else if (clazz == double.class || clazz == Double.class) { + double minValue = max.doubleValue(); + comparator = number -> Double.compare(number.doubleValue(), minValue); + } else { + throw new IllegalArgumentException("Max annotation is not supported for " + clazz); + } + if (max.inclusive()) { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= 0; + } else { + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() < 0; + } + } } diff --git a/testsrc/de/steamwar/command/BetterExceptionCommand.java b/testsrc/de/steamwar/command/BetterExceptionCommand.java index cd38179..d014186 100644 --- a/testsrc/de/steamwar/command/BetterExceptionCommand.java +++ b/testsrc/de/steamwar/command/BetterExceptionCommand.java @@ -45,6 +45,7 @@ public class BetterExceptionCommand extends TestSWCommand { @Override public boolean validate(String sender, String value, MessageSender messageSender) { + System.out.println("Validate: " + value); throw new SecurityException(); } diff --git a/testsrc/de/steamwar/command/NumberValidatorCommand.java b/testsrc/de/steamwar/command/NumberValidatorCommand.java new file mode 100644 index 0000000..9ba53a6 --- /dev/null +++ b/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/testsrc/de/steamwar/command/NumberValidatorCommandTest.java b/testsrc/de/steamwar/command/NumberValidatorCommandTest.java new file mode 100644 index 0000000..c45b4a8 --- /dev/null +++ b/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/testsrc/de/steamwar/command/SimpleCommand.java b/testsrc/de/steamwar/command/SimpleCommand.java index 95aa43d..7388633 100644 --- a/testsrc/de/steamwar/command/SimpleCommand.java +++ b/testsrc/de/steamwar/command/SimpleCommand.java @@ -19,7 +19,6 @@ package de.steamwar.command; -import de.steamwar.command.AbstractSWCommand.Register; import de.steamwar.command.dto.ExecutionIdentifier; import de.steamwar.command.dto.TestSWCommand; @@ -29,7 +28,7 @@ public class SimpleCommand extends TestSWCommand { super("simple"); } - @Register(value = "a", help = true) + @Register(value = "a", noTabComplete = true) public void test(String s, String... varargs) { throw new ExecutionIdentifier("RunSimple with Varargs"); } diff --git a/testsrc/de/steamwar/command/dto/TestSWCommand.java b/testsrc/de/steamwar/command/dto/TestSWCommand.java index 5fd3b3f..e0ac34b 100644 --- a/testsrc/de/steamwar/command/dto/TestSWCommand.java +++ b/testsrc/de/steamwar/command/dto/TestSWCommand.java @@ -51,9 +51,11 @@ public class TestSWCommand extends AbstractSWCommand { @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()); } } From f2c0a2a16b98af98c90c6389dec656f137baf5d4 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 18:14:44 +0100 Subject: [PATCH 02/12] Add MethodMetaData and ParameterMetaData for better type safety --- .../steamwar/command/AbstractSWCommand.java | 121 ++++++++++-------- src/de/steamwar/command/MethodMetaData.java | 33 +++++ ...cableTypes.java => ParameterMetaData.java} | 4 +- 3 files changed, 103 insertions(+), 55 deletions(-) create mode 100644 src/de/steamwar/command/MethodMetaData.java rename src/de/steamwar/command/{ApplicableTypes.java => ParameterMetaData.java} (94%) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 9ff2e28..30af847 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -25,6 +25,7 @@ import java.lang.reflect.Parameter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.function.IntPredicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -133,49 +134,32 @@ public abstract class AbstractSWCommand { private synchronized void initialize() { if (initialized) return; - List methods = methods(); + List methods = methods().stream() + .filter(this::validateMethod) + .collect(Collectors.toList()); for (Method method : methods) { Cached cached = method.getAnnotation(Cached.class); - addMapper(Mapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> { + addMapper(Mapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); - if (anno.local()) { - localTypeMapper.putIfAbsent(anno.value(), (AbstractTypeMapper) typeMapper); - } else { - SWCommandUtils.getMAPPER_FUNCTIONS().putIfAbsent(anno.value(), typeMapper); - } + (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value(), typeMapper); }); - addMapper(ClassMapper.class, method, i -> i == 0, false, AbstractTypeMapper.class, (anno, typeMapper) -> { + addMapper(ClassMapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); - if (anno.local()) { - localTypeMapper.putIfAbsent(anno.value().getTypeName(), (AbstractTypeMapper) typeMapper); - } else { - SWCommandUtils.getMAPPER_FUNCTIONS().putIfAbsent(anno.value().getTypeName(), typeMapper); - } + (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value().getName(), typeMapper); }); - addValidator(Validator.class, method, i -> i == 0, false, AbstractValidator.class, (anno, validator) -> { - if (anno.local()) { - localValidators.putIfAbsent(anno.value(), (AbstractValidator) validator); - } else { - SWCommandUtils.getVALIDATOR_FUNCTIONS().putIfAbsent(anno.value(), validator); - } + addValidator(Validator.class, method, (anno, validator) -> { + (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value(), validator); }); - addValidator(ClassValidator.class, method, i -> i == 0, false, AbstractValidator.class, (anno, validator) -> { - if (anno.local()) { - localValidators.putIfAbsent(anno.value().getTypeName(), (AbstractValidator) validator); - } else { - SWCommandUtils.getVALIDATOR_FUNCTIONS().putIfAbsent(anno.value().getTypeName(), validator); - } + addValidator(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, i -> i > 0, true, null, (anno, parameters) -> { + 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() && i == parameters.length - 1) { - clazz = parameter.getType().getComponentType(); - } - checkAnnotationApplicability(parameter, clazz); + if (parameter.isVarArgs()) clazz = clazz.getComponentType(); Mapper mapper = parameter.getAnnotation(Mapper.class); if (clazz.isEnum() && mapper == null && !SWCommandUtils.getMAPPER_FUNCTIONS().containsKey(clazz.getTypeName())) { continue; @@ -201,12 +185,42 @@ public abstract class AbstractSWCommand { initialized = true; } - private void checkAnnotationApplicability(Parameter parameter, Class clazz) { - Annotation[] annotations = parameter.getAnnotations(); + private boolean validateMethod(Method method) { + if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { + MethodMetaData methodMetaData = annotation.annotationType().getAnnotation(MethodMetaData.class); + if (methodMetaData == null) return null; + if (method.getParameterCount() > methodMetaData.maxParameterCount()) { + return new Class[0]; + } + if (method.getParameterCount() < methodMetaData.minParameterCount()) { + return new Class[0]; + } + return methodMetaData.possibleReturnTypes(); + }, "The method '" + method + "'")) { + return false; + } + boolean valid = true; + for (Parameter parameter : method.getParameters()) { + Class type = parameter.getType(); + if (parameter.isVarArgs()) { + type = type.getComponentType(); + } + if (!checkType(parameter.getAnnotations(), type, annotation -> { + ParameterMetaData parameterMetaData = annotation.annotationType().getAnnotation(ParameterMetaData.class); + if (parameterMetaData == null) return null; + return parameterMetaData.possibleTypes(); + }, "The parameter '" + parameter + "'")) { + valid = false; + } + } + return valid; + } + + private boolean checkType(Annotation[] annotations, Class clazz, Function[]> toApplicableTypes, String warning) { + boolean valid = true; for (Annotation annotation : annotations) { - ApplicableTypes applicableTypes = annotation.annotationType().getAnnotation(ApplicableTypes.class); - if (applicableTypes == null) continue; - Class[] types = applicableTypes.value(); + Class[] types = toApplicableTypes.apply(annotation); + if (types == null) continue; boolean applicable = false; for (Class type : types) { if (type.isAssignableFrom(clazz)) { @@ -215,33 +229,27 @@ public abstract class AbstractSWCommand { } } if (!applicable) { - commandSystemWarning(() -> "The parameter '" + parameter.toString() + "' is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); + commandSystemWarning(() -> warning + " is using an unsupported annotation of type '" + annotation.annotationType().getName() + "'"); + valid = false; } } + return valid; } - private void add(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer consumer) { + 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 (!parameterTester.test(parameters.length)) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking parameters or has too many"); - return; - } if (firstParameter && !clazz.isAssignableFrom(parameters[0].getType())) { commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the first parameter of type '" + clazz.getTypeName() + "'"); return; } - if (returnType != null && !returnType.isAssignableFrom(method.getReturnType())) { - commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the desired return type '" + returnType.getTypeName() + "'"); - return; - } Arrays.stream(anno).forEach(t -> consumer.accept(t, parameters)); } - private void addMapper(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { - add(annotation, method, parameterTester, firstParameter, returnType, (anno, parameters) -> { + private void addMapper(Class annotation, Method method, BiConsumer> consumer) { + add(annotation, method, false, (anno, parameters) -> { try { method.setAccessible(true); consumer.accept(anno, (AbstractTypeMapper) method.invoke(this)); @@ -251,8 +259,8 @@ public abstract class AbstractSWCommand { }); } - private void addValidator(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { - add(annotation, method, parameterTester, firstParameter, returnType, (anno, parameters) -> { + private void addValidator(Class annotation, Method method, BiConsumer> consumer) { + add(annotation, method, false, (anno, parameters) -> { try { method.setAccessible(true); consumer.accept(anno, (AbstractValidator) method.invoke(this)); @@ -287,6 +295,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) + @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) protected @interface Register { /** @@ -303,6 +312,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) @interface Registeres { Register[] value(); } @@ -310,6 +320,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) protected @interface Mapper { String value(); @@ -318,6 +329,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) protected @interface ClassMapper { Class value(); @@ -326,6 +338,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) protected @interface Cached { long cacheDuration() default 5; TimeUnit timeUnit() default TimeUnit.SECONDS; @@ -334,6 +347,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) protected @interface Validator { String value() default ""; @@ -342,6 +356,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) + @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) protected @interface ClassValidator { Class value(); @@ -352,7 +367,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({String.class, int.class, Integer.class, boolean.class, Boolean.class}) + @ParameterMetaData(possibleTypes = {String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -409,13 +424,13 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({String.class}) + @ParameterMetaData(possibleTypes = {String.class}) protected @interface Quotable { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -427,7 +442,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ApplicableTypes({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; diff --git a/src/de/steamwar/command/MethodMetaData.java b/src/de/steamwar/command/MethodMetaData.java new file mode 100644 index 0000000..4975ca8 --- /dev/null +++ b/src/de/steamwar/command/MethodMetaData.java @@ -0,0 +1,33 @@ +/* + * 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.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +@interface MethodMetaData { + Class[] possibleReturnTypes(); + int minParameterCount() default 0; + int maxParameterCount() default Integer.MAX_VALUE; +} diff --git a/src/de/steamwar/command/ApplicableTypes.java b/src/de/steamwar/command/ParameterMetaData.java similarity index 94% rename from src/de/steamwar/command/ApplicableTypes.java rename to src/de/steamwar/command/ParameterMetaData.java index 2385b43..c7c0467 100644 --- a/src/de/steamwar/command/ApplicableTypes.java +++ b/src/de/steamwar/command/ParameterMetaData.java @@ -26,6 +26,6 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) -@interface ApplicableTypes { - Class[] value(); +@interface ParameterMetaData { + Class[] possibleTypes(); } From 19e4949048ecaf7bb40159ca600ff6b3ab626eb7 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 18:37:10 +0100 Subject: [PATCH 03/12] Add MetaData to bundle metadata annotations --- .../steamwar/command/AbstractSWCommand.java | 80 +++++++------------ .../{MethodMetaData.java => MetaData.java} | 21 +++-- .../steamwar/command/ParameterMetaData.java | 31 ------- 3 files changed, 42 insertions(+), 90 deletions(-) rename src/de/steamwar/command/{MethodMetaData.java => MetaData.java} (70%) delete mode 100644 src/de/steamwar/command/ParameterMetaData.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 30af847..8ab7cbd 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -26,7 +26,6 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Function; -import java.util.function.IntPredicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -139,18 +138,18 @@ public abstract class AbstractSWCommand { .collect(Collectors.toList()); for (Method method : methods) { Cached cached = method.getAnnotation(Cached.class); - addMapper(Mapper.class, method, (anno, typeMapper) -> { + this.>add(Mapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value(), typeMapper); }); - addMapper(ClassMapper.class, method, (anno, typeMapper) -> { + this.>add(ClassMapper.class, method, (anno, typeMapper) -> { TabCompletionCache.add(typeMapper, cached); (anno.local() ? ((Map) localTypeMapper) : SWCommandUtils.getMAPPER_FUNCTIONS()).put(anno.value().getName(), typeMapper); }); - addValidator(Validator.class, method, (anno, validator) -> { + this.>add(Validator.class, method, (anno, validator) -> { (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value(), validator); }); - addValidator(ClassValidator.class, method, (anno, validator) -> { + this.>add(ClassValidator.class, method, (anno, validator) -> { (anno.local() ? ((Map) localValidators) : SWCommandUtils.getVALIDATOR_FUNCTIONS()).put(anno.value().getName(), validator); }); } @@ -176,42 +175,28 @@ public abstract class AbstractSWCommand { this.commandList.sort((o1, o2) -> { int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length); - if (compare != 0) { - return compare; - } else { - return Integer.compare(o1.comparableValue, o2.comparableValue); - } + if (compare == 0) return Integer.compare(o1.comparableValue, o2.comparableValue); + return compare; }); initialized = true; } private boolean validateMethod(Method method) { if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { - MethodMetaData methodMetaData = annotation.annotationType().getAnnotation(MethodMetaData.class); + MetaData.Method methodMetaData = annotation.annotationType().getAnnotation(MetaData.Method.class); if (methodMetaData == null) return null; - if (method.getParameterCount() > methodMetaData.maxParameterCount()) { - return new Class[0]; - } - if (method.getParameterCount() < methodMetaData.minParameterCount()) { - return new Class[0]; - } - return methodMetaData.possibleReturnTypes(); - }, "The method '" + method + "'")) { - return false; - } + if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0]; + return methodMetaData.value(); + }, "The method '" + method + "'")) return false; boolean valid = true; for (Parameter parameter : method.getParameters()) { Class type = parameter.getType(); - if (parameter.isVarArgs()) { - type = type.getComponentType(); - } + if (parameter.isVarArgs()) type = type.getComponentType(); if (!checkType(parameter.getAnnotations(), type, annotation -> { - ParameterMetaData parameterMetaData = annotation.annotationType().getAnnotation(ParameterMetaData.class); + MetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(MetaData.Parameter.class); if (parameterMetaData == null) return null; - return parameterMetaData.possibleTypes(); - }, "The parameter '" + parameter + "'")) { - valid = false; - } + return parameterMetaData.value(); + }, "The parameter '" + parameter + "'")) valid = false; } return valid; } @@ -248,22 +233,11 @@ public abstract class AbstractSWCommand { Arrays.stream(anno).forEach(t -> consumer.accept(t, parameters)); } - private void addMapper(Class annotation, Method method, BiConsumer> consumer) { + private void add(Class annotation, Method method, BiConsumer consumer) { add(annotation, method, false, (anno, parameters) -> { try { method.setAccessible(true); - consumer.accept(anno, (AbstractTypeMapper) method.invoke(this)); - } catch (Exception e) { - throw new SecurityException(e.getMessage(), e); - } - }); - } - - private void addValidator(Class annotation, Method method, BiConsumer> consumer) { - add(annotation, method, false, (anno, parameters) -> { - try { - method.setAccessible(true); - consumer.accept(anno, (AbstractValidator) method.invoke(this)); + consumer.accept(anno, (K) method.invoke(this)); } catch (Exception e) { throw new SecurityException(e.getMessage(), e); } @@ -295,7 +269,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) - @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) + @MetaData.Method(value = void.class, minParameterCount = 1) protected @interface Register { /** @@ -312,7 +286,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = void.class, minParameterCount = 1) + @MetaData.Method(value = void.class, minParameterCount = 1) @interface Registeres { Register[] value(); } @@ -320,7 +294,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Mapper { String value(); @@ -329,7 +303,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface ClassMapper { Class value(); @@ -338,7 +312,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractTypeMapper.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Cached { long cacheDuration() default 5; TimeUnit timeUnit() default TimeUnit.SECONDS; @@ -347,7 +321,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface Validator { String value() default ""; @@ -356,7 +330,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MethodMetaData(possibleReturnTypes = AbstractValidator.class, minParameterCount = 0, maxParameterCount = 0) + @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface ClassValidator { Class value(); @@ -367,7 +341,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) + @MetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -424,13 +398,13 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {String.class}) + @MetaData.Parameter({String.class}) protected @interface Quotable { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -442,7 +416,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @ParameterMetaData(possibleTypes = {int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; diff --git a/src/de/steamwar/command/MethodMetaData.java b/src/de/steamwar/command/MetaData.java similarity index 70% rename from src/de/steamwar/command/MethodMetaData.java rename to src/de/steamwar/command/MetaData.java index 4975ca8..5745fc4 100644 --- a/src/de/steamwar/command/MethodMetaData.java +++ b/src/de/steamwar/command/MetaData.java @@ -24,10 +24,19 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.ANNOTATION_TYPE) -@interface MethodMetaData { - Class[] possibleReturnTypes(); - int minParameterCount() default 0; - int maxParameterCount() default Integer.MAX_VALUE; +@interface MetaData { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface Method { + Class[] value(); + int minParameterCount() default 0; + int maxParameterCount() default Integer.MAX_VALUE; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @interface Parameter { + Class[] value(); + } } diff --git a/src/de/steamwar/command/ParameterMetaData.java b/src/de/steamwar/command/ParameterMetaData.java deleted file mode 100644 index c7c0467..0000000 --- a/src/de/steamwar/command/ParameterMetaData.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.ANNOTATION_TYPE) -@interface ParameterMetaData { - Class[] possibleTypes(); -} From 2e9e4e2e49645207f11da6332e687165a98b7c7a Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 21:36:46 +0100 Subject: [PATCH 04/12] Fix ErrorMessage annotation and ClassValidator never being present on Parameter --- src/de/steamwar/command/SWCommandUtils.java | 11 +++-------- src/de/steamwar/command/SubCommand.java | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 203bdd8..2f16d7a 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -135,14 +135,6 @@ public class SWCommandUtils { public static AbstractValidator getValidator(Parameter parameter, Map> localValidator) { Class clazz = parameter.getType(); - AbstractSWCommand.ClassValidator classValidator = parameter.getAnnotation(AbstractSWCommand.ClassValidator.class); - if (classValidator != null) { - if (classValidator.value() != null) { - return getValidator(classValidator.value().getTypeName(), localValidator); - } - return getValidator(clazz.getTypeName(), localValidator); - } - AbstractSWCommand.Validator validator = parameter.getAnnotation(AbstractSWCommand.Validator.class); if (validator != null) { if (validator.value() != null && !validator.value().isEmpty()) { @@ -150,7 +142,10 @@ public class SWCommandUtils { } return getValidator(clazz.getTypeName(), localValidator); } + return null; + } + public static AbstractValidator getErrorMessage(Parameter parameter) { AbstractSWCommand.ErrorMessage errorMessage = parameter.getAnnotation(AbstractSWCommand.ErrorMessage.class); if (errorMessage != null) { return (AbstractValidator) (sender, value, messageSender) -> { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index bf11674..0e75d7b 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -155,6 +155,7 @@ public class SubCommand { commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); } commandPart.addValidator(validator); + commandPart.addValidator((AbstractValidator) SWCommandUtils.getErrorMessage(parameter)); if (min != null) { commandPart.addValidator((AbstractValidator) createMinValidator(varArgType != null ? varArgType : parameter.getType(), min)); } From 518207f343ae8fa054eaa6efe25bbd59a8d23b79 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 18 Dec 2022 21:49:36 +0100 Subject: [PATCH 05/12] Optimize some code --- src/de/steamwar/command/CommandPart.java | 28 ++++---------- src/de/steamwar/command/SubCommand.java | 47 +++++++----------------- 2 files changed, 21 insertions(+), 54 deletions(-) diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index bb39119..dc8c6a9 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -65,7 +65,9 @@ class CommandPart { this.parameter = parameter; this.parameterIndex = parameterIndex; - validatePart(); + 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) { @@ -80,28 +82,18 @@ class CommandPart { this.next = next; } - private void validatePart() { - if (optional != null && varArgType != null) { - throw new IllegalArgumentException("A vararg part can't have an optional part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); - } - } - 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, i); - if (!validArgument.success) { - throw new CommandParseException(); - } + 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(); - } + })) throw new CommandParseException(); } current.add(array); return; @@ -137,11 +129,8 @@ class CommandPart { public void generateTabComplete(List current, T sender, String[] args, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length - 1; i++) { - CheckArgumentResult validArgument = checkArgument((ignore) -> { - }, sender, args, i); - if (!validArgument.success) { - return; - } + CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, i); + if (!validArgument.success) return; } Collection strings = tabCompletes(sender, args, args.length - 1); if (strings != null) { @@ -151,8 +140,7 @@ class CommandPart { } if (args.length - 1 > startIndex) { - CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> { - }, sender, args, startIndex); + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, startIndex); if (checkArgumentResult.success && next != null) { next.generateTabComplete(current, sender, args, startIndex + 1); return; diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 0e75d7b..9084fe7 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -110,12 +110,8 @@ public class SubCommand { } List tabComplete(T sender, String[] args) { - if (validator != null) { - if (!validator.validate(sender, sender, (s, objects) -> { - // ignore - })) { - return null; - } + if (validator != null && !validator.validate(sender, sender, (s, objects) -> {})) { + return null; } if (commandPart == null) { return null; @@ -180,55 +176,38 @@ public class SubCommand { private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { if (!(value instanceof String)) return true; - String s = (String) value; - return !s.contains(" "); + return !((String) value).contains(" "); }; private static AbstractValidator createMinValidator(Class clazz, AbstractSWCommand.Min min) { Function comparator; if (clazz == int.class || clazz == Integer.class) { - int minValue = min.intValue(); - comparator = number -> Integer.compare(number.intValue(), minValue); + comparator = number -> Integer.compare(number.intValue(), min.intValue()); } else if (clazz == long.class || clazz == Long.class) { - long minValue = min.longValue(); - comparator = number -> Long.compare(number.longValue(), minValue); + comparator = number -> Long.compare(number.longValue(), min.longValue()); } else if (clazz == float.class || clazz == Float.class) { - float minValue = min.floatValue(); - comparator = number -> Float.compare(number.floatValue(), minValue); + comparator = number -> Float.compare(number.floatValue(), min.floatValue()); } else if (clazz == double.class || clazz == Double.class) { - double minValue = min.doubleValue(); - comparator = number -> Double.compare(number.doubleValue(), minValue); + comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); } else { throw new IllegalArgumentException("Min annotation is not supported for " + clazz); } - if (min.inclusive()) { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= 0; - } else { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() > 0; - } + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= (min.inclusive() ? 0 : 1); } private static AbstractValidator createMaxValidator(Class clazz, AbstractSWCommand.Max max) { Function comparator; if (clazz == int.class || clazz == Integer.class) { - int minValue = max.intValue(); - comparator = number -> Integer.compare(number.intValue(), minValue); + comparator = number -> Integer.compare(number.intValue(), max.intValue()); } else if (clazz == long.class || clazz == Long.class) { - long minValue = max.longValue(); - comparator = number -> Long.compare(number.longValue(), minValue); + comparator = number -> Long.compare(number.longValue(), max.longValue()); } else if (clazz == float.class || clazz == Float.class) { - float minValue = max.floatValue(); - comparator = number -> Float.compare(number.floatValue(), minValue); + comparator = number -> Float.compare(number.floatValue(), max.floatValue()); } else if (clazz == double.class || clazz == Double.class) { - double minValue = max.doubleValue(); - comparator = number -> Double.compare(number.doubleValue(), minValue); + comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); } else { throw new IllegalArgumentException("Max annotation is not supported for " + clazz); } - if (max.inclusive()) { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= 0; - } else { - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() < 0; - } + return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= (max.inclusive() ? 0 : -1); } } From bd7635da0d686565fd45496e9966f14d336e3f1f Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 11:23:58 +0100 Subject: [PATCH 06/12] Add CommandMetaData.ImplicitValidator for easier extension of implicit validators --- .../steamwar/command/AbstractSWCommand.java | 29 ++--- .../steamwar/command/AbstractTypeMapper.java | 2 + .../{MetaData.java => CommandMetaData.java} | 29 ++++- src/de/steamwar/command/CommandPart.java | 4 + .../command/ImplicitTypeValidators.java | 102 ++++++++++++++++++ src/de/steamwar/command/SubCommand.java | 48 +++++++-- 6 files changed, 192 insertions(+), 22 deletions(-) rename src/de/steamwar/command/{MetaData.java => CommandMetaData.java} (54%) create mode 100644 src/de/steamwar/command/ImplicitTypeValidators.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 8ab7cbd..2793c3c 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -183,7 +183,7 @@ public abstract class AbstractSWCommand { private boolean validateMethod(Method method) { if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { - MetaData.Method methodMetaData = annotation.annotationType().getAnnotation(MetaData.Method.class); + CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class); if (methodMetaData == null) return null; if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0]; return methodMetaData.value(); @@ -193,7 +193,7 @@ public abstract class AbstractSWCommand { Class type = parameter.getType(); if (parameter.isVarArgs()) type = type.getComponentType(); if (!checkType(parameter.getAnnotations(), type, annotation -> { - MetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(MetaData.Parameter.class); + CommandMetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Parameter.class); if (parameterMetaData == null) return null; return parameterMetaData.value(); }, "The parameter '" + parameter + "'")) valid = false; @@ -269,7 +269,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Repeatable(Register.Registeres.class) - @MetaData.Method(value = void.class, minParameterCount = 1) + @CommandMetaData.Method(value = void.class, minParameterCount = 1) protected @interface Register { /** @@ -286,7 +286,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = void.class, minParameterCount = 1) + @CommandMetaData.Method(value = void.class, minParameterCount = 1) @interface Registeres { Register[] value(); } @@ -294,7 +294,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Mapper { String value(); @@ -303,7 +303,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface ClassMapper { Class value(); @@ -312,7 +312,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) protected @interface Cached { long cacheDuration() default 5; TimeUnit timeUnit() default TimeUnit.SECONDS; @@ -321,7 +321,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) - @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface Validator { String value() default ""; @@ -330,7 +330,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) - @MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) + @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) protected @interface ClassValidator { Class value(); @@ -341,7 +341,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) + @CommandMetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) protected @interface StaticValue { String[] value(); @@ -376,6 +376,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) + @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.ErrorMessageValidator.class, order = 1) protected @interface ErrorMessage { /** * Error message to be displayed when the parameter is invalid. @@ -398,13 +399,14 @@ public abstract class AbstractSWCommand { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({String.class}) + @CommandMetaData.Parameter({String.class}) protected @interface Quotable { } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MinValidator.class, order = 2) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -416,7 +418,8 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) + @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MaxValidator.class, order = 2) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index 4a340d8..1c8550b 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -25,6 +25,8 @@ public interface AbstractTypeMapper extends AbstractValidator { /** * The CommandSender can be null! */ + // TODO: Change the 'previousArguments' to List or something like that + // TODO: Change T return value to Pair or something like that. This would make OptionalValue easier to implement as the Boolean would indicate if s should be consumed or not T map(K sender, String[] previousArguments, String s); @Override diff --git a/src/de/steamwar/command/MetaData.java b/src/de/steamwar/command/CommandMetaData.java similarity index 54% rename from src/de/steamwar/command/MetaData.java rename to src/de/steamwar/command/CommandMetaData.java index 5745fc4..c7f79c7 100644 --- a/src/de/steamwar/command/MetaData.java +++ b/src/de/steamwar/command/CommandMetaData.java @@ -24,8 +24,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@interface MetaData { +public @interface CommandMetaData { + /** + * This annotation is only for internal use. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) @interface Method { @@ -34,9 +37,33 @@ import java.lang.annotation.Target; 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(); } + + /** + * This annotation can be used in conjunction with a class that implement {@link AbstractValidator} to + * create custom validator short hands for commands. The validator class itself should contain a constructor + * with two parameters the first is the annotation this annotation is used on and the second is of type {@link Class}. + */ + @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/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index dc8c6a9..2969872 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -192,4 +192,8 @@ class CommandPart { } return new CheckArgumentResult(true, value); } + + public Class getType() { + return varArgType != null ? varArgType : parameter.getType(); + } } diff --git a/src/de/steamwar/command/ImplicitTypeValidators.java b/src/de/steamwar/command/ImplicitTypeValidators.java new file mode 100644 index 0000000..7204ce5 --- /dev/null +++ b/src/de/steamwar/command/ImplicitTypeValidators.java @@ -0,0 +1,102 @@ +/* + * 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.experimental.UtilityClass; + +import java.lang.reflect.Array; +import java.util.function.Function; + +@UtilityClass +public class ImplicitTypeValidators { + + public static class ErrorMessageValidator implements AbstractValidator { + + private AbstractSWCommand.ErrorMessage errorMessage; + + public ErrorMessageValidator(AbstractSWCommand.ErrorMessage errorMessage, Class type) { + 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; + } + } + + public static class MinValidator implements AbstractValidator { + + private int value; + private Function comparator; + + public MinValidator(AbstractSWCommand.Min min, Class clazz) { + this.value = min.inclusive() ? 0 : 1; + + if (clazz == int.class || clazz == Integer.class) { + comparator = number -> Integer.compare(number.intValue(), min.intValue()); + } else if (clazz == long.class || clazz == Long.class) { + comparator = number -> Long.compare(number.longValue(), min.longValue()); + } else if (clazz == float.class || clazz == Float.class) { + comparator = number -> Float.compare(number.floatValue(), min.floatValue()); + } else if (clazz == double.class || clazz == Double.class) { + comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); + } else { + throw new IllegalArgumentException("Min annotation is not supported for " + clazz); + } + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + return (comparator.apply(value).intValue()) >= this.value; + } + } + + public static class MaxValidator implements AbstractValidator { + + private int value; + private Function comparator; + + public MaxValidator(AbstractSWCommand.Max max, Class clazz) { + this.value = max.inclusive() ? 0 : -1; + + if (clazz == int.class || clazz == Integer.class) { + comparator = number -> Integer.compare(number.intValue(), max.intValue()); + } else if (clazz == long.class || clazz == Long.class) { + comparator = number -> Long.compare(number.longValue(), max.longValue()); + } else if (clazz == float.class || clazz == Float.class) { + comparator = number -> Float.compare(number.floatValue(), max.floatValue()); + } else if (clazz == double.class || clazz == Double.class) { + comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); + } else { + throw new IllegalArgumentException("Max annotation is not supported for " + clazz); + } + } + + @Override + public boolean validate(T sender, Number value, MessageSender messageSender) { + return (comparator.apply(value).intValue()) <= this.value; + } + } +} diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 9084fe7..da3576e 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -19,10 +19,13 @@ 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; @@ -142,22 +145,16 @@ public class SubCommand { AbstractValidator validator = (AbstractValidator) SWCommandUtils.getValidator(parameter, localValidator); Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); - AbstractSWCommand.Min min = parameter.getAnnotation(AbstractSWCommand.Min.class); - AbstractSWCommand.Max max = parameter.getAnnotation(AbstractSWCommand.Max.class); CommandPart commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); } + handleImplicitTypeValidator(parameter, commandPart, true); commandPart.addValidator(validator); commandPart.addValidator((AbstractValidator) SWCommandUtils.getErrorMessage(parameter)); - if (min != null) { - commandPart.addValidator((AbstractValidator) createMinValidator(varArgType != null ? varArgType : parameter.getType(), min)); - } - if (max != null) { - commandPart.addValidator((AbstractValidator) createMaxValidator(varArgType != null ? varArgType : parameter.getType(), max)); - } + handleImplicitTypeValidator(parameter, commandPart, false); if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { commandPart.addValidator((AbstractValidator) NULL_FILTER); } @@ -172,6 +169,41 @@ public class SubCommand { return first; } + private static void handleImplicitTypeValidator(Parameter parameter, CommandPart commandPart, boolean beforeValidatorAnnotation) { + 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; + if (beforeValidatorAnnotation && implicitValidator.order() >= 0) continue; + if (!beforeValidatorAnnotation && implicitValidator.order() <= 0) continue; + Class clazz = implicitValidator.handler(); + if (!AbstractValidator.class.isAssignableFrom(clazz)) continue; + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) continue; + Constructor constructor = constructors[0]; + Class[] parameterTypes = constructor.getParameterTypes(); + if (parameterTypes.length != 2) continue; + AbstractValidator validator; + if (!annotation.annotationType().isAssignableFrom(parameterTypes[0])) continue; + if (!Class.class.isAssignableFrom(parameterTypes[1])) continue; + try { + validator = (AbstractValidator) constructor.newInstance(annotation, commandPart.getType()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + 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 final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { From d62dd7614a352cc58fa4030b5d244349aa375a4a Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sun, 15 Jan 2023 12:30:38 +0100 Subject: [PATCH 07/12] Adjust CI config to new CI --- steamwarci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/steamwarci.yml b/steamwarci.yml index 73f8039..47930f4 100644 --- a/steamwarci.yml +++ b/steamwarci.yml @@ -1,5 +1,6 @@ -build: +setup: - "ln -s /home/gitea/lib" - - "cp ~/gradle.properties ." - - "chmod u+x build.gradle" + +build: - "./gradlew buildProject" + - "./gradlew --stop" From bf1dde08c2294a8877c908f0b8cc74dc7886434f Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sun, 15 Jan 2023 12:30:57 +0100 Subject: [PATCH 08/12] Fix build.gradle --- build.gradle | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build.gradle diff --git a/build.gradle b/build.gradle old mode 100644 new mode 100755 From dbd91bf41a95d138679112b18ebb3582be2606c8 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sun, 15 Jan 2023 12:34:18 +0100 Subject: [PATCH 09/12] Fix closing connection on invalid connection --- src/de/steamwar/sql/internal/Statement.java | 32 ++++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/de/steamwar/sql/internal/Statement.java b/src/de/steamwar/sql/internal/Statement.java index 6c44332..ded799c 100644 --- a/src/de/steamwar/sql/internal/Statement.java +++ b/src/de/steamwar/sql/internal/Statement.java @@ -157,32 +157,36 @@ public class Statement implements AutoCloseable { private T withConnection(SQLRunnable runnable, Object... objects) { Connection connection = aquireConnection(); + T result; try { - try { - return tryWithConnection(connection, runnable, objects); - } finally { - if(connectionInvalid(connection)) { - closeConnection(connection); - } else { - synchronized (connections) { - connections.push(connection); - connections.notify(); - } - } - } - } catch (SQLException e) { + result = tryWithConnection(connection, runnable, objects); + } catch (Throwable e) { if(connectionInvalid(connection)) { + closeConnection(connection); + return withConnection(runnable, objects); } else { + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + throw new SecurityException("Failing sql statement", e); } } + + synchronized (connections) { + connections.push(connection); + connections.notify(); + } + + return result; } private boolean connectionInvalid(Connection connection) { try { - return connection.isClosed(); + return connection.isClosed() || !connection.isValid(1); } catch (SQLException e) { logger.log(Level.INFO, "Could not check SQL connection status", e); // No database logging possible at this state return true; From 7e67f745718b38d3cfff2995656becc943d21ccd Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 16:40:39 +0100 Subject: [PATCH 10/12] Add PreviousArguments for better handling in AbstractTypeMapper --- .../steamwar/command/AbstractSWCommand.java | 160 +++++++++++++++++- .../steamwar/command/AbstractTypeMapper.java | 23 ++- .../command/CommandFrameworkException.java | 6 - src/de/steamwar/command/CommandMetaData.java | 31 +++- src/de/steamwar/command/CommandPart.java | 38 +++-- .../command/ImplicitTypeValidators.java | 102 ----------- .../steamwar/command/PreviousArguments.java | 39 +++++ src/de/steamwar/command/SWCommandUtils.java | 84 ++------- src/de/steamwar/command/SubCommand.java | 141 +++++++++------ .../command/BetterExceptionCommand.java | 4 +- testsrc/de/steamwar/command/CacheCommand.java | 4 +- .../steamwar/command/NullMapperCommand.java | 4 +- 12 files changed, 370 insertions(+), 266 deletions(-) delete mode 100644 src/de/steamwar/command/ImplicitTypeValidators.java create mode 100644 src/de/steamwar/command/PreviousArguments.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 2793c3c..bf65ec8 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -20,6 +20,7 @@ package de.steamwar.command; import java.lang.annotation.*; +import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; @@ -295,10 +296,35 @@ public abstract class AbstractSWCommand { @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) @@ -322,10 +348,25 @@ public abstract class AbstractSWCommand { @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; + + class Handler implements AbstractValidator { + + private AbstractValidator inner; + + public Handler(AbstractSWCommand.Validator validator, Class clazz, Map> localValidator) { + inner = (AbstractValidator) SWCommandUtils.getValidator(validator, clazz, localValidator); + } + + @Override + public boolean validate(T sender, Object value, MessageSender messageSender) { + return inner.validate(sender, value, messageSender); + } + } } @Retention(RetentionPolicy.RUNTIME) @@ -342,6 +383,7 @@ public abstract class AbstractSWCommand { @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(); @@ -356,6 +398,53 @@ public abstract class AbstractSWCommand { 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 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) @@ -376,7 +465,7 @@ public abstract class AbstractSWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) - @CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.ErrorMessageValidator.class, order = 1) + @CommandMetaData.ImplicitValidator(handler = ErrorMessage.Handler.class, order = 1) protected @interface ErrorMessage { /** * Error message to be displayed when the parameter is invalid. @@ -387,6 +476,25 @@ public abstract class AbstractSWCommand { * 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) @@ -406,7 +514,7 @@ public abstract class AbstractSWCommand { @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 = ImplicitTypeValidators.MinValidator.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = Min.Handler.class, order = 2) protected @interface Min { int intValue() default Integer.MIN_VALUE; long longValue() default Long.MIN_VALUE; @@ -414,12 +522,28 @@ public abstract class AbstractSWCommand { 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) { + 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 = ImplicitTypeValidators.MaxValidator.class, order = 2) + @CommandMetaData.ImplicitValidator(handler = Max.Handler.class, order = 2) protected @interface Max { int intValue() default Integer.MAX_VALUE; long longValue() default Long.MAX_VALUE; @@ -427,5 +551,35 @@ public abstract class AbstractSWCommand { 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) { + 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); + } } } diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index 1c8550b..0363cd0 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -25,14 +25,29 @@ public interface AbstractTypeMapper extends AbstractValidator { /** * The CommandSender can be null! */ - // TODO: Change the 'previousArguments' to List or something like that - // TODO: Change T return value to Pair or something like that. This would make OptionalValue easier to implement as the Boolean would indicate if s should be consumed or not - T map(K sender, String[] previousArguments, String s); + @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; } - Collection tabCompletes(K sender, String[] previousArguments, String s); + @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); + } } diff --git a/src/de/steamwar/command/CommandFrameworkException.java b/src/de/steamwar/command/CommandFrameworkException.java index 53ee025..ea3d835 100644 --- a/src/de/steamwar/command/CommandFrameworkException.java +++ b/src/de/steamwar/command/CommandFrameworkException.java @@ -36,12 +36,6 @@ public class CommandFrameworkException extends RuntimeException { private String message; - static CommandFrameworkException commandGetExceptions(String type, Class clazzType, Executable executable, int index) { - return new CommandFrameworkException(throwable -> { - return CommandFrameworkException.class.getTypeName() + ": Error while getting " + type + " for " + clazzType.getTypeName() + " with parameter index " + index; - }, null, throwable -> Stream.empty(), executable.getDeclaringClass().getTypeName() + "." + executable.getName() + "(Unknown Source)"); - } - 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; diff --git a/src/de/steamwar/command/CommandMetaData.java b/src/de/steamwar/command/CommandMetaData.java index c7f79c7..fdb1ea2 100644 --- a/src/de/steamwar/command/CommandMetaData.java +++ b/src/de/steamwar/command/CommandMetaData.java @@ -47,9 +47,34 @@ public @interface CommandMetaData { } /** - * This annotation can be used in conjunction with a class that implement {@link AbstractValidator} to - * create custom validator short hands for commands. The validator class itself should contain a constructor - * with two parameters the first is the annotation this annotation is used on and the second is of type {@link 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) diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 2969872..5a056d2 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -32,7 +32,8 @@ import java.util.function.Consumer; class CommandPart { - private static final String[] EMPTY_ARRAY = new String[0]; + 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 { @@ -86,7 +87,7 @@ class CommandPart { 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, i); + CheckArgumentResult validArgument = checkArgument(null, sender, args, current, i); if (!validArgument.success) throw new CommandParseException(); Array.set(array, i - startIndex, validArgument.value); } @@ -99,16 +100,16 @@ class CommandPart { return; } - CheckArgumentResult validArgument = checkArgument(errors, sender, args, startIndex); + 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, EMPTY_ARRAY, optional)); + current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); } else if (startIndex >= args.length) { - current.add(typeMapper.map(sender, EMPTY_ARRAY, optional)); + current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); } else { throw new CommandParseException(); } @@ -126,13 +127,13 @@ class CommandPart { } } - public void generateTabComplete(List current, T sender, String[] args, int startIndex) { + public void generateTabComplete(List current, T sender, String[] args, List mappedArgs, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length - 1; i++) { - CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, i); + CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, mappedArgs, i); if (!validArgument.success) return; } - Collection strings = tabCompletes(sender, args, args.length - 1); + Collection strings = tabCompletes(sender, args, mappedArgs, args.length - 1); if (strings != null) { current.addAll(strings); } @@ -140,40 +141,43 @@ class CommandPart { } if (args.length - 1 > startIndex) { - CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, startIndex); + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, mappedArgs, startIndex); if (checkArgumentResult.success && next != null) { - next.generateTabComplete(current, sender, args, startIndex + 1); + 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, startIndex); + next.generateTabComplete(current, sender, args, mappedArgs, startIndex); } return; } - Collection strings = tabCompletes(sender, args, startIndex); + Collection strings = tabCompletes(sender, args, mappedArgs, startIndex); if (strings != null) { current.addAll(strings); } if (optional != null && next != null) { - next.generateTabComplete(current, sender, args, startIndex); + next.generateTabComplete(current, sender, args, mappedArgs, startIndex); } } - private Collection tabCompletes(T sender, String[] args, int startIndex) { + private Collection tabCompletes(T sender, String[] args, List mappedArgs, int startIndex) { return TabCompletionCache.tabComplete(sender, typeMapper, command, () -> { try { - return typeMapper.tabCompletes(sender, Arrays.copyOf(args, startIndex), args[startIndex]); + 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, int index) { + private CheckArgumentResult checkArgument(Consumer errors, T sender, String[] args, List mappedArgs, int index) { Object value; try { - value = typeMapper.map(sender, Arrays.copyOf(args, index), args[index]); + value = typeMapper.map(sender, new PreviousArguments(Arrays.copyOf(args, index), mappedArgs.toArray()), args[index]); } catch (Exception e) { return new CheckArgumentResult(false, null); } diff --git a/src/de/steamwar/command/ImplicitTypeValidators.java b/src/de/steamwar/command/ImplicitTypeValidators.java deleted file mode 100644 index 7204ce5..0000000 --- a/src/de/steamwar/command/ImplicitTypeValidators.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.experimental.UtilityClass; - -import java.lang.reflect.Array; -import java.util.function.Function; - -@UtilityClass -public class ImplicitTypeValidators { - - public static class ErrorMessageValidator implements AbstractValidator { - - private AbstractSWCommand.ErrorMessage errorMessage; - - public ErrorMessageValidator(AbstractSWCommand.ErrorMessage errorMessage, Class type) { - 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; - } - } - - public static class MinValidator implements AbstractValidator { - - private int value; - private Function comparator; - - public MinValidator(AbstractSWCommand.Min min, Class clazz) { - this.value = min.inclusive() ? 0 : 1; - - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), min.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), min.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), min.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); - } else { - throw new IllegalArgumentException("Min annotation is not supported for " + clazz); - } - } - - @Override - public boolean validate(T sender, Number value, MessageSender messageSender) { - return (comparator.apply(value).intValue()) >= this.value; - } - } - - public static class MaxValidator implements AbstractValidator { - - private int value; - private Function comparator; - - public MaxValidator(AbstractSWCommand.Max max, Class clazz) { - this.value = max.inclusive() ? 0 : -1; - - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), max.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), max.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), max.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); - } else { - throw new IllegalArgumentException("Max annotation is not supported for " + clazz); - } - } - - @Override - public boolean validate(T sender, Number value, MessageSender messageSender) { - return (comparator.apply(value).intValue()) <= this.value; - } - } -} diff --git a/src/de/steamwar/command/PreviousArguments.java b/src/de/steamwar/command/PreviousArguments.java new file mode 100644 index 0000000..24701c0 --- /dev/null +++ b/src/de/steamwar/command/PreviousArguments.java @@ -0,0 +1,39 @@ +/* + * 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; + +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]; + } +} diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 2f16d7a..5bedbe6 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -82,49 +82,7 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { - Class clazz = parameter.getType(); - if (parameter.isVarArgs()) { - clazz = clazz.getComponentType(); - } - - AbstractSWCommand.ClassMapper classMapper = parameter.getAnnotation(AbstractSWCommand.ClassMapper.class); - AbstractSWCommand.Mapper mapper = parameter.getAnnotation(AbstractSWCommand.Mapper.class); - if (clazz.isEnum() && classMapper == null && mapper == null && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { - return createEnumMapper((Class>) clazz); - } - - String name = clazz.getTypeName(); - if (classMapper != null) { - name = classMapper.value().getTypeName(); - } else if (mapper != null) { - name = mapper.value(); - } else { - AbstractSWCommand.StaticValue staticValue = parameter.getAnnotation(AbstractSWCommand.StaticValue.class); - if (staticValue != null) { - if (parameter.getType() == String.class) { - return createMapper(staticValue.value()); - } - if (staticValue.allowISE()) { - if ((parameter.getType() == boolean.class || parameter.getType() == Boolean.class)) { - List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); - Set falseValues = new HashSet<>(); - for (int i : staticValue.falseValues()) falseValues.add(i); - return createMapper(s -> { - int index = tabCompletes.indexOf(s); - return index == -1 ? null : !falseValues.contains(index); - }, (commandSender, s) -> tabCompletes); - } - if ((parameter.getType() == int.class || parameter.getType() == Integer.class || parameter.getType() == long.class || parameter.getType() == Long.class) && staticValue.value().length >= 2) { - List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); - return createMapper(s -> { - Number index = tabCompletes.indexOf(s); - return index.longValue() == -1 ? null : index; - }, (commandSender, s) -> tabCompletes); - } - } - } - } + 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); @@ -132,40 +90,24 @@ public class SWCommandUtils { return typeMapper; } - public static AbstractValidator getValidator(Parameter parameter, Map> localValidator) { + public static AbstractTypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { Class clazz = parameter.getType(); - - AbstractSWCommand.Validator validator = parameter.getAnnotation(AbstractSWCommand.Validator.class); - if (validator != null) { - if (validator.value() != null && !validator.value().isEmpty()) { - return getValidator(validator.value(), localValidator); - } - return getValidator(clazz.getTypeName(), localValidator); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); } - return null; + if (clazz.isEnum() && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { + return createEnumMapper((Class>) clazz); + } + return getTypeMapper(clazz.getTypeName(), localTypeMapper); } - public static AbstractValidator getErrorMessage(Parameter parameter) { - AbstractSWCommand.ErrorMessage errorMessage = parameter.getAnnotation(AbstractSWCommand.ErrorMessage.class); - if (errorMessage != null) { - return (AbstractValidator) (sender, value, 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; - }; - } - return null; - } - - private static AbstractValidator getValidator(String s, Map> localGuardChecker) { - AbstractValidator validator = localGuardChecker.getOrDefault(s, (AbstractValidator) VALIDATOR_FUNCTIONS.getOrDefault(s, null)); - if (validator == null) { + 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 validator; + return concreteValidator; } public static void addMapper(Class clazz, AbstractTypeMapper mapper) { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index da3576e..961cf29 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -61,7 +61,10 @@ public class SubCommand { Parameter[] parameters = method.getParameters(); comparableValue = parameters[parameters.length - 1].isVarArgs() ? Integer.MAX_VALUE : -parameters.length; - validator = (AbstractValidator) SWCommandUtils.getValidator(parameters[0], localValidator); + AbstractSWCommand.Validator validator = parameters[0].getAnnotation(AbstractSWCommand.Validator.class); + if (validator != null) { + this.validator = (AbstractValidator) SWCommandUtils.getValidator(validator, parameters[0].getType(), localValidator); + } commandPart = generateCommandPart(abstractSWCommand, subCommand, parameters, localTypeMapper, localValidator); @@ -120,7 +123,7 @@ public class SubCommand { return null; } List list = new ArrayList<>(); - commandPart.generateTabComplete(list, sender, args, 0); + commandPart.generateTabComplete(list, sender, args, new ArrayList<>(), 0); return list; } @@ -141,8 +144,7 @@ public class SubCommand { } for (int i = 1; i < parameters.length; i++) { Parameter parameter = parameters[i]; - AbstractTypeMapper typeMapper = SWCommandUtils.getTypeMapper(parameter, localTypeMapper); - AbstractValidator validator = (AbstractValidator) SWCommandUtils.getValidator(parameter, localValidator); + AbstractTypeMapper typeMapper = handleImplicitTypeMapper(parameter, localTypeMapper); Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); @@ -151,10 +153,7 @@ public class SubCommand { if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { commandPart.addValidator((AbstractValidator) STRING_SPACE_FILTER); } - handleImplicitTypeValidator(parameter, commandPart, true); - commandPart.addValidator(validator); - commandPart.addValidator((AbstractValidator) SWCommandUtils.getErrorMessage(parameter)); - handleImplicitTypeValidator(parameter, commandPart, false); + handleImplicitTypeValidator(parameter, commandPart, localValidator); if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { commandPart.addValidator((AbstractValidator) NULL_FILTER); } @@ -169,29 +168,72 @@ public class SubCommand { return first; } - private static void handleImplicitTypeValidator(Parameter parameter, CommandPart commandPart, boolean beforeValidatorAnnotation) { + 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; - if (beforeValidatorAnnotation && implicitValidator.order() >= 0) continue; - if (!beforeValidatorAnnotation && implicitValidator.order() <= 0) continue; Class clazz = implicitValidator.handler(); if (!AbstractValidator.class.isAssignableFrom(clazz)) continue; Constructor[] constructors = clazz.getConstructors(); if (constructors.length != 1) continue; - Constructor constructor = constructors[0]; - Class[] parameterTypes = constructor.getParameterTypes(); - if (parameterTypes.length != 2) continue; - AbstractValidator validator; - if (!annotation.annotationType().isAssignableFrom(parameterTypes[0])) continue; - if (!Class.class.isAssignableFrom(parameterTypes[1])) continue; - try { - validator = (AbstractValidator) constructor.newInstance(annotation, commandPart.getType()); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } + AbstractValidator validator = createInstance(constructors[0], annotation, commandPart.getType(), localValidator); validators.computeIfAbsent(implicitValidator.order(), integer -> new ArrayList<>()).add(validator); } List keys = new ArrayList<>(validators.keySet()); @@ -204,42 +246,33 @@ public class SubCommand { } } + 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; private static final AbstractValidator STRING_SPACE_FILTER = (sender, value, messageSender) -> { if (!(value instanceof String)) return true; return !((String) value).contains(" "); }; - - private static AbstractValidator createMinValidator(Class clazz, AbstractSWCommand.Min min) { - Function comparator; - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), min.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), min.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), min.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), min.doubleValue()); - } else { - throw new IllegalArgumentException("Min annotation is not supported for " + clazz); - } - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() >= (min.inclusive() ? 0 : 1); - } - - private static AbstractValidator createMaxValidator(Class clazz, AbstractSWCommand.Max max) { - Function comparator; - if (clazz == int.class || clazz == Integer.class) { - comparator = number -> Integer.compare(number.intValue(), max.intValue()); - } else if (clazz == long.class || clazz == Long.class) { - comparator = number -> Long.compare(number.longValue(), max.longValue()); - } else if (clazz == float.class || clazz == Float.class) { - comparator = number -> Float.compare(number.floatValue(), max.floatValue()); - } else if (clazz == double.class || clazz == Double.class) { - comparator = number -> Double.compare(number.doubleValue(), max.doubleValue()); - } else { - throw new IllegalArgumentException("Max annotation is not supported for " + clazz); - } - return (sender, value, messageSender) -> comparator.apply((Number) value).intValue() <= (max.inclusive() ? 0 : -1); - } } diff --git a/testsrc/de/steamwar/command/BetterExceptionCommand.java b/testsrc/de/steamwar/command/BetterExceptionCommand.java index d014186..feb47fc 100644 --- a/testsrc/de/steamwar/command/BetterExceptionCommand.java +++ b/testsrc/de/steamwar/command/BetterExceptionCommand.java @@ -39,7 +39,7 @@ public class BetterExceptionCommand extends TestSWCommand { public TestTypeMapper tabCompleteException() { return new TestTypeMapper() { @Override - public String map(String sender, String[] previousArguments, String s) { + public String map(String sender, PreviousArguments previousArguments, String s) { return null; } @@ -50,7 +50,7 @@ public class BetterExceptionCommand extends TestSWCommand { } @Override - public Collection tabCompletes(String sender, String[] previousArguments, String s) { + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { throw new SecurityException(); } }; diff --git a/testsrc/de/steamwar/command/CacheCommand.java b/testsrc/de/steamwar/command/CacheCommand.java index 1771bca..5057ffd 100644 --- a/testsrc/de/steamwar/command/CacheCommand.java +++ b/testsrc/de/steamwar/command/CacheCommand.java @@ -44,12 +44,12 @@ public class CacheCommand extends TestSWCommand { System.out.println("TypeMapper register"); return new TestTypeMapper() { @Override - public Integer map(String sender, String[] previousArguments, String s) { + public Integer map(String sender, PreviousArguments previousArguments, String s) { return Integer.parseInt(s); } @Override - public Collection tabCompletes(String sender, String[] previousArguments, String s) { + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { return Arrays.asList(count.getAndIncrement() + ""); } }; diff --git a/testsrc/de/steamwar/command/NullMapperCommand.java b/testsrc/de/steamwar/command/NullMapperCommand.java index e47eab7..30f4061 100644 --- a/testsrc/de/steamwar/command/NullMapperCommand.java +++ b/testsrc/de/steamwar/command/NullMapperCommand.java @@ -43,7 +43,7 @@ public class NullMapperCommand extends TestSWCommand { public TestTypeMapper typeMapper() { return new TestTypeMapper() { @Override - public String map(String sender, String[] previousArguments, String s) { + public String map(String sender, PreviousArguments previousArguments, String s) { if (s.equals("Hello World")) { return null; } @@ -51,7 +51,7 @@ public class NullMapperCommand extends TestSWCommand { } @Override - public Collection tabCompletes(String sender, String[] previousArguments, String s) { + public Collection tabCompletes(String sender, PreviousArguments previousArguments, String s) { return null; } }; From d23f2563f69a973c35709a76c3b201704d6f6f39 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 18:29:14 +0100 Subject: [PATCH 11/12] Add test for new system --- .../command/PreviousArgumentCommand.java | 54 ++++++++++++++++++ .../command/PreviousArgumentCommandTest.java | 56 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 testsrc/de/steamwar/command/PreviousArgumentCommand.java create mode 100644 testsrc/de/steamwar/command/PreviousArgumentCommandTest.java diff --git a/testsrc/de/steamwar/command/PreviousArgumentCommand.java b/testsrc/de/steamwar/command/PreviousArgumentCommand.java new file mode 100644 index 0000000..454283c --- /dev/null +++ b/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/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java b/testsrc/de/steamwar/command/PreviousArgumentCommandTest.java new file mode 100644 index 0000000..941b1c3 --- /dev/null +++ b/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"); + } + } +} From d56150a8b105f5ad63a8c3a21b6833f388a83225 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 20:45:29 +0100 Subject: [PATCH 12/12] Fix one weird behaviour --- .../steamwar/command/PreviousArguments.java | 23 +++++++++++++++++++ src/de/steamwar/command/SWCommandUtils.java | 5 ++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/command/PreviousArguments.java b/src/de/steamwar/command/PreviousArguments.java index 24701c0..e9851c0 100644 --- a/src/de/steamwar/command/PreviousArguments.java +++ b/src/de/steamwar/command/PreviousArguments.java @@ -19,6 +19,10 @@ package de.steamwar.command; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + public class PreviousArguments { public final String[] userArgs; @@ -36,4 +40,23 @@ public class PreviousArguments { 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/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 5bedbe6..32b7fcc 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -23,7 +23,6 @@ import lombok.Getter; import lombok.experimental.UtilityClass; import java.lang.annotation.Annotation; -import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; @@ -42,12 +41,12 @@ public class SWCommandUtils { private SWTypeMapperCreator swTypeMapperCreator = (mapper, tabCompleter) -> new AbstractTypeMapper() { @Override - public Object map(Object sender, String[] previousArguments, String s) { + public Object map(Object sender, PreviousArguments previousArguments, String s) { return mapper.apply(s); } @Override - public Collection tabCompletes(Object sender, String[] previousArguments, String s) { + public Collection tabCompletes(Object sender, PreviousArguments previousArguments, String s) { return ((BiFunction>) tabCompleter).apply(sender, s); } };