From 7e67f745718b38d3cfff2995656becc943d21ccd Mon Sep 17 00:00:00 2001 From: yoyosource Date: Sun, 15 Jan 2023 16:40:39 +0100 Subject: [PATCH] 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; } };