Add CommandMetaData.ImplicitValidator for easier extension of implicit validators
Einige Prüfungen sind fehlgeschlagen
SteamWarCI Build failed

Dieser Commit ist enthalten in:
yoyosource 2023-01-15 11:23:58 +01:00
Ursprung 518207f343
Commit bd7635da0d
6 geänderte Dateien mit 192 neuen und 22 gelöschten Zeilen

Datei anzeigen

@ -183,7 +183,7 @@ public abstract class AbstractSWCommand<T> {
private boolean validateMethod(Method method) { private boolean validateMethod(Method method) {
if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> { if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> {
MetaData.Method methodMetaData = annotation.annotationType().getAnnotation(MetaData.Method.class); CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class);
if (methodMetaData == null) return null; if (methodMetaData == null) return null;
if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0]; if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0];
return methodMetaData.value(); return methodMetaData.value();
@ -193,7 +193,7 @@ public abstract class AbstractSWCommand<T> {
Class<?> type = parameter.getType(); Class<?> type = parameter.getType();
if (parameter.isVarArgs()) type = type.getComponentType(); if (parameter.isVarArgs()) type = type.getComponentType();
if (!checkType(parameter.getAnnotations(), type, annotation -> { if (!checkType(parameter.getAnnotations(), type, annotation -> {
MetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(MetaData.Parameter.class); CommandMetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Parameter.class);
if (parameterMetaData == null) return null; if (parameterMetaData == null) return null;
return parameterMetaData.value(); return parameterMetaData.value();
}, "The parameter '" + parameter + "'")) valid = false; }, "The parameter '" + parameter + "'")) valid = false;
@ -269,7 +269,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@Repeatable(Register.Registeres.class) @Repeatable(Register.Registeres.class)
@MetaData.Method(value = void.class, minParameterCount = 1) @CommandMetaData.Method(value = void.class, minParameterCount = 1)
protected @interface Register { protected @interface Register {
/** /**
@ -286,7 +286,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@MetaData.Method(value = void.class, minParameterCount = 1) @CommandMetaData.Method(value = void.class, minParameterCount = 1)
@interface Registeres { @interface Registeres {
Register[] value(); Register[] value();
} }
@ -294,7 +294,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD}) @Target({ElementType.PARAMETER, ElementType.METHOD})
@MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
protected @interface Mapper { protected @interface Mapper {
String value(); String value();
@ -303,7 +303,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
protected @interface ClassMapper { protected @interface ClassMapper {
Class<?> value(); Class<?> value();
@ -312,7 +312,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0) @CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
protected @interface Cached { protected @interface Cached {
long cacheDuration() default 5; long cacheDuration() default 5;
TimeUnit timeUnit() default TimeUnit.SECONDS; TimeUnit timeUnit() default TimeUnit.SECONDS;
@ -321,7 +321,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.METHOD}) @Target({ElementType.PARAMETER, ElementType.METHOD})
@MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0)
protected @interface Validator { protected @interface Validator {
String value() default ""; String value() default "";
@ -330,7 +330,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0) @CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0)
protected @interface ClassValidator { protected @interface ClassValidator {
Class<?> value(); Class<?> value();
@ -341,7 +341,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER}) @Target({ElementType.PARAMETER})
@MetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class}) @CommandMetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class})
protected @interface StaticValue { protected @interface StaticValue {
String[] value(); String[] value();
@ -376,6 +376,7 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER}) @Target({ElementType.PARAMETER})
@CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.ErrorMessageValidator.class, order = 1)
protected @interface ErrorMessage { protected @interface ErrorMessage {
/** /**
* Error message to be displayed when the parameter is invalid. * Error message to be displayed when the parameter is invalid.
@ -398,13 +399,14 @@ public abstract class AbstractSWCommand<T> {
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER}) @Target({ElementType.PARAMETER})
@MetaData.Parameter({String.class}) @CommandMetaData.Parameter({String.class})
protected @interface Quotable { protected @interface Quotable {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER}) @Target({ElementType.PARAMETER})
@MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class})
@CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MinValidator.class, order = 2)
protected @interface Min { protected @interface Min {
int intValue() default Integer.MIN_VALUE; int intValue() default Integer.MIN_VALUE;
long longValue() default Long.MIN_VALUE; long longValue() default Long.MIN_VALUE;
@ -416,7 +418,8 @@ public abstract class AbstractSWCommand<T> {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER}) @Target({ElementType.PARAMETER})
@MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class}) @CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class})
@CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MaxValidator.class, order = 2)
protected @interface Max { protected @interface Max {
int intValue() default Integer.MAX_VALUE; int intValue() default Integer.MAX_VALUE;
long longValue() default Long.MAX_VALUE; long longValue() default Long.MAX_VALUE;

Datei anzeigen

@ -25,6 +25,8 @@ public interface AbstractTypeMapper<K, T> extends AbstractValidator<K, T> {
/** /**
* The CommandSender can be null! * 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); T map(K sender, String[] previousArguments, String s);
@Override @Override

Datei anzeigen

@ -24,8 +24,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@interface MetaData { public @interface CommandMetaData {
/**
* This annotation is only for internal use.
*/
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) @Target(ElementType.ANNOTATION_TYPE)
@interface Method { @interface Method {
@ -34,9 +37,33 @@ import java.lang.annotation.Target;
int maxParameterCount() default Integer.MAX_VALUE; int maxParameterCount() default Integer.MAX_VALUE;
} }
/**
* This annotation denotes what types are allowed as parameter types the annotation annotated with can use.
*/
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE) @Target(ElementType.ANNOTATION_TYPE)
@interface Parameter { @interface Parameter {
Class<?>[] value(); Class<?>[] value();
} }
/**
* This annotation can be used in conjunction with a class that implement {@link AbstractValidator} to
* create custom validator short hands for commands. The validator class itself should contain a constructor
* with two parameters the first is the annotation this annotation is used on and the second is of type {@link Class}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@interface ImplicitValidator {
/**
* The validator class that should be used.
*/
Class<?> handler();
/**
* Defines when this validator should be processed. Negative numbers denote that this will be
* processed before {@link AbstractSWCommand.Validator} and positive numbers
* denote that this will be processed after {@link AbstractSWCommand.Validator}.
*/
int order();
}
} }

Datei anzeigen

@ -192,4 +192,8 @@ class CommandPart<T> {
} }
return new CheckArgumentResult(true, value); return new CheckArgumentResult(true, value);
} }
public Class<?> getType() {
return varArgType != null ? varArgType : parameter.getType();
}
} }

Datei anzeigen

@ -0,0 +1,102 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2022 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <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

@ -19,10 +19,13 @@
package de.steamwar.command; package de.steamwar.command;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -142,22 +145,16 @@ public class SubCommand<T> {
AbstractValidator<T, Object> validator = (AbstractValidator<T, Object>) SWCommandUtils.getValidator(parameter, localValidator); AbstractValidator<T, Object> validator = (AbstractValidator<T, Object>) SWCommandUtils.getValidator(parameter, localValidator);
Class<?> varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; Class<?> varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null;
AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class);
AbstractSWCommand.Min min = parameter.getAnnotation(AbstractSWCommand.Min.class);
AbstractSWCommand.Max max = parameter.getAnnotation(AbstractSWCommand.Max.class);
CommandPart<T> commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); CommandPart<T> commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i);
commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG());
if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) { if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) {
commandPart.addValidator((AbstractValidator<T, Object>) STRING_SPACE_FILTER); commandPart.addValidator((AbstractValidator<T, Object>) STRING_SPACE_FILTER);
} }
handleImplicitTypeValidator(parameter, commandPart, true);
commandPart.addValidator(validator); commandPart.addValidator(validator);
commandPart.addValidator((AbstractValidator<T, Object>) SWCommandUtils.getErrorMessage(parameter)); commandPart.addValidator((AbstractValidator<T, Object>) SWCommandUtils.getErrorMessage(parameter));
if (min != null) { handleImplicitTypeValidator(parameter, commandPart, false);
commandPart.addValidator((AbstractValidator<T, Object>) createMinValidator(varArgType != null ? varArgType : parameter.getType(), min));
}
if (max != null) {
commandPart.addValidator((AbstractValidator<T, Object>) createMaxValidator(varArgType != null ? varArgType : parameter.getType(), max));
}
if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) {
commandPart.addValidator((AbstractValidator<T, Object>) NULL_FILTER); commandPart.addValidator((AbstractValidator<T, Object>) NULL_FILTER);
} }
@ -172,6 +169,41 @@ public class SubCommand<T> {
return first; return first;
} }
private static <T> void handleImplicitTypeValidator(Parameter parameter, CommandPart<T> commandPart, boolean beforeValidatorAnnotation) {
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);
}
validators.computeIfAbsent(implicitValidator.order(), integer -> new ArrayList<>()).add(validator);
}
List<Integer> keys = new ArrayList<>(validators.keySet());
keys.sort(Integer::compareTo);
for (Integer key : keys) {
List<AbstractValidator<T, Object>> list = validators.get(key);
for (AbstractValidator<T, Object> validator : list) {
commandPart.addValidator(validator);
}
}
}
private static final AbstractValidator<?, Object> NULL_FILTER = (sender, value, messageSender) -> value != null; private static final AbstractValidator<?, Object> NULL_FILTER = (sender, value, messageSender) -> value != null;
private static final AbstractValidator<?, Object> STRING_SPACE_FILTER = (sender, value, messageSender) -> { private static final AbstractValidator<?, Object> STRING_SPACE_FILTER = (sender, value, messageSender) -> {