Add PreviousArguments for better handling in AbstractTypeMapper

Dieser Commit ist enthalten in:
yoyosource 2023-01-15 16:40:39 +01:00
Ursprung bd7635da0d
Commit 7e67f74571
12 geänderte Dateien mit 370 neuen und 266 gelöschten Zeilen

Datei anzeigen

@ -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<T> {
@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<T> implements AbstractTypeMapper<T, Object> {
private AbstractTypeMapper<T, Object> inner;
public Handler(AbstractSWCommand.Mapper mapper, Map<String, AbstractTypeMapper<T, ?>> localTypeMapper) {
inner = (AbstractTypeMapper<T, Object>) 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<String> tabCompletes(T sender, PreviousArguments previousArguments, String s) {
return inner.tabCompletes(sender, previousArguments, s);
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@ -322,10 +348,25 @@ public abstract class AbstractSWCommand<T> {
@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<T> implements AbstractValidator<T, Object> {
private AbstractValidator<T, Object> inner;
public Handler(AbstractSWCommand.Validator validator, Class<?> clazz, Map<String, AbstractValidator<T, ?>> localValidator) {
inner = (AbstractValidator<T, Object>) 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<T> {
@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<T> {
boolean allowISE() default false;
int[] falseValues() default { 0 };
class Handler<T> implements AbstractTypeMapper<T, Object> {
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<String> tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value()));
Set<Integer> 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<String> 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<String> tabCompletes(T sender, PreviousArguments previousArguments, String s) {
return inner.tabCompletes(sender, previousArguments, s);
}
}
}
@Retention(RetentionPolicy.RUNTIME)
@ -376,7 +465,7 @@ public abstract class AbstractSWCommand<T> {
@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<T> {
* This is the short form for 'allowEmptyArrays'.
*/
boolean allowEAs() default true;
class Handler<T> implements AbstractValidator<T, Object> {
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<T> {
@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<T> {
double doubleValue() default Double.MIN_VALUE;
boolean inclusive() default true;
class Handler<T> implements AbstractValidator<T, Number> {
private int value;
private Function<Number, Number> 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<T> {
double doubleValue() default Double.MAX_VALUE;
boolean inclusive() default true;
class Handler<T> implements AbstractValidator<T, Number> {
private int value;
private Function<Number, Number> 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<Number, Number> 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);
}
}
}

Datei anzeigen

@ -25,14 +25,29 @@ public interface AbstractTypeMapper<K, T> extends AbstractValidator<K, T> {
/**
* The CommandSender can be null!
*/
// TODO: Change the 'previousArguments' to List<Object> or something like that
// TODO: Change T return value to Pair<T, Boolean> 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<String> tabCompletes(K sender, String[] previousArguments, String s);
@Deprecated
default Collection<String> tabCompletes(K sender, String[] previousArguments, String s) {
throw new IllegalArgumentException("This method is deprecated and should not be used anymore!");
}
default Collection<String> tabCompletes(K sender, PreviousArguments previousArguments, String s) {
return tabCompletes(sender, previousArguments.userArgs, s);
}
}

Datei anzeigen

@ -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;

Datei anzeigen

@ -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:
* <ul>
* <li>Annotation this annotation annotates</li>
* <li>{@link Class}</li>
* <li>{@link AbstractTypeMapper}, optional, if not present only one per parameter</li>
* <li>{@link java.util.Map} with types {@link String} and {@link AbstractValidator}</li>
* </ul>
*/
@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:
* <ul>
* <li>Annotation this annotation annotates</li>
* <li>{@link Class}</li>
* <li>{@link java.util.Map} with types {@link String} and {@link AbstractValidator}</li>
* </ul>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)

Datei anzeigen

@ -32,7 +32,8 @@ import java.util.function.Consumer;
class CommandPart<T> {
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<T> {
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<T> {
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<T> {
}
}
public void generateTabComplete(List<String> current, T sender, String[] args, int startIndex) {
public void generateTabComplete(List<String> current, T sender, String[] args, List<Object> 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<String> strings = tabCompletes(sender, args, args.length - 1);
Collection<String> strings = tabCompletes(sender, args, mappedArgs, args.length - 1);
if (strings != null) {
current.addAll(strings);
}
@ -140,40 +141,43 @@ class CommandPart<T> {
}
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<String> strings = tabCompletes(sender, args, startIndex);
Collection<String> 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<String> tabCompletes(T sender, String[] args, int startIndex) {
private Collection<String> tabCompletes(T sender, String[] args, List<Object> 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<Runnable> errors, T sender, String[] args, int index) {
private CheckArgumentResult checkArgument(Consumer<Runnable> errors, T sender, String[] args, List<Object> 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);
}

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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<T> implements AbstractValidator<T, Object> {
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<T> implements AbstractValidator<T, Number> {
private int value;
private Function<Number, Number> 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<T> implements AbstractValidator<T, Number> {
private int value;
private Function<Number, Number> 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;
}
}
}

Datei anzeigen

@ -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 <https://www.gnu.org/licenses/>.
*/
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> T getMappedArg(int index) {
return (T) mappedArgs[mappedArgs.length - index - 1];
}
}

Datei anzeigen

@ -82,49 +82,7 @@ public class SWCommandUtils {
MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper);
}
public static <T> AbstractTypeMapper<T, ?> getTypeMapper(Parameter parameter, Map<String, AbstractTypeMapper<T, ?>> 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<Enum<?>>) 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<String> tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value()));
Set<Integer> 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<String> 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 <T> AbstractTypeMapper<T, ?> getTypeMapper(String name, Map<String, AbstractTypeMapper<T, ?>> localTypeMapper) {
AbstractTypeMapper<T, ?> typeMapper = localTypeMapper.getOrDefault(name, (AbstractTypeMapper<T, ?>) 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 <T> AbstractValidator<T, ?> getValidator(Parameter parameter, Map<String, AbstractValidator<T, ?>> localValidator) {
public static <T> AbstractTypeMapper<T, ?> getTypeMapper(Parameter parameter, Map<String, AbstractTypeMapper<T, ?>> 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<Enum<?>>) clazz);
}
return getTypeMapper(clazz.getTypeName(), localTypeMapper);
}
public static <T> AbstractValidator<T, ?> getErrorMessage(Parameter parameter) {
AbstractSWCommand.ErrorMessage errorMessage = parameter.getAnnotation(AbstractSWCommand.ErrorMessage.class);
if (errorMessage != null) {
return (AbstractValidator<T, Object>) (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 <T> AbstractValidator<T, ?> getValidator(String s, Map<String, AbstractValidator<T, ?>> localGuardChecker) {
AbstractValidator<T, ?> validator = localGuardChecker.getOrDefault(s, (AbstractValidator<T, ?>) VALIDATOR_FUNCTIONS.getOrDefault(s, null));
if (validator == null) {
public static <T> AbstractValidator<T, ?> getValidator(AbstractSWCommand.Validator validator, Class<?> type, Map<String, AbstractValidator<T, ?>> localValidator) {
String s = validator.value() != null && !validator.value().isEmpty() ? validator.value() : type.getTypeName();
AbstractValidator<T, ?> concreteValidator = localValidator.getOrDefault(s, (AbstractValidator<T, ?>) VALIDATOR_FUNCTIONS.getOrDefault(s, null));
if (concreteValidator == null) {
throw new IllegalArgumentException("No validator found for " + s);
}
return validator;
return concreteValidator;
}
public static <K, T> void addMapper(Class<T> clazz, AbstractTypeMapper<K, T> mapper) {

Datei anzeigen

@ -61,7 +61,10 @@ public class SubCommand<T> {
Parameter[] parameters = method.getParameters();
comparableValue = parameters[parameters.length - 1].isVarArgs() ? Integer.MAX_VALUE : -parameters.length;
validator = (AbstractValidator<T, T>) SWCommandUtils.getValidator(parameters[0], localValidator);
AbstractSWCommand.Validator validator = parameters[0].getAnnotation(AbstractSWCommand.Validator.class);
if (validator != null) {
this.validator = (AbstractValidator<T, T>) SWCommandUtils.getValidator(validator, parameters[0].getType(), localValidator);
}
commandPart = generateCommandPart(abstractSWCommand, subCommand, parameters, localTypeMapper, localValidator);
@ -120,7 +123,7 @@ public class SubCommand<T> {
return null;
}
List<String> 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<T> {
}
for (int i = 1; i < parameters.length; i++) {
Parameter parameter = parameters[i];
AbstractTypeMapper<T, ?> typeMapper = SWCommandUtils.getTypeMapper(parameter, localTypeMapper);
AbstractValidator<T, Object> validator = (AbstractValidator<T, Object>) SWCommandUtils.getValidator(parameter, localValidator);
AbstractTypeMapper<T, ?> 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<T> {
if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) {
commandPart.addValidator((AbstractValidator<T, Object>) STRING_SPACE_FILTER);
}
handleImplicitTypeValidator(parameter, commandPart, true);
commandPart.addValidator(validator);
commandPart.addValidator((AbstractValidator<T, Object>) SWCommandUtils.getErrorMessage(parameter));
handleImplicitTypeValidator(parameter, commandPart, false);
handleImplicitTypeValidator(parameter, commandPart, localValidator);
if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) {
commandPart.addValidator((AbstractValidator<T, Object>) NULL_FILTER);
}
@ -169,29 +168,72 @@ public class SubCommand<T> {
return first;
}
private static <T> void handleImplicitTypeValidator(Parameter parameter, CommandPart<T> commandPart, boolean beforeValidatorAnnotation) {
private static <T> AbstractTypeMapper<T, Object> handleImplicitTypeMapper(Parameter parameter, Map<String, AbstractTypeMapper<T, ?>> localTypeMapper) {
Class<?> type = parameter.getType();
if (parameter.isVarArgs()) {
type = type.getComponentType();
}
Annotation[] annotations = parameter.getAnnotations();
Constructor<?> sourceConstructor = null;
Annotation sourceAnnotation = null;
List<Constructor<?>> parentConstructors = new ArrayList<>();
List<Annotation> 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<T, Object> current;
if (sourceAnnotation != null) {
current = createInstance(sourceConstructor, sourceAnnotation, type, localTypeMapper);
} else {
current = (AbstractTypeMapper<T, Object>) 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 <T> void handleImplicitTypeValidator(Parameter parameter, CommandPart<T> commandPart, Map<String, AbstractValidator<T, ?>> localValidator) {
Annotation[] annotations = parameter.getAnnotations();
Map<Integer, List<AbstractValidator<T, Object>>> 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<T, Object> validator;
if (!annotation.annotationType().isAssignableFrom(parameterTypes[0])) continue;
if (!Class.class.isAssignableFrom(parameterTypes[1])) continue;
try {
validator = (AbstractValidator<T, Object>) constructor.newInstance(annotation, commandPart.getType());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
AbstractValidator<T, Object> validator = createInstance(constructors[0], annotation, commandPart.getType(), localValidator);
validators.computeIfAbsent(implicitValidator.order(), integer -> new ArrayList<>()).add(validator);
}
List<Integer> keys = new ArrayList<>(validators.keySet());
@ -204,42 +246,33 @@ public class SubCommand<T> {
}
}
private static <T> T createInstance(Constructor<?> constructor, Object... parameter) {
Class<?>[] types = constructor.getParameterTypes();
List<Object> 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<?, Object> NULL_FILTER = (sender, value, messageSender) -> value != null;
private static final AbstractValidator<?, Object> STRING_SPACE_FILTER = (sender, value, messageSender) -> {
if (!(value instanceof String)) return true;
return !((String) value).contains(" ");
};
private static AbstractValidator<?, Object> createMinValidator(Class<?> clazz, AbstractSWCommand.Min min) {
Function<Number, Number> 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<?, Object> createMaxValidator(Class<?> clazz, AbstractSWCommand.Max max) {
Function<Number, Number> 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);
}
}

Datei anzeigen

@ -39,7 +39,7 @@ public class BetterExceptionCommand extends TestSWCommand {
public TestTypeMapper<String> tabCompleteException() {
return new TestTypeMapper<String>() {
@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<String> tabCompletes(String sender, String[] previousArguments, String s) {
public Collection<String> tabCompletes(String sender, PreviousArguments previousArguments, String s) {
throw new SecurityException();
}
};

Datei anzeigen

@ -44,12 +44,12 @@ public class CacheCommand extends TestSWCommand {
System.out.println("TypeMapper register");
return new TestTypeMapper<Integer>() {
@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<String> tabCompletes(String sender, String[] previousArguments, String s) {
public Collection<String> tabCompletes(String sender, PreviousArguments previousArguments, String s) {
return Arrays.asList(count.getAndIncrement() + "");
}
};

Datei anzeigen

@ -43,7 +43,7 @@ public class NullMapperCommand extends TestSWCommand {
public TestTypeMapper<String> typeMapper() {
return new TestTypeMapper<String>() {
@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<String> tabCompletes(String sender, String[] previousArguments, String s) {
public Collection<String> tabCompletes(String sender, PreviousArguments previousArguments, String s) {
return null;
}
};