CMDAPIRework #25
@ -183,7 +183,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
private boolean validateMethod(Method method) {
|
||||
if (!checkType(method.getAnnotations(), method.getReturnType(), annotation -> {
|
||||
MetaData.Method methodMetaData = annotation.annotationType().getAnnotation(MetaData.Method.class);
|
||||
CommandMetaData.Method methodMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Method.class);
|
||||
if (methodMetaData == null) return null;
|
||||
if (method.getParameterCount() > methodMetaData.maxParameterCount() || method.getParameterCount() < methodMetaData.minParameterCount()) return new Class[0];
|
||||
return methodMetaData.value();
|
||||
@ -193,7 +193,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
Class<?> type = parameter.getType();
|
||||
if (parameter.isVarArgs()) type = type.getComponentType();
|
||||
if (!checkType(parameter.getAnnotations(), type, annotation -> {
|
||||
MetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(MetaData.Parameter.class);
|
||||
CommandMetaData.Parameter parameterMetaData = annotation.annotationType().getAnnotation(CommandMetaData.Parameter.class);
|
||||
if (parameterMetaData == null) return null;
|
||||
return parameterMetaData.value();
|
||||
}, "The parameter '" + parameter + "'")) valid = false;
|
||||
@ -269,7 +269,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Repeatable(Register.Registeres.class)
|
||||
@MetaData.Method(value = void.class, minParameterCount = 1)
|
||||
@CommandMetaData.Method(value = void.class, minParameterCount = 1)
|
||||
protected @interface Register {
|
||||
|
||||
/**
|
||||
@ -286,7 +286,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@MetaData.Method(value = void.class, minParameterCount = 1)
|
||||
@CommandMetaData.Method(value = void.class, minParameterCount = 1)
|
||||
@interface Registeres {
|
||||
Register[] value();
|
||||
}
|
||||
@ -294,7 +294,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||
@MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
|
||||
@CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
|
||||
protected @interface Mapper {
|
||||
String value();
|
||||
|
||||
@ -303,7 +303,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
|
||||
@CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
|
||||
protected @interface ClassMapper {
|
||||
Class<?> value();
|
||||
|
||||
@ -312,7 +312,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@MetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
|
||||
@CommandMetaData.Method(value = AbstractTypeMapper.class, maxParameterCount = 0)
|
||||
protected @interface Cached {
|
||||
long cacheDuration() default 5;
|
||||
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||
@ -321,7 +321,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||
@MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0)
|
||||
@CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0)
|
||||
protected @interface Validator {
|
||||
String value() default "";
|
||||
|
||||
@ -330,7 +330,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@MetaData.Method(value = AbstractValidator.class, maxParameterCount = 0)
|
||||
@CommandMetaData.Method(value = AbstractValidator.class, maxParameterCount = 0)
|
||||
protected @interface ClassValidator {
|
||||
Class<?> value();
|
||||
|
||||
@ -341,7 +341,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@MetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class})
|
||||
@CommandMetaData.Parameter({String.class, int.class, Integer.class, long.class, Long.class, boolean.class, Boolean.class})
|
||||
protected @interface StaticValue {
|
||||
String[] value();
|
||||
|
||||
@ -376,6 +376,7 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.ErrorMessageValidator.class, order = 1)
|
||||
protected @interface ErrorMessage {
|
||||
/**
|
||||
* Error message to be displayed when the parameter is invalid.
|
||||
@ -398,13 +399,14 @@ public abstract class AbstractSWCommand<T> {
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@MetaData.Parameter({String.class})
|
||||
@CommandMetaData.Parameter({String.class})
|
||||
protected @interface Quotable {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class})
|
||||
@CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class})
|
||||
@CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MinValidator.class, order = 2)
|
||||
protected @interface Min {
|
||||
int intValue() default Integer.MIN_VALUE;
|
||||
long longValue() default Long.MIN_VALUE;
|
||||
@ -416,7 +418,8 @@ public abstract class AbstractSWCommand<T> {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@MetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class})
|
||||
@CommandMetaData.Parameter({int.class, Integer.class, long.class, Long.class, float.class, Float.class, double.class, Double.class})
|
||||
@CommandMetaData.ImplicitValidator(handler = ImplicitTypeValidators.MaxValidator.class, order = 2)
|
||||
protected @interface Max {
|
||||
int intValue() default Integer.MAX_VALUE;
|
||||
long longValue() default Long.MAX_VALUE;
|
||||
|
@ -25,6 +25,8 @@ 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);
|
||||
|
||||
@Override
|
||||
|
@ -24,8 +24,11 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@interface MetaData {
|
||||
public @interface CommandMetaData {
|
||||
|
||||
/**
|
||||
* This annotation is only for internal use.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@interface Method {
|
||||
@ -34,9 +37,33 @@ import java.lang.annotation.Target;
|
||||
int maxParameterCount() default Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This annotation denotes what types are allowed as parameter types the annotation annotated with can use.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@interface Parameter {
|
||||
Class<?>[] value();
|
||||
}
|
||||
|
||||
/**
|
||||
* This annotation can be used in conjunction with a class that implement {@link AbstractValidator} to
|
||||
* create custom validator short hands for commands. The validator class itself should contain a constructor
|
||||
* with two parameters the first is the annotation this annotation is used on and the second is of type {@link Class}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@interface ImplicitValidator {
|
||||
/**
|
||||
* The validator class that should be used.
|
||||
*/
|
||||
Class<?> handler();
|
||||
|
||||
/**
|
||||
* Defines when this validator should be processed. Negative numbers denote that this will be
|
||||
* processed before {@link AbstractSWCommand.Validator} and positive numbers
|
||||
* denote that this will be processed after {@link AbstractSWCommand.Validator}.
|
||||
*/
|
||||
int order();
|
||||
}
|
||||
}
|
@ -192,4 +192,8 @@ class CommandPart<T> {
|
||||
}
|
||||
return new CheckArgumentResult(true, value);
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return varArgType != null ? varArgType : parameter.getType();
|
||||
}
|
||||
}
|
||||
|
102
src/de/steamwar/command/ImplicitTypeValidators.java
Normale Datei
102
src/de/steamwar/command/ImplicitTypeValidators.java
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -19,10 +19,13 @@
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
@ -142,22 +145,16 @@ public class SubCommand<T> {
|
||||
AbstractValidator<T, Object> validator = (AbstractValidator<T, Object>) SWCommandUtils.getValidator(parameter, localValidator);
|
||||
Class<?> varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null;
|
||||
AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class);
|
||||
AbstractSWCommand.Min min = parameter.getAnnotation(AbstractSWCommand.Min.class);
|
||||
AbstractSWCommand.Max max = parameter.getAnnotation(AbstractSWCommand.Max.class);
|
||||
|
||||
CommandPart<T> commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i);
|
||||
commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG());
|
||||
if (parameter.getAnnotation(AbstractSWCommand.Quotable.class) == null) {
|
||||
commandPart.addValidator((AbstractValidator<T, Object>) STRING_SPACE_FILTER);
|
||||
}
|
||||
handleImplicitTypeValidator(parameter, commandPart, true);
|
||||
commandPart.addValidator(validator);
|
||||
commandPart.addValidator((AbstractValidator<T, Object>) SWCommandUtils.getErrorMessage(parameter));
|
||||
if (min != null) {
|
||||
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));
|
||||
}
|
||||
handleImplicitTypeValidator(parameter, commandPart, false);
|
||||
if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) {
|
||||
commandPart.addValidator((AbstractValidator<T, Object>) NULL_FILTER);
|
||||
}
|
||||
@ -172,6 +169,41 @@ public class SubCommand<T> {
|
||||
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> STRING_SPACE_FILTER = (sender, value, messageSender) -> {
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren