From b8d659d1c6d9d51475d37ba1ba402d1670a453c1 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 16 Jun 2022 13:18:14 +0200 Subject: [PATCH 1/6] Add AbstractValidator Deprecate AbstractGuardChecker --- .../command/AbstractGuardChecker.java | 1 + .../steamwar/command/AbstractSWCommand.java | 53 +++++++++++++++++++ .../steamwar/command/AbstractTypeMapper.java | 2 +- .../steamwar/command/AbstractValidator.java | 26 +++++++++ src/de/steamwar/command/GuardCheckType.java | 1 + src/de/steamwar/command/GuardResult.java | 1 + src/de/steamwar/command/SWCommandUtils.java | 10 +++- 7 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/de/steamwar/command/AbstractValidator.java diff --git a/src/de/steamwar/command/AbstractGuardChecker.java b/src/de/steamwar/command/AbstractGuardChecker.java index f5f2597..dd4c555 100644 --- a/src/de/steamwar/command/AbstractGuardChecker.java +++ b/src/de/steamwar/command/AbstractGuardChecker.java @@ -19,6 +19,7 @@ package de.steamwar.command; +@Deprecated @FunctionalInterface public interface AbstractGuardChecker { /** diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index d654df7..2500bd1 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -38,6 +38,7 @@ public abstract class AbstractSWCommand { private final Map> localTypeMapper = new HashMap<>(); private final Map> localGuardChecker = new HashMap<>(); + private final Map> localValidators = new HashMap<>(); protected AbstractSWCommand(Class clazz, String command) { this(clazz, command, new String[0]); @@ -127,6 +128,20 @@ public abstract class AbstractSWCommand { SWCommandUtils.getGUARD_FUNCTIONS().putIfAbsent(anno.value().getTypeName(), guardChecker); } }); + 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(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); + } + }); } for (Method method : methods) { add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> { @@ -228,6 +243,17 @@ 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) -> { + try { + method.setAccessible(true); + consumer.accept(anno, (AbstractValidator) method.invoke(this)); + } catch (Exception e) { + throw new SecurityException(e.getMessage(), e); + } + }); + } + // TODO: Implement this when Message System is ready /* public void addDefaultHelpMessage(String message) { @@ -280,6 +306,7 @@ public abstract class AbstractSWCommand { boolean local() default false; } + @Deprecated @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.METHOD}) protected @interface Guard { @@ -288,6 +315,7 @@ public abstract class AbstractSWCommand { boolean local() default false; } + @Deprecated @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) protected @interface ClassGuard { @@ -296,6 +324,22 @@ public abstract class AbstractSWCommand { boolean local() default false; } + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER, ElementType.METHOD}) + protected @interface Validator { + String value() default ""; + + boolean local() default false; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + protected @interface ClassValidator { + Class value(); + + boolean local() default false; + } + @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) protected @interface StaticValue { @@ -320,4 +364,13 @@ public abstract class AbstractSWCommand { */ String value(); } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface ErrorMessage { + /** + * Error message to be displayed when the parameter is invalid. + */ + String value(); + } } diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index 744b726..a9174dc 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -21,7 +21,7 @@ package de.steamwar.command; import java.util.Collection; -public interface AbstractTypeMapper { +public interface AbstractTypeMapper extends AbstractValidator { /** * The CommandSender can be null! */ diff --git a/src/de/steamwar/command/AbstractValidator.java b/src/de/steamwar/command/AbstractValidator.java new file mode 100644 index 0000000..c4df8ed --- /dev/null +++ b/src/de/steamwar/command/AbstractValidator.java @@ -0,0 +1,26 @@ +/* + * 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 interface AbstractValidator { + default boolean validate(K sender, T value) { + return true; + } +} diff --git a/src/de/steamwar/command/GuardCheckType.java b/src/de/steamwar/command/GuardCheckType.java index 0f023b8..d2e6ed0 100644 --- a/src/de/steamwar/command/GuardCheckType.java +++ b/src/de/steamwar/command/GuardCheckType.java @@ -19,6 +19,7 @@ package de.steamwar.command; +@Deprecated public enum GuardCheckType { COMMAND, HELP_COMMAND, diff --git a/src/de/steamwar/command/GuardResult.java b/src/de/steamwar/command/GuardResult.java index 9ffbf77..3e6aafc 100644 --- a/src/de/steamwar/command/GuardResult.java +++ b/src/de/steamwar/command/GuardResult.java @@ -19,6 +19,7 @@ package de.steamwar.command; +@Deprecated public enum GuardResult { ALLOWED, DENIED_WITH_HELP, diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index aaf0c7a..4e57876 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -28,6 +28,7 @@ import java.lang.reflect.Parameter; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Collectors; @UtilityClass public class SWCommandUtils { @@ -36,8 +37,12 @@ public class SWCommandUtils { private final Map> MAPPER_FUNCTIONS = new HashMap<>(); @Getter + @Deprecated private final Map> GUARD_FUNCTIONS = new HashMap<>(); + @Getter + private final Map> VALIDATOR_FUNCTIONS = new HashMap<>(); + private SWTypeMapperCreator swTypeMapperCreator = (mapper, tabCompleter) -> new AbstractTypeMapper() { @Override public Object map(Object sender, String[] previousArguments, String s) { @@ -209,8 +214,9 @@ public class SWCommandUtils { } public static , K> T createMapper(String... values) { - List strings = Arrays.asList(values); - return createMapper((s) -> strings.contains(s) ? s : null, s -> strings); + List strings = Arrays.stream(values).map(String::toLowerCase).collect(Collectors.toList()); + List tabCompletes = Arrays.asList(values); + return createMapper(s -> strings.contains(s.toLowerCase()) ? s : null, s -> tabCompletes); } public static , K, V> T createMapper(Function mapper, Function> tabCompleter) { From fd6612ac1b5bad4ddb365711b34dd1577ae8dad9 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 16 Jun 2022 15:59:28 +0200 Subject: [PATCH 2/6] Update validators --- .../steamwar/command/AbstractSWCommand.java | 6 ++- .../steamwar/command/AbstractValidator.java | 13 ++++- src/de/steamwar/command/CommandPart.java | 39 +++++++++++++- src/de/steamwar/command/SWCommandUtils.java | 53 +++++++++++++++++-- src/de/steamwar/command/SubCommand.java | 51 ++++++++++++++---- .../de/steamwar/command/GuardCommandTest.java | 2 +- 6 files changed, 146 insertions(+), 18 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 2500bd1..9387a02 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -65,6 +65,8 @@ public abstract class AbstractSWCommand { System.out.println(message.get()); } + protected void sendMessage(T sender, String message, Object[] args) {} + protected final void execute(T sender, String alias, String[] args) { initialize(); try { @@ -156,7 +158,7 @@ public abstract class AbstractSWCommand { commandSystemWarning(() -> "The method '" + method.toString() + "' is lacking the varArgs parameters of type '" + String.class.getTypeName() + "' as last Argument"); return; } - commandHelpList.add(new SubCommand<>(this, method, anno.value(), new HashMap<>(), localGuardChecker, true, null, anno.noTabComplete())); + commandHelpList.add(new SubCommand<>(this, method, anno.value(), new HashMap<>(), new HashMap<>(), localGuardChecker, true, null, anno.noTabComplete())); }); add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> { @@ -177,7 +179,7 @@ public abstract class AbstractSWCommand { return; } } - commandList.add(new SubCommand<>(this, method, anno.value(), localTypeMapper, localGuardChecker, false, anno.description(), anno.noTabComplete())); + commandList.add(new SubCommand<>(this, method, anno.value(), localTypeMapper, localValidators, localGuardChecker, false, anno.description(), anno.noTabComplete())); }); } diff --git a/src/de/steamwar/command/AbstractValidator.java b/src/de/steamwar/command/AbstractValidator.java index c4df8ed..0f74382 100644 --- a/src/de/steamwar/command/AbstractValidator.java +++ b/src/de/steamwar/command/AbstractValidator.java @@ -19,8 +19,19 @@ package de.steamwar.command; +import java.util.function.BiConsumer; + public interface AbstractValidator { - default boolean validate(K sender, T value) { + + /** + * Validates the given value. + * + * @param sender The sender of the command. + * @param value The value to validate or null if mapping returned null. + * @param messageSender The message sender to send messages to the player. Never send messages directly to the player. + * @return The result of the validation. + */ + default boolean validate(K sender, T value, BiConsumer messageSender) { return true; } } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 0688caf..503a7af 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -37,7 +37,9 @@ class CommandPart { private final Object value; } + private AbstractSWCommand command; private AbstractTypeMapper typeMapper; + private AbstractValidator validator; private AbstractGuardChecker guardChecker; private Class varArgType; private String optional; @@ -48,8 +50,10 @@ class CommandPart { @Setter private boolean ignoreAsArgument = false; - public CommandPart(AbstractTypeMapper typeMapper, AbstractGuardChecker guardChecker, Class varArgType, String optional, GuardCheckType guardCheckType) { + public CommandPart(AbstractSWCommand command, AbstractTypeMapper typeMapper, AbstractValidator validator, AbstractGuardChecker guardChecker, Class varArgType, String optional, GuardCheckType guardCheckType) { + this.command = command; this.typeMapper = typeMapper; + this.validator = validator; this.guardChecker = guardChecker; this.varArgType = varArgType; this.optional = optional; @@ -117,6 +121,30 @@ class CommandPart { } } + public boolean validate(T sender, List values, int startIndex) { + if (varArgType != null) { + for (int i = startIndex; i < values.size(); i++) { + Object value = values.get(i); + if (validator != null && !validator.validate(sender, value, (s, objects) -> command.sendMessage(sender, s, objects))) { + return false; + } + } + return true; + } + + Object value = values.get(startIndex); + if (validator != null && !validator.validate(sender, value, (s, objects) -> command.sendMessage(sender, s, objects))) { + if (optional != null && next != null) { + return next.validate(sender, values, startIndex); + } + return false; + } + if (next != null) { + return next.validate(sender, values, startIndex + 1); + } + return true; + } + public boolean guardCheck(T sender, String[] args, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length; i++) { @@ -192,6 +220,15 @@ class CommandPart { if (value == null) { return new CheckArgumentResult(false, null); } + if (validator != null) { + if (!validator.validate(sender, value, (s, objects) -> { + // ignore + })) { + return new CheckArgumentResult(false, null); + } + return new CheckArgumentResult(true, value); + } + GuardResult guardResult = checkGuard(guardCheckType, sender, args, index); switch (guardResult) { case ALLOWED: diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 4e57876..81d759a 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -26,6 +26,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -77,11 +78,11 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - static CommandPart generateCommandPart(boolean help, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map> localGuardChecker) { + static CommandPart generateCommandPart(AbstractSWCommand command, boolean help, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map> localValidator, Map> localGuardChecker) { CommandPart first = null; CommandPart current = null; for (String s : subCommand) { - CommandPart commandPart = new CommandPart(createMapper(s), null, null, null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); + CommandPart commandPart = new CommandPart(command, createMapper(s), null, null, null, null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); commandPart.setIgnoreAsArgument(true); if (current != null) { current.setNext(commandPart); @@ -94,11 +95,12 @@ public class SWCommandUtils { for (int i = 1; i < parameters.length; i++) { Parameter parameter = parameters[i]; AbstractTypeMapper typeMapper = getTypeMapper(parameter, localTypeMapper); + AbstractValidator validator = (AbstractValidator) getValidator(parameter, localValidator); AbstractGuardChecker guardChecker = getGuardChecker(parameter, localGuardChecker); Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); - CommandPart commandPart = new CommandPart<>(typeMapper, guardChecker, varArgType, optionalValue != null ? optionalValue.value() : null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); + CommandPart commandPart = new CommandPart<>(command, typeMapper, validator, guardChecker, varArgType, optionalValue != null ? optionalValue.value() : null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); if (current != null) { current.setNext(commandPart); } @@ -119,7 +121,7 @@ public class SWCommandUtils { 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 (AbstractTypeMapper) createEnumMapper((Class>) clazz); + return createEnumMapper((Class>) clazz); } String name = clazz.getTypeName(); @@ -165,6 +167,49 @@ public class SWCommandUtils { return typeMapper; } + public static AbstractValidator getValidator(Parameter parameter, Map> localValidator) { + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); + } + + 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) { + return getValidator(validator.value(), localValidator); + } + return getValidator(clazz.getTypeName(), localValidator); + } + + AbstractSWCommand.ErrorMessage errorMessage = parameter.getAnnotation(AbstractSWCommand.ErrorMessage.class); + if (errorMessage != null) { + return new AbstractValidator() { + @Override + public boolean validate(T sender, String value, BiConsumer messageSender) { + if (value == null) messageSender.accept(errorMessage.value(), new Object[0]); + 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) { + throw new IllegalArgumentException("No validator found for " + s); + } + return validator; + } + public static AbstractGuardChecker getGuardChecker(Parameter parameter, Map> localGuardChecker) { Class clazz = parameter.getType(); if (parameter.isVarArgs()) { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 603e6d3..6456bfd 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -36,13 +36,14 @@ public class SubCommand { String[] subCommand; private Predicate senderPredicate; private Function senderFunction; + AbstractValidator validator; AbstractGuardChecker guardChecker; boolean noTabComplete; int comparableValue; private CommandPart commandPart; - SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localGuardChecker, boolean help, String[] description, boolean noTabComplete) { + SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localValidator, Map> localGuardChecker, boolean help, String[] description, boolean noTabComplete) { this.abstractSWCommand = abstractSWCommand; this.method = method; this.subCommand = subCommand; @@ -52,9 +53,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); guardChecker = SWCommandUtils.getGuardChecker(parameters[0], localGuardChecker); - commandPart = SWCommandUtils.generateCommandPart(help, subCommand, parameters, localTypeMapper, localGuardChecker); + commandPart = SWCommandUtils.generateCommandPart(abstractSWCommand, help, subCommand, parameters, localTypeMapper, localValidator, localGuardChecker); senderPredicate = t -> parameters[0].getType().isAssignableFrom(t.getClass()); senderFunction = t -> parameters[0].getType().cast(t); @@ -70,12 +72,13 @@ public class SubCommand { if (args.length != 0) { return false; } - method.setAccessible(true); - method.invoke(abstractSWCommand, senderFunction.apply(sender)); - } else { - List objects = new ArrayList<>(); - commandPart.generateArgumentArray(objects, sender, args, 0); - if (guardChecker != null) { + if (validator != null) { + if (!validator.validate(sender, sender, (s, objectArgs) -> { + // TODO: implement + })) { + throw new CommandNoHelpException(); + } + } else if (guardChecker != null) { GuardResult guardResult = guardChecker.guard(sender, GuardCheckType.COMMAND, new String[0], null); switch (guardResult) { case ALLOWED: @@ -87,6 +90,30 @@ public class SubCommand { return true; } } + method.setAccessible(true); + method.invoke(abstractSWCommand, senderFunction.apply(sender)); + } else { + List objects = new ArrayList<>(); + commandPart.generateArgumentArray(objects, sender, args, 0); + if (validator != null) { + if (!validator.validate(sender, sender, (s, objectArgs) -> { + // TODO: implement + })) { + throw new CommandNoHelpException(); + } + } else if (guardChecker != null) { + GuardResult guardResult = guardChecker.guard(sender, GuardCheckType.COMMAND, new String[0], null); + switch (guardResult) { + case ALLOWED: + break; + case DENIED: + throw new CommandNoHelpException(); + case DENIED_WITH_HELP: + default: + return true; + } + } + commandPart.validate(sender, objects, 0); commandPart.guardCheck(sender, args, 0); objects.add(0, senderFunction.apply(sender)); method.setAccessible(true); @@ -105,7 +132,13 @@ public class SubCommand { } List tabComplete(T sender, String[] args) { - if (guardChecker != null && guardChecker.guard(sender, GuardCheckType.TAB_COMPLETE, new String[0], null) != GuardResult.ALLOWED) { + if (validator != null) { + if (!validator.validate(sender, sender, (s, objects) -> { + // TODO: implement + })) { + return null; + } + } else if (guardChecker != null && guardChecker.guard(sender, GuardCheckType.TAB_COMPLETE, new String[0], null) != GuardResult.ALLOWED) { return null; } if (commandPart == null) { diff --git a/testsrc/de/steamwar/command/GuardCommandTest.java b/testsrc/de/steamwar/command/GuardCommandTest.java index 233333c..51eae0e 100644 --- a/testsrc/de/steamwar/command/GuardCommandTest.java +++ b/testsrc/de/steamwar/command/GuardCommandTest.java @@ -35,7 +35,7 @@ public class GuardCommandTest { try { cmd.execute("test", "", new String[0]); } catch (Exception e) { - assertCMDFramework(e, ExecutionIdentifier.class, "RunTypeMapper"); + assertThat(e.getMessage(), is("GuardChecker COMMAND")); } } From a7d41ddc80a26b905081adfc5c64aa65c502eb7b Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 16 Jun 2022 16:00:14 +0200 Subject: [PATCH 3/6] Update when messages and stuff is send --- src/de/steamwar/command/SubCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 6456bfd..4758433 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -74,7 +74,7 @@ public class SubCommand { } if (validator != null) { if (!validator.validate(sender, sender, (s, objectArgs) -> { - // TODO: implement + abstractSWCommand.sendMessage(sender, s, objectArgs); })) { throw new CommandNoHelpException(); } @@ -97,7 +97,7 @@ public class SubCommand { commandPart.generateArgumentArray(objects, sender, args, 0); if (validator != null) { if (!validator.validate(sender, sender, (s, objectArgs) -> { - // TODO: implement + abstractSWCommand.sendMessage(sender, s, objectArgs); })) { throw new CommandNoHelpException(); } @@ -134,7 +134,7 @@ public class SubCommand { List tabComplete(T sender, String[] args) { if (validator != null) { if (!validator.validate(sender, sender, (s, objects) -> { - // TODO: implement + // ignore })) { return null; } From a8880e53dbc03ada5eb2385487e43d9f7eb7dde8 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 16 Jun 2022 16:46:22 +0200 Subject: [PATCH 4/6] Fix a bunch of errors --- .../steamwar/command/AbstractSWCommand.java | 6 +- .../steamwar/command/AbstractTypeMapper.java | 6 ++ .../steamwar/command/AbstractValidator.java | 5 +- src/de/steamwar/command/CommandPart.java | 30 +++++---- src/de/steamwar/command/SWCommandUtils.java | 11 ++-- src/de/steamwar/command/SubCommand.java | 5 +- .../de/steamwar/command/ValidatorCommand.java | 60 ++++++++++++++++++ .../command/ValidatorCommandTest.java | 63 +++++++++++++++++++ .../steamwar/command/dto/TestValidator.java | 25 ++++++++ 9 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 testsrc/de/steamwar/command/ValidatorCommand.java create mode 100644 testsrc/de/steamwar/command/ValidatorCommandTest.java create mode 100644 testsrc/de/steamwar/command/dto/TestValidator.java diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 9387a02..5f3da93 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -69,9 +69,11 @@ public abstract class AbstractSWCommand { protected final void execute(T sender, String alias, String[] args) { initialize(); + List errors = new ArrayList<>(); try { - if (!commandList.stream().anyMatch(s -> s.invoke(sender, alias, args))) { - commandHelpList.stream().anyMatch(s -> s.invoke(sender, alias, args)); + if (!commandList.stream().anyMatch(s -> s.invoke(errors::add, sender, alias, args))) { + errors.forEach(Runnable::run); + commandHelpList.stream().anyMatch(s -> s.invoke((ignore) -> {}, sender, alias, args)); } } catch (CommandNoHelpException e) { // Ignored diff --git a/src/de/steamwar/command/AbstractTypeMapper.java b/src/de/steamwar/command/AbstractTypeMapper.java index a9174dc..ca8c50b 100644 --- a/src/de/steamwar/command/AbstractTypeMapper.java +++ b/src/de/steamwar/command/AbstractTypeMapper.java @@ -20,6 +20,7 @@ package de.steamwar.command; import java.util.Collection; +import java.util.function.BiConsumer; public interface AbstractTypeMapper extends AbstractValidator { /** @@ -27,5 +28,10 @@ public interface AbstractTypeMapper extends AbstractValidator { */ T map(K sender, String[] previousArguments, String s); + @Override + default boolean validate(K sender, T value, BiConsumer messageSender) { + return true; + } + Collection tabCompletes(K sender, String[] previousArguments, String s); } diff --git a/src/de/steamwar/command/AbstractValidator.java b/src/de/steamwar/command/AbstractValidator.java index 0f74382..a12c0e4 100644 --- a/src/de/steamwar/command/AbstractValidator.java +++ b/src/de/steamwar/command/AbstractValidator.java @@ -21,6 +21,7 @@ package de.steamwar.command; import java.util.function.BiConsumer; +@FunctionalInterface public interface AbstractValidator { /** @@ -31,7 +32,5 @@ public interface AbstractValidator { * @param messageSender The message sender to send messages to the player. Never send messages directly to the player. * @return The result of the validation. */ - default boolean validate(K sender, T value, BiConsumer messageSender) { - return true; - } + boolean validate(K sender, T value, BiConsumer messageSender); } diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 503a7af..1072c2a 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -23,9 +23,11 @@ import lombok.AllArgsConstructor; import lombok.Setter; import java.lang.reflect.Array; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.function.Consumer; class CommandPart { @@ -86,11 +88,11 @@ class CommandPart { } } - public void generateArgumentArray(List current, T sender, String[] args, int startIndex) { + 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); + CheckArgumentResult validArgument = checkArgument(errors, null, sender, args, i); if (!validArgument.success) { throw new CommandParseException(); } @@ -100,7 +102,7 @@ class CommandPart { return; } - CheckArgumentResult validArgument = checkArgument(null, sender, args, startIndex); + CheckArgumentResult validArgument = checkArgument(errors, null, sender, args, startIndex); if (!validArgument.success && optional == null) { throw new CommandParseException(); } @@ -109,7 +111,7 @@ class CommandPart { current.add(typeMapper.map(sender, EMPTY_ARRAY, optional)); } if (next != null) { - next.generateArgumentArray(current, sender, args, startIndex); + next.generateArgumentArray(errors, current, sender, args, startIndex); } return; } @@ -117,7 +119,7 @@ class CommandPart { current.add(validArgument.value); } if (next != null) { - next.generateArgumentArray(current, sender, args, startIndex + 1); + next.generateArgumentArray(errors, current, sender, args, startIndex + 1); } } @@ -181,7 +183,7 @@ 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(GuardCheckType.TAB_COMPLETE, sender, args, i); + CheckArgumentResult validArgument = checkArgument((ignore) -> {}, GuardCheckType.TAB_COMPLETE, sender, args, i); if (!validArgument.success) { return; } @@ -194,7 +196,7 @@ class CommandPart { } if (args.length - 1 > startIndex) { - CheckArgumentResult checkArgumentResult = checkArgument(GuardCheckType.TAB_COMPLETE, sender, args, startIndex); + CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, GuardCheckType.TAB_COMPLETE, sender, args, startIndex); if (checkArgumentResult.success && next != null) { next.generateTabComplete(current, sender, args, startIndex + 1); return; @@ -214,19 +216,21 @@ class CommandPart { } } - private CheckArgumentResult checkArgument(GuardCheckType guardCheckType, T sender, String[] args, int index) { + private CheckArgumentResult checkArgument(Consumer errors, GuardCheckType guardCheckType, T sender, String[] args, int index) { try { Object value = typeMapper.map(sender, Arrays.copyOf(args, index), args[index]); - if (value == null) { - return new CheckArgumentResult(false, null); - } if (validator != null) { if (!validator.validate(sender, value, (s, objects) -> { - // ignore + errors.accept(() -> { + command.sendMessage(sender, s, objects); + }); })) { return new CheckArgumentResult(false, null); } - return new CheckArgumentResult(true, value); + return new CheckArgumentResult(value != null, value); + } + if (value == null) { + return new CheckArgumentResult(false, null); } GuardResult guardResult = checkGuard(guardCheckType, sender, args, index); diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index 81d759a..a3ee3de 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -183,7 +183,7 @@ public class SWCommandUtils { AbstractSWCommand.Validator validator = parameter.getAnnotation(AbstractSWCommand.Validator.class); if (validator != null) { - if (validator.value() != null) { + if (validator.value() != null && !validator.value().isEmpty()) { return getValidator(validator.value(), localValidator); } return getValidator(clazz.getTypeName(), localValidator); @@ -191,12 +191,9 @@ public class SWCommandUtils { AbstractSWCommand.ErrorMessage errorMessage = parameter.getAnnotation(AbstractSWCommand.ErrorMessage.class); if (errorMessage != null) { - return new AbstractValidator() { - @Override - public boolean validate(T sender, String value, BiConsumer messageSender) { - if (value == null) messageSender.accept(errorMessage.value(), new Object[0]); - return value != null; - } + return (AbstractValidator) (sender, value, messageSender) -> { + if (value == null) messageSender.accept(errorMessage.value(), new Object[0]); + return value != null; }; } return null; diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index 4758433..f8e7014 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -25,6 +25,7 @@ import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -62,7 +63,7 @@ public class SubCommand { senderFunction = t -> parameters[0].getType().cast(t); } - boolean invoke(T sender, String alias, String[] args) { + boolean invoke(Consumer errors, T sender, String alias, String[] args) { try { if (!senderPredicate.test(sender)) { return false; @@ -94,7 +95,7 @@ public class SubCommand { method.invoke(abstractSWCommand, senderFunction.apply(sender)); } else { List objects = new ArrayList<>(); - commandPart.generateArgumentArray(objects, sender, args, 0); + commandPart.generateArgumentArray(errors, objects, sender, args, 0); if (validator != null) { if (!validator.validate(sender, sender, (s, objectArgs) -> { abstractSWCommand.sendMessage(sender, s, objectArgs); diff --git a/testsrc/de/steamwar/command/ValidatorCommand.java b/testsrc/de/steamwar/command/ValidatorCommand.java new file mode 100644 index 0000000..ea95a5e --- /dev/null +++ b/testsrc/de/steamwar/command/ValidatorCommand.java @@ -0,0 +1,60 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import de.steamwar.command.dto.TestSWCommand; +import de.steamwar.command.dto.TestValidator; + +public class ValidatorCommand extends TestSWCommand { + + public ValidatorCommand() { + super("testvalidator"); + } + + @Register + public void test(@Validator String sender) { + throw new ExecutionIdentifier("RunTest"); + } + + @Override + protected void sendMessage(String sender, String message, Object[] args) { + if (message.equals("Hello World")) { + throw new ExecutionIdentifier("RunSendMessageWithHelloWorldParameter"); + } + } + + @Register + public void onError(String sender, @ErrorMessage("Hello World") int error) { + throw new ExecutionIdentifier("RunOnError"); + } + + @Register + public void onError(String sender, double error) { + throw new ExecutionIdentifier("RunOnErrorDouble"); + } + + @ClassValidator(value = String.class, local = true) + public TestValidator validator() { + return (sender, value, messageSender) -> { + throw new ExecutionIdentifier("RunValidator"); + }; + } +} diff --git a/testsrc/de/steamwar/command/ValidatorCommandTest.java b/testsrc/de/steamwar/command/ValidatorCommandTest.java new file mode 100644 index 0000000..d9712e3 --- /dev/null +++ b/testsrc/de/steamwar/command/ValidatorCommandTest.java @@ -0,0 +1,63 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import de.steamwar.command.dto.ExecutionIdentifier; +import org.junit.Test; + +import static de.steamwar.AssertionUtils.assertCMDFramework; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class ValidatorCommandTest { + + @Test + public void testValidator() { + ValidatorCommand cmd = new ValidatorCommand(); + try { + cmd.execute("test", "", new String[0]); + assertThat(true, is(false)); + } catch (Exception e) { + assertThat(e.getMessage(), is("RunValidator")); + } + } + + @Test + public void testErrorMessage() { + ValidatorCommand cmd = new ValidatorCommand(); + try { + cmd.execute("test", "", new String[]{"Hello"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertThat(e.getMessage(), is("RunSendMessageWithHelloWorldParameter")); + } + } + + @Test + public void testErrorNoMessage() { + ValidatorCommand cmd = new ValidatorCommand(); + try { + cmd.execute("test", "", new String[]{"0.0"}); + assertThat(true, is(false)); + } catch (Exception e) { + assertCMDFramework(e, ExecutionIdentifier.class, "RunOnErrorDouble"); + } + } +} diff --git a/testsrc/de/steamwar/command/dto/TestValidator.java b/testsrc/de/steamwar/command/dto/TestValidator.java new file mode 100644 index 0000000..8cf0495 --- /dev/null +++ b/testsrc/de/steamwar/command/dto/TestValidator.java @@ -0,0 +1,25 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command.dto; + +import de.steamwar.command.AbstractValidator; + +public interface TestValidator extends AbstractValidator { +} From d0150a23357ad7214097ce0743a3cb5715b20f34 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 16 Jun 2022 16:55:35 +0200 Subject: [PATCH 5/6] Fix a bunch of errors --- src/de/steamwar/command/AbstractSWCommand.java | 9 +++++---- src/de/steamwar/command/SWCommandUtils.java | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/de/steamwar/command/AbstractSWCommand.java b/src/de/steamwar/command/AbstractSWCommand.java index 5f3da93..16b764c 100644 --- a/src/de/steamwar/command/AbstractSWCommand.java +++ b/src/de/steamwar/command/AbstractSWCommand.java @@ -236,22 +236,23 @@ public abstract class AbstractSWCommand { }); } - private void addGuard(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { + private void addValidator(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { add(annotation, method, parameterTester, firstParameter, returnType, (anno, parameters) -> { try { method.setAccessible(true); - consumer.accept(anno, (AbstractGuardChecker) method.invoke(this)); + consumer.accept(anno, (AbstractValidator) method.invoke(this)); } catch (Exception e) { throw new SecurityException(e.getMessage(), e); } }); } - private void addValidator(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { + @Deprecated + private void addGuard(Class annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class returnType, BiConsumer> consumer) { add(annotation, method, parameterTester, firstParameter, returnType, (anno, parameters) -> { try { method.setAccessible(true); - consumer.accept(anno, (AbstractValidator) method.invoke(this)); + consumer.accept(anno, (AbstractGuardChecker) method.invoke(this)); } catch (Exception e) { throw new SecurityException(e.getMessage(), e); } diff --git a/src/de/steamwar/command/SWCommandUtils.java b/src/de/steamwar/command/SWCommandUtils.java index a3ee3de..4756630 100644 --- a/src/de/steamwar/command/SWCommandUtils.java +++ b/src/de/steamwar/command/SWCommandUtils.java @@ -207,6 +207,7 @@ public class SWCommandUtils { return validator; } + @Deprecated public static AbstractGuardChecker getGuardChecker(Parameter parameter, Map> localGuardChecker) { Class clazz = parameter.getType(); if (parameter.isVarArgs()) { @@ -231,6 +232,7 @@ public class SWCommandUtils { return null; } + @Deprecated private static AbstractGuardChecker getGuardChecker(String s, Map> localGuardChecker) { AbstractGuardChecker guardChecker = localGuardChecker.getOrDefault(s, (AbstractGuardChecker) GUARD_FUNCTIONS.getOrDefault(s, null)); if (guardChecker == null) { @@ -247,10 +249,20 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.putIfAbsent(name, mapper); } + public static void addValidator(Class clazz, AbstractValidator validator) { + addValidator(clazz.getTypeName(), validator); + } + + public static void addValidator(String name, AbstractValidator validator) { + VALIDATOR_FUNCTIONS.putIfAbsent(name, validator); + } + + @Deprecated public static void addGuard(Class clazz, AbstractGuardChecker guardChecker) { addGuard(clazz.getTypeName(), guardChecker); } + @Deprecated public static void addGuard(String name, AbstractGuardChecker guardChecker) { GUARD_FUNCTIONS.putIfAbsent(name, guardChecker); } From 10525d2a5cf24245a633dc35b8f8793da37fd1b1 Mon Sep 17 00:00:00 2001 From: yoyosource Date: Thu, 16 Jun 2022 17:28:14 +0200 Subject: [PATCH 6/6] Fix stuff --- src/de/steamwar/command/CommandPart.java | 24 ------------------------ src/de/steamwar/command/SubCommand.java | 1 - 2 files changed, 25 deletions(-) diff --git a/src/de/steamwar/command/CommandPart.java b/src/de/steamwar/command/CommandPart.java index 1072c2a..2f7e075 100644 --- a/src/de/steamwar/command/CommandPart.java +++ b/src/de/steamwar/command/CommandPart.java @@ -123,30 +123,6 @@ class CommandPart { } } - public boolean validate(T sender, List values, int startIndex) { - if (varArgType != null) { - for (int i = startIndex; i < values.size(); i++) { - Object value = values.get(i); - if (validator != null && !validator.validate(sender, value, (s, objects) -> command.sendMessage(sender, s, objects))) { - return false; - } - } - return true; - } - - Object value = values.get(startIndex); - if (validator != null && !validator.validate(sender, value, (s, objects) -> command.sendMessage(sender, s, objects))) { - if (optional != null && next != null) { - return next.validate(sender, values, startIndex); - } - return false; - } - if (next != null) { - return next.validate(sender, values, startIndex + 1); - } - return true; - } - public boolean guardCheck(T sender, String[] args, int startIndex) { if (varArgType != null) { for (int i = startIndex; i < args.length; i++) { diff --git a/src/de/steamwar/command/SubCommand.java b/src/de/steamwar/command/SubCommand.java index f8e7014..d3a266c 100644 --- a/src/de/steamwar/command/SubCommand.java +++ b/src/de/steamwar/command/SubCommand.java @@ -114,7 +114,6 @@ public class SubCommand { return true; } } - commandPart.validate(sender, objects, 0); commandPart.guardCheck(sender, args, 0); objects.add(0, senderFunction.apply(sender)); method.setAccessible(true);