Add Annotation Processor for compile time checks

This commit is contained in:
2025-11-23 20:27:44 +01:00
parent f6fc65a370
commit 18659385d2
40 changed files with 1029 additions and 185 deletions
@@ -132,14 +132,14 @@ public class CommandUtils {
return annotations;
}
public static ToIntFunction<Number> createComparator(String type, Class<?> clazz, int iValue, long lValue, float fValue, double dValue) {
if (clazz == int.class || clazz == Integer.class) {
public static ToIntFunction<Number> createComparator(String type, Handler.TypeWrapper clazz, int iValue, long lValue, float fValue, double dValue) {
if (clazz.is(int.class) || clazz.is(Integer.class)) {
return number -> Integer.compare(number.intValue(), iValue);
} else if (clazz == long.class || clazz == Long.class) {
} else if (clazz.is(long.class) || clazz.is(Long.class)) {
return number -> Long.compare(number.longValue(), lValue);
} else if (clazz == float.class || clazz == Float.class) {
} else if (clazz.is(float.class) || clazz.is(Float.class)) {
return number -> Float.compare(number.floatValue(), fValue);
} else if (clazz == double.class || clazz == Double.class) {
} else if (clazz.is(double.class) || clazz.is(Double.class)) {
return number -> Double.compare(number.doubleValue(), dValue);
} else {
throw new IllegalArgumentException(type + " annotation is not supported for " + clazz);
@@ -19,11 +19,15 @@
package de.steamwar.command;
import lombok.Getter;
import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
@@ -58,17 +62,29 @@ public interface Handler {
Class<? extends Handler> value();
}
class HandlerException extends Exception {
public HandlerException(String message) {
super(message);
}
}
interface DataCheckable {
boolean hasExecutorMapper(Class<?> clazz);
boolean hasMapper(String key);
boolean hasValidator(String key);
boolean hasSupplier(String key);
}
interface DataReadable {
Function<?, ?> getExecutorMapper(Class<?> clazz);
<A, B> AbstractTypeMapper<A, B> getMapper(String key);
<A, B> AbstractTypeValidator<A, B> getValidator(String key);
<A, B> AbstractTypeSupplier<A, B> getSupplier(String key);
}
@@ -76,17 +92,26 @@ public interface Handler {
Function<?, ?> getExecutorMapper(Class<?> clazz);
<A, B> void addMapper(String key, boolean local, AbstractTypeMapper<A, B> mapper);
<A, B> void addValidator(String key, boolean local, AbstractTypeValidator<A, B> validator);
<A, B> void addSupplier(String key, boolean local, AbstractTypeSupplier<A, B> supplier);
/**
* Invoking the same method twice will result in the exact same result.
*/
<T> T invoke(Method method);
void addCommand(Method method, String[] subCommand, String[] description, boolean noTabComplete);
}
interface HandlerMethod<T extends Annotation> extends Handler {
void check(T annotation, Method method, DataCheckable dataCheckable) throws Exception;
default void check(T annotation, Method method, DataCheckable dataCheckable) throws Exception {
check(annotation, new MethodWrapper.ForMethod(method), dataCheckable);
}
default void check(T annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
}
int getRunPriority();
@@ -94,7 +119,12 @@ public interface Handler {
}
interface HandlerParameter<T extends Annotation, A, B> extends Handler {
void check(T annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception;
default void check(T annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
check(annotation, new MethodWrapper.ForMethod((Method) parameter.getDeclaringExecutable()), new ParameterWrapper.ForParameter(parameter), index, dataCheckable);
}
default void check(T annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
}
default boolean needsParentTypeMapper() {
return false;
@@ -118,4 +148,179 @@ public interface Handler {
return null;
}
}
interface MethodWrapper {
String getName();
int getParameterCount();
TypeWrapper getReturnType();
ParameterWrapper[] getParameters();
class ForMethod implements MethodWrapper {
@Getter
private final Method method;
public ForMethod(Method method) {
this.method = method;
}
@Override
public String getName() {
return method.getName();
}
@Override
public int getParameterCount() {
return method.getParameterCount();
}
@Override
public TypeWrapper getReturnType() {
return new TypeWrapper.ForClass(method.getReturnType());
}
@Override
public ParameterWrapper[] getParameters() {
return Arrays.stream(method.getParameters()).map(ParameterWrapper.ForParameter::new).toArray(ParameterWrapper[]::new);
}
}
}
interface ParameterWrapper {
TypeWrapper getType();
boolean isVarArgs();
boolean isAnnotationPresent(Class<? extends Annotation> annotation);
<A extends Annotation> A getAnnotation(Class<A> annotation);
String getName();
List<Annotation> getAnnotations();
class ForParameter implements ParameterWrapper {
@Getter
private final Parameter parameter;
public ForParameter(Parameter parameter) {
this.parameter = parameter;
}
@Override
public TypeWrapper getType() {
return new TypeWrapper.ForClass(this.parameter.getType());
}
@Override
public boolean isVarArgs() {
return parameter.isVarArgs();
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
return parameter.isAnnotationPresent(annotation);
}
@Override
public <A extends Annotation> A getAnnotation(Class<A> annotation) {
return parameter.getAnnotation(annotation);
}
@Override
public String getName() {
return parameter.getName();
}
@Override
public List<Annotation> getAnnotations() {
return CommandUtils.getAnnotations(parameter);
}
}
}
interface TypeWrapper {
boolean isAssignableTo(Class<?> clazz);
boolean isAssignableTo(TypeWrapper type);
boolean isPrimitive();
boolean isArray();
boolean isEnum();
boolean is(Class<?> clazz);
boolean is(TypeWrapper type);
TypeWrapper getComponentType();
String getName();
class ForClass implements TypeWrapper {
@Getter
private final Class<?> clazz;
public ForClass(Class<?> clazz) {
this.clazz = clazz;
}
@Override
public boolean isAssignableTo(Class<?> clazz) {
return clazz.isAssignableFrom(this.clazz);
}
@Override
public boolean isAssignableTo(TypeWrapper type) {
if (type instanceof ForClass) {
return ((ForClass) type).getClazz().isAssignableFrom(clazz);
}
return false;
}
@Override
public boolean isPrimitive() {
return clazz.isPrimitive();
}
@Override
public boolean isArray() {
return clazz.isArray();
}
@Override
public boolean isEnum() {
return clazz.isEnum();
}
@Override
public boolean is(Class<?> clazz) {
return this.clazz == clazz;
}
@Override
public boolean is(TypeWrapper type) {
if (type instanceof ForClass) {
return ((ForClass) type).getClazz() == this.clazz;
}
return false;
}
@Override
public TypeWrapper getComponentType() {
return new TypeWrapper.ForClass(this.clazz.getComponentType());
}
@Override
public String getName() {
return this.clazz.getName();
}
}
}
}
@@ -36,12 +36,12 @@ public final class AllowNullHandler {
public static final class Impl implements Handler.HandlerParameter<AllowNull, Object, Object> {
@Override
public void check(AllowNull annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(AllowNull annotation, MethodWrapper method, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("AllowNull annotation cannot be used on the first parameter");
throw new HandlerException("AllowNull annotation cannot be used on the first parameter");
}
if (parameter.getType().isPrimitive()) {
throw new UnsupportedOperationException("AllowNull annotation cannot be used on primitive types");
throw new HandlerException("AllowNull annotation cannot be used on primitive types");
}
}
}
@@ -47,15 +47,15 @@ public final class ArrayLengthHandler {
public static final class Impl implements Handler.HandlerParameter<ArrayLength, Object, Object> {
@Override
public void check(ArrayLength arrayLength, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(ArrayLength arrayLength, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("ArrayLength annotation cannot be used on first parameter");
throw new HandlerException("ArrayLength annotation cannot be used on first parameter");
}
if (!parameter.getType().isArray()) {
throw new UnsupportedOperationException("Parameter " + index + " of " + parameter.getDeclaringExecutable().getName() + " is not an array");
throw new HandlerException("Parameter " + index + " of " + methodWrapper.getName() + " is not an array");
}
if (arrayLength.min() > arrayLength.max()) {
throw new UnsupportedOperationException("Min ArrayLength cannot be smaller than Max ArrayLength");
throw new HandlerException("Min ArrayLength cannot be smaller than Max ArrayLength");
}
}
}
@@ -31,12 +31,12 @@ public final class CachedHandler {
public static final class Impl implements Handler.HandlerMethod<Cached> {
@Override
public void check(Cached annotation, Method method, DataCheckable dataCheckable) throws Exception {
public void check(Cached annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
if (method.getParameterCount() != 0) {
throw new UnsupportedOperationException("Cached method must have no parameters");
throw new HandlerException("Cached method must have no parameters");
}
if (!AbstractTypeMapper.class.isAssignableFrom(method.getReturnType())) {
throw new UnsupportedOperationException("Cached method must return AbstractTypeMapper");
if (!method.getReturnType().isAssignableTo(AbstractTypeMapper.class)) {
throw new HandlerException("Cached method must return AbstractTypeMapper");
}
}
@@ -31,12 +31,12 @@ public final class ClassMapperHandler {
public static final class Impl implements Handler.HandlerMethod<ClassMapper> {
@Override
public void check(ClassMapper annotation, Method method, DataCheckable dataCheckable) throws Exception {
public void check(ClassMapper annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
if (method.getParameterCount() != 0) {
throw new UnsupportedOperationException("ClassMapper method must have no parameters");
throw new HandlerException("ClassMapper method must have no parameters");
}
if (!AbstractTypeMapper.class.isAssignableFrom(method.getReturnType())) {
throw new UnsupportedOperationException("ClassMapper method must return AbstractTypeMapper");
if (!method.getReturnType().isAssignableTo(AbstractTypeMapper.class)) {
throw new HandlerException("ClassMapper method must return AbstractTypeMapper");
}
}
@@ -31,12 +31,12 @@ public final class ClassValidatorHandler {
public static final class Impl implements Handler.HandlerMethod<ClassValidator> {
@Override
public void check(ClassValidator annotation, Method method, DataCheckable dataCheckable) throws Exception {
public void check(ClassValidator annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
if (method.getParameterCount() != 0) {
throw new UnsupportedOperationException("ClassValidator method must have no parameters");
throw new HandlerException("ClassValidator method must have no parameters");
}
if (!AbstractTypeValidator.class.isAssignableFrom(method.getReturnType())) {
throw new UnsupportedOperationException("ClassValidator method must return AbstractValidator");
if (!method.getReturnType().isAssignableTo(AbstractTypeValidator.class)) {
throw new HandlerException("ClassValidator method must return AbstractValidator");
}
}
@@ -35,12 +35,12 @@ public final class EndsWithHandler {
public static final class Impl implements Handler.HandlerParameter<EndsWith, Object, String> {
@Override
public void check(EndsWith annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(EndsWith annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("EndsWith annotation cannot be used on first parameter");
throw new HandlerException("EndsWith annotation cannot be used on first parameter");
}
if (parameter.getType() != String.class) {
throw new UnsupportedOperationException("EndsWith annotation cannot be used on String parameter");
if (!parameter.getType().is(String.class)) {
throw new HandlerException("EndsWith annotation cannot be used on String parameter");
}
}
@@ -31,12 +31,12 @@ public final class ErrorMessageHandler {
public static final class Impl implements Handler.HandlerParameter<ErrorMessage, Object, Object> {
@Override
public void check(ErrorMessage errorMessage, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(ErrorMessage errorMessage, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("ErrorMessage annotation cannot be used on first parameter");
throw new HandlerException("ErrorMessage annotation cannot be used on first parameter");
}
if (!errorMessage.allowEAs() && !(parameter.isVarArgs() || parameter.getType().isArray())) {
throw new UnsupportedOperationException("ErrorMessage allowESs cannot be used on non array or varargs parameter");
throw new HandlerException("ErrorMessage allowESs cannot be used on non array or varargs parameter");
}
}
@@ -44,9 +44,9 @@ public final class GreedyHandler {
public static final class Impl implements Handler.HandlerParameter<Greedy, Object, Object> {
@Override
public void check(Greedy annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Greedy annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (!parameter.getType().isArray() || parameter.isVarArgs()) {
throw new UnsupportedOperationException("Greedy annotation cannot be used on non array parameters");
throw new HandlerException("Greedy annotation cannot be used on non array parameters");
}
}
}
@@ -35,9 +35,9 @@ public final class LengthHandler {
public static final class Impl implements Handler.HandlerParameter<Length, Object, Object> {
@Override
public void check(Length annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Length annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Length annotation cannot be used on first parameter");
throw new HandlerException("Length annotation cannot be used on first parameter");
}
}
@@ -32,26 +32,29 @@ public final class MapperHandler {
public static final class Impl implements Handler.HandlerMethod<Mapper>, Handler.HandlerParameter<Mapper, Object, Object> {
@Override
public void check(Mapper annotation, Method method, DataCheckable dataCheckable) throws Exception {
public void check(Mapper annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
if (method.getParameterCount() != 0) {
throw new UnsupportedOperationException("Mapper method must have no parameters");
throw new HandlerException("Mapper method must have no parameters");
}
if (!AbstractTypeMapper.class.isAssignableFrom(method.getReturnType())) {
throw new UnsupportedOperationException("Mapper method must return AbstractTypeMapper");
if (!method.getReturnType().isAssignableTo(AbstractTypeMapper.class)) {
throw new HandlerException("Mapper method must return AbstractTypeMapper");
}
if (annotation.value() != null && !annotation.value().isEmpty()) {
return;
}
Optional<Class<?>> genericType = getGenericTypeOfReturn(method);
if (annotation.type() == void.class) {
if (genericType.isEmpty()) {
throw new UnsupportedOperationException("Please supply a class type to the Mapper");
}
} else {
if (genericType.isPresent() && !annotation.type().isAssignableFrom(genericType.get())) {
throw new UnsupportedOperationException("Supplied type does not conform to actual type");
// TODO: Implement! for AnnotationProcessor as well!
if (method instanceof MethodWrapper.ForMethod) {
Optional<Class<?>> genericType = getGenericTypeOfReturn(((MethodWrapper.ForMethod) method).getMethod());
if (annotation.type() == void.class) {
if (genericType.isEmpty()) {
throw new HandlerException("Please supply a class type to the Mapper");
}
} else {
if (genericType.isPresent() && !annotation.type().isAssignableFrom(genericType.get())) {
throw new HandlerException("Supplied type does not conform to actual type");
}
}
}
}
@@ -74,9 +77,9 @@ public final class MapperHandler {
}
@Override
public void check(Mapper annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Mapper annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Mapper annotation cannot be used on first parameter");
throw new HandlerException("Mapper annotation cannot be used on first parameter");
}
if (!dataCheckable.hasMapper(annotation.value())) {
throw new UnsupportedOperationException("Mapper '" + annotation.value() + "' not found");
@@ -34,15 +34,15 @@ public final class MaxHandler {
public static final class Impl implements Handler.HandlerParameter<Max, Object, Number> {
@Override
public void check(Max max, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Max max, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Max annotation cannot be used on first parameter");
throw new HandlerException("Max annotation cannot be used on first parameter");
}
Class<?> type = parameter.getType();
TypeWrapper type = parameter.getType();
if (parameter.isVarArgs()) type = type.getComponentType();
if (type != int.class && type != Integer.class && type != long.class && type != Long.class && type != float.class && type != Float.class && type != double.class && type != Double.class) {
throw new UnsupportedOperationException("Parameter " + index + " of " + parameter.getDeclaringExecutable() + " is not a number.");
if (!type.is(int.class) && !type.is(Integer.class) && !type.is(long.class) && !type.is(Long.class) && !type.is(float.class) && !type.is(Float.class) && !type.is(double.class) && !type.is(Double.class)) {
throw new HandlerException("Parameter " + index + " of " + methodWrapper.getName() + " is not a number.");
}
if (!parameter.isAnnotationPresent(Min.class)) {
@@ -52,13 +52,13 @@ public final class MaxHandler {
Min min = parameter.getAnnotation(Min.class);
Number minValue;
if (type.equals(int.class) || type.equals(Integer.class)) {
if (type.is(int.class) || type.is(Integer.class)) {
minValue = min.intValue();
} else if (type.equals(long.class) || type.equals(Long.class)) {
} else if (type.is(long.class) || type.is(Long.class)) {
minValue = min.longValue();
} else if (type.equals(float.class) || type.equals(Float.class)) {
} else if (type.is(float.class) || type.is(Float.class)) {
minValue = min.floatValue();
} else if (type.equals(double.class) || type.equals(Double.class)) {
} else if (type.is(double.class) || type.is(Double.class)) {
minValue = min.doubleValue();
} else {
throw new SecurityException();
@@ -66,9 +66,9 @@ public final class MaxHandler {
ToIntFunction<Number> maxComparator = CommandUtils.createComparator("Max", type, max.intValue(), max.longValue(), max.floatValue(), max.doubleValue());
if (!(min.inclusive() && max.inclusive()) && maxComparator.applyAsInt(minValue) == 0) {
throw new UnsupportedOperationException("Min and Max cannot be equal if not both are inclusive");
throw new HandlerException("Min and Max cannot be equal if not both are inclusive");
} else if (maxComparator.applyAsInt(minValue) > 0) {
throw new UnsupportedOperationException("Max must be bigger then Min");
throw new HandlerException("Max must be bigger then Min");
}
}
@@ -83,7 +83,7 @@ public final class MaxHandler {
if (parameter.isVarArgs()) type = type.getComponentType();
int compareValue = max.inclusive() ? 0 : -1;
ToIntFunction<Number> comparator = CommandUtils.createComparator("Max", type, max.intValue(), max.longValue(), max.floatValue(), max.doubleValue());
ToIntFunction<Number> comparator = CommandUtils.createComparator("Max", new TypeWrapper.ForClass(type), max.intValue(), max.longValue(), max.floatValue(), max.doubleValue());
boolean onlyNegative = comparator.applyAsInt(0) >= 0;
Min min = parameter.getAnnotation(Min.class);
@@ -155,7 +155,7 @@ public final class MaxHandler {
}
int compareValue = max.inclusive() ? 0 : -1;
ToIntFunction<Number> comparator = CommandUtils.createComparator("Max", type, max.intValue(), max.longValue(), max.floatValue(), max.doubleValue());
ToIntFunction<Number> comparator = CommandUtils.createComparator("Max", new TypeWrapper.ForClass(type), max.intValue(), max.longValue(), max.floatValue(), max.doubleValue());
return (sender, value, messageSender) -> {
if (value == null) return true;
@@ -36,20 +36,20 @@ public final class MaxReferenceHandler {
public static final class Impl implements Handler.HandlerParameter<Max.Reference, Object, Number> {
@Override
public void check(Max.Reference annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Max.Reference annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Max.Reference annotation cannot be used on first parameter");
throw new HandlerException("Max.Reference annotation cannot be used on first parameter");
}
Class<?> type = parameter.getType();
TypeWrapper type = parameter.getType();
if (type.isArray()) type = type.getComponentType();
if (type != int.class && type != Integer.class && type != long.class && type != Long.class && type != float.class && type != Float.class && type != double.class && type != Double.class) {
throw new UnsupportedOperationException("Parameter " + index + " of " + parameter.getDeclaringExecutable() + " is not a number.");
if (!type.is(int.class) && !type.is(Integer.class) && !type.is(long.class) && !type.is(Long.class) && !type.is(float.class) && !type.is(Float.class) && !type.is(double.class) && !type.is(Double.class)) {
throw new HandlerException("Parameter " + index + " of " + methodWrapper.getName() + " is not a number.");
}
Parameter[] parameters = parameter.getDeclaringExecutable().getParameters();
ParameterWrapper[] parameters = methodWrapper.getParameters();
for (int i = index - 1; i >= 1; i--) {
Parameter toCheck = parameters[i];
ParameterWrapper toCheck = parameters[i];
Name name = toCheck.getAnnotation(Name.class);
String parameterName;
if (name == null) {
@@ -60,14 +60,14 @@ public final class MaxReferenceHandler {
if (!parameterName.equals(annotation.value())) {
continue;
}
Class<?> parameterType = toCheck.getType();
if (!type.isAssignableFrom(parameterType)) {
throw new UnsupportedOperationException("Parameter type being referenced cannot be assigned here!");
TypeWrapper parameterType = toCheck.getType();
if (!parameterType.isAssignableTo(type)) {
throw new HandlerException("Parameter type being referenced cannot be assigned here!");
} else {
return;
}
}
throw new UnsupportedOperationException("Parameter with name '" + annotation.value() + "' does not exist.");
throw new HandlerException("Parameter with name '" + annotation.value() + "' does not exist.");
}
@Override
@@ -79,7 +79,7 @@ public final class MaxReferenceHandler {
public AbstractTypeMapper<Object, Number> getTypeMapper(Max.Reference maxReference, Parameter parameter, int index, DataReadable dataReadable, AbstractTypeMapper<Object, Number> parentTypeMapper) {
Class<?> type = parameter.getType();
if (type.isArray()) type = type.getComponentType();
Class<?> finalType = type;
Handler.TypeWrapper finalType = new TypeWrapper.ForClass(type);
String name = maxReference.value();
int compareValue = maxReference.inclusive() ? 0 : -1;
@@ -119,7 +119,7 @@ public final class MaxReferenceHandler {
public AbstractTypeValidator<Object, Number> getValidator(Max.Reference maxReference, Parameter parameter, int index, DataReadable dataReadable) {
Class<?> type = parameter.getType();
if (type.isArray()) type = type.getComponentType();
Class<?> finalType = type;
Handler.TypeWrapper finalType = new TypeWrapper.ForClass(type);
String name = maxReference.value();
int compareValue = maxReference.inclusive() ? 0 : -1;
@@ -32,19 +32,18 @@ import java.util.function.ToIntFunction;
public final class MinHandler {
public static final class Impl implements Handler.HandlerParameter<Min, Object, Number> {
@Override
public void check(Min min, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Min annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Min annotation cannot be used on first parameter");
throw new HandlerException("Min annotation cannot be used on first parameter");
}
Class<?> type = parameter.getType();
TypeWrapper type = parameter.getType();
if (parameter.isVarArgs()) type = type.getComponentType();
if (type == int.class || type == Integer.class) return;
if (type == long.class || type == Long.class) return;
if (type == float.class || type == Float.class) return;
if (type == double.class || type == Double.class) return;
throw new UnsupportedOperationException("Parameter " + index + " of " + parameter.getDeclaringExecutable() + " is not a number.");
if (type.is(int.class) || type.is(Integer.class)) return;
if (type.is(long.class) || type.is(Long.class)) return;
if (type.is(float.class) || type.is(Float.class)) return;
if (type.is(double.class) || type.is(Double.class)) return;
throw new HandlerException("Parameter " + index + " of " + methodWrapper.getName() + " is not a number.");
}
@Override
@@ -58,7 +57,7 @@ public final class MinHandler {
if (parameter.isVarArgs()) type = type.getComponentType();
int compareValue = min.inclusive() ? 0 : 1;
ToIntFunction<Number> comparator = CommandUtils.createComparator("Min", type, min.intValue(), min.longValue(), min.floatValue(), min.doubleValue());
ToIntFunction<Number> comparator = CommandUtils.createComparator("Min", new TypeWrapper.ForClass(type), min.intValue(), min.longValue(), min.floatValue(), min.doubleValue());
boolean onlyPositive = comparator.applyAsInt(0) < 0;
Max max = parameter.getAnnotation(Max.class);
@@ -130,7 +129,7 @@ public final class MinHandler {
}
int compareValue = min.inclusive() ? 0 : 1;
ToIntFunction<Number> comparator = CommandUtils.createComparator("Min", type, min.intValue(), min.longValue(), min.floatValue(), min.doubleValue());
ToIntFunction<Number> comparator = CommandUtils.createComparator("Min", new TypeWrapper.ForClass(type), min.intValue(), min.longValue(), min.floatValue(), min.doubleValue());
return (sender, value, messageSender) -> {
if (value == null) return true;
@@ -36,20 +36,20 @@ public final class MinReferenceHandler {
public static final class Impl implements Handler.HandlerParameter<Min.Reference, Object, Number> {
@Override
public void check(Min.Reference annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Min.Reference annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Min.Reference annotation cannot be used on first parameter");
throw new HandlerException("Min.Reference annotation cannot be used on first parameter");
}
Class<?> type = parameter.getType();
TypeWrapper type = parameter.getType();
if (type.isArray()) type = type.getComponentType();
if (type != int.class && type != Integer.class && type != long.class && type != Long.class && type != float.class && type != Float.class && type != double.class && type != Double.class) {
throw new UnsupportedOperationException("Parameter " + index + " of " + parameter.getDeclaringExecutable() + " is not a number.");
if (!type.is(int.class) && !type.is(Integer.class) && !type.is(long.class) && !type.is(Long.class) && !type.is(float.class) && !type.is(Float.class) && !type.is(double.class) && !type.is(Double.class)) {
throw new HandlerException("Parameter " + index + " of " + methodWrapper.getName() + " is not a number.");
}
Parameter[] parameters = parameter.getDeclaringExecutable().getParameters();
ParameterWrapper[] parameters = methodWrapper.getParameters();
for (int i = index - 1; i >= 1; i--) {
Parameter toCheck = parameters[i];
ParameterWrapper toCheck = parameters[i];
Name name = toCheck.getAnnotation(Name.class);
String parameterName;
if (name == null) {
@@ -60,14 +60,14 @@ public final class MinReferenceHandler {
if (!parameterName.equals(annotation.value())) {
continue;
}
Class<?> parameterType = toCheck.getType();
if (!type.isAssignableFrom(parameterType)) {
throw new UnsupportedOperationException("Parameter type being referenced cannot be assigned here!");
TypeWrapper parameterType = toCheck.getType();
if (!parameterType.isAssignableTo(type)) {
throw new HandlerException("Parameter type being referenced cannot be assigned here!");
} else {
return;
}
}
throw new UnsupportedOperationException("Parameter with name '" + annotation.value() + "' does not exist.");
throw new HandlerException("Parameter with name '" + annotation.value() + "' does not exist.");
}
@Override
@@ -79,7 +79,7 @@ public final class MinReferenceHandler {
public AbstractTypeMapper<Object, Number> getTypeMapper(Min.Reference minReference, Parameter parameter, int index, DataReadable dataReadable, AbstractTypeMapper<Object, Number> parentTypeMapper) {
Class<?> type = parameter.getType();
if (type.isArray()) type = type.getComponentType();
Class<?> finalType = type;
Handler.TypeWrapper finalType = new TypeWrapper.ForClass(type);
String name = minReference.value();
int compareValue = minReference.inclusive() ? 0 : 1;
@@ -119,7 +119,7 @@ public final class MinReferenceHandler {
public AbstractTypeValidator<Object, Number> getValidator(Min.Reference minReference, Parameter parameter, int index, DataReadable dataReadable) {
Class<?> type = parameter.getType();
if (type.isArray()) type = type.getComponentType();
Class<?> finalType = type;
Handler.TypeWrapper finalType = new TypeWrapper.ForClass(type);
String name = minReference.value();
int compareValue = minReference.inclusive() ? 0 : 1;
@@ -35,12 +35,12 @@ public final class OptionalValueHandler {
public static final class Impl implements Handler.HandlerParameter<OptionalValue, Object, Object> {
@Override
public void check(OptionalValue annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(OptionalValue annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("OptionalValue annotation cannot be used on the first parameter");
throw new HandlerException("OptionalValue annotation cannot be used on the first parameter");
}
if (parameter.isVarArgs() || parameter.getType().isArray()) {
throw new UnsupportedOperationException("OptionalValue annotation cannot be used on varargs or array parameters");
throw new HandlerException("OptionalValue annotation cannot be used on varargs or array parameters");
}
}
@@ -31,12 +31,12 @@ public final class RegexHandler {
public static final class Impl implements Handler.HandlerParameter<Regex, Object, String> {
@Override
public void check(Regex annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Regex annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Regex annotation cannot be used on first parameter");
throw new HandlerException("Regex annotation cannot be used on first parameter");
}
if (parameter.getType() != String.class) {
throw new UnsupportedOperationException("Regex annotation cannot be used on String parameter");
if (!parameter.getType().is(String.class)) {
throw new HandlerException("Regex annotation cannot be used on String parameter");
}
}
@@ -34,52 +34,55 @@ public final class RegisterHandler {
public static final class Impl implements Handler.HandlerMethod<Register> {
@Override
public void check(de.steamwar.command.annotations.Register annotation, Method method, DataCheckable dataCheckable) throws Exception {
public void check(Register annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
if (method.getParameterCount() == 0) {
throw new UnsupportedOperationException("Register method must have at least one parameter");
throw new HandlerException("Register method must have at least one parameter");
}
if (method.getReturnType() != void.class) {
throw new UnsupportedOperationException("Register method must return void");
if (!method.getReturnType().is(void.class)) {
throw new HandlerException("Register method must return void");
}
if (!dataCheckable.hasExecutorMapper(method.getParameterTypes()[0])) {
throw new UnsupportedOperationException("Register method first parameter must have a executor mapper registered");
// TODO: Implement hasExecutorMapper!
if (method instanceof MethodWrapper.ForMethod) {
if (!dataCheckable.hasExecutorMapper(((MethodWrapper.ForMethod) method).getMethod().getParameterTypes()[0])) {
throw new HandlerException("Register method first parameter must have a executor mapper registered");
}
}
Parameter[] parameters = method.getParameters();
ParameterWrapper[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
List<Annotation> annotationList = CommandUtils.getAnnotations(parameter);
ParameterWrapper parameter = parameters[i];
List<Annotation> annotationList = parameter.getAnnotations();
if (annotationList.isEmpty()) {
if (i == 0) {
continue;
}
Class<?> type = parameter.getType();
TypeWrapper type = parameter.getType();
if (type.isArray()) {
type = type.getComponentType();
}
if (!type.isEnum() && !dataCheckable.hasMapper(type.getName())) {
throw new UnsupportedOperationException("Register method parameter " + parameter.getName() + " must be annotated or have a type mapper");
throw new HandlerException("Register method parameter " + parameter.getName() + " must be annotated or have a type mapper");
}
} else {
for (Annotation annotation1 : annotationList) {
getParameterHandler(annotation1).check(annotation1, parameter, i, dataCheckable);
getParameterHandler(annotation1).check(annotation1, method, parameter, i, dataCheckable);
}
}
}
}
public static HandlerParameter getParameterHandler(Annotation annotation) {
public static HandlerParameter getParameterHandler(Annotation annotation) throws HandlerException {
Implementation handler = annotation.annotationType().getAnnotation(Implementation.class);
Handler handlerObject;
try {
handlerObject = handler.value().getConstructor().newInstance();
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
throw new UnsupportedOperationException("Handler " + handler.value().getName() + " cannot be used to check the argument validity", e);
throw new HandlerException("Handler " + handler.value().getName() + " cannot be used to check the argument validity");
}
if (!(handlerObject instanceof HandlerParameter)) {
throw new UnsupportedOperationException("Handler " + handlerObject.getClass().getName() + " is not a HandlerParameter");
throw new HandlerException("Handler " + handlerObject.getClass().getName() + " is not a HandlerParameter");
}
return (HandlerParameter) handlerObject;
}
@@ -35,12 +35,12 @@ public final class StartsWithHandler {
public static final class Impl implements Handler.HandlerParameter<StartsWith, Object, String> {
@Override
public void check(StartsWith annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(StartsWith annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("StartsWith annotation cannot be used on first parameter");
throw new HandlerException("StartsWith annotation cannot be used on first parameter");
}
if (parameter.getType() != String.class) {
throw new UnsupportedOperationException("StartsWith annotation cannot be used on String parameter");
if (!parameter.getType().is(String.class)) {
throw new HandlerException("StartsWith annotation cannot be used on String parameter");
}
}
@@ -34,16 +34,16 @@ public final class StaticValueHandler {
public static final class Impl implements Handler.HandlerParameter<StaticValue, Object, Object> {
@Override
public void check(StaticValue annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(StaticValue annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("StaticValue annotation cannot be used on first parameter");
throw new HandlerException("StaticValue annotation cannot be used on first parameter");
}
Class<?> type = parameter.getType();
if (type == String.class) return;
if (type == int.class || type == Integer.class) return;
if (type == long.class || type == Long.class) return;
if (type == boolean.class || type == Boolean.class) return;
throw new UnsupportedOperationException("StaticValue parameter must be String, int, long or boolean");
TypeWrapper type = parameter.getType();
if (type.is(String.class)) return;
if (type.is(int.class) || type.is(Integer.class)) return;
if (type.is(long.class) || type.is(Long.class)) return;
if (type.is(boolean.class) || type.is(Boolean.class)) return;
throw new HandlerException("StaticValue parameter must be String, int, long or boolean");
}
@Override
@@ -19,7 +19,6 @@
package de.steamwar.command.handler;
import de.steamwar.command.AbstractTypeMapper;
import de.steamwar.command.AbstractTypeSupplier;
import de.steamwar.command.Handler;
import de.steamwar.command.annotations.AllowNull;
@@ -36,26 +35,29 @@ public final class SupplierHandler {
public static final class Impl implements Handler.HandlerMethod<Supplier>, Handler.HandlerParameter<Supplier, Object, Object> {
@Override
public void check(Supplier annotation, Method method, DataCheckable dataCheckable) throws Exception {
public void check(Supplier annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
if (method.getParameterCount() != 0) {
throw new UnsupportedOperationException("Supplier method must have no parameters");
throw new HandlerException("Supplier method must have no parameters");
}
if (!AbstractTypeSupplier.class.isAssignableFrom(method.getReturnType())) {
throw new UnsupportedOperationException("Supplier method must return AbstractTypeSupplier");
if (!method.getReturnType().isAssignableTo(AbstractTypeSupplier.class)) {
throw new HandlerException("Supplier method must return AbstractTypeSupplier");
}
if (annotation.value() != null && !annotation.value().isEmpty()) {
return;
}
Optional<Class<?>> genericType = getGenericTypeOfReturn(method);
if (annotation.type() == void.class) {
if (genericType.isEmpty()) {
throw new UnsupportedOperationException("Please supply a class type to the Supplier");
}
} else {
if (genericType.isPresent() && !annotation.type().isAssignableFrom(genericType.get())) {
throw new UnsupportedOperationException("Supplied type does not conform to actual type");
// TODO: Implement! for AnnotationProcessor as well!
if (method instanceof MethodWrapper.ForMethod) {
Optional<Class<?>> genericType = getGenericTypeOfReturn(((MethodWrapper.ForMethod) method).getMethod());
if (annotation.type() == void.class) {
if (genericType.isEmpty()) {
throw new UnsupportedOperationException("Please supply a class type to the Supplier");
}
} else {
if (genericType.isPresent() && !annotation.type().isAssignableFrom(genericType.get())) {
throw new UnsupportedOperationException("Supplied type does not conform to actual type");
}
}
}
}
@@ -40,9 +40,9 @@ public final class TabFilterHandler {
public static final class Impl implements Handler.HandlerParameter<TabFilter, Object, Object> {
@Override
public void check(TabFilter annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(TabFilter annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("TabFilter annotation cannot be used on first parameter");
throw new HandlerException("TabFilter annotation cannot be used on first parameter");
}
}
}
@@ -36,12 +36,12 @@ public final class UniqueHandler {
public static final class Impl implements Handler.HandlerParameter<Unique, Object, Object> {
@Override
public void check(Unique annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Unique annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Unique annotation cannot be used on the first parameter");
throw new HandlerException("Unique annotation cannot be used on the first parameter");
}
if (!parameter.getType().isArray() && !parameter.isVarArgs()) {
throw new UnsupportedOperationException("Unique annotation cannot be used on non array types");
throw new HandlerException("Unique annotation cannot be used on non array types");
}
}
}
@@ -32,26 +32,29 @@ public final class ValidatorHandler {
public static final class Impl implements Handler.HandlerMethod<Validator>, Handler.HandlerParameter<Validator, Object, Object> {
@Override
public void check(Validator annotation, Method method, DataCheckable dataCheckable) throws Exception {
public void check(Validator annotation, MethodWrapper method, DataCheckable dataCheckable) throws HandlerException {
if (method.getParameterCount() != 0) {
throw new UnsupportedOperationException("Validator method must have no parameters");
throw new HandlerException("Validator method must have no parameters");
}
if (!AbstractTypeValidator.class.isAssignableFrom(method.getReturnType())) {
throw new UnsupportedOperationException("Validator method must return AbstractValidator");
if (!method.getReturnType().isAssignableTo(AbstractTypeValidator.class)) {
throw new HandlerException("Validator method must return AbstractValidator");
}
if (annotation.value() != null && !annotation.value().isEmpty()) {
return;
}
Optional<Class<?>> genericType = getGenericTypeOfReturn(method);
if (annotation.type() == void.class) {
if (genericType.isEmpty()) {
throw new UnsupportedOperationException("Please supply a class type to the Validator");
}
} else {
if (genericType.isPresent() && !annotation.type().isAssignableFrom(genericType.get())) {
throw new UnsupportedOperationException("Supplied type does not conform to actual type");
// TODO: Implement! for AnnotationProcessor as well!
if (method instanceof MethodWrapper.ForMethod) {
Optional<Class<?>> genericType = getGenericTypeOfReturn(((MethodWrapper.ForMethod) method).getMethod());
if (annotation.type() == void.class) {
if (genericType.isEmpty()) {
throw new HandlerException("Please supply a class type to the Validator");
}
} else {
if (genericType.isPresent() && !annotation.type().isAssignableFrom(genericType.get())) {
throw new HandlerException("Supplied type does not conform to actual type");
}
}
}
}
@@ -33,16 +33,16 @@ public final class ValuesHandler {
public static final class Impl implements Handler.HandlerParameter<Values, Object, Object> {
@Override
public void check(Values annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Values annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Values annotation cannot be used on first parameter");
throw new HandlerException("Values annotation cannot be used on first parameter");
}
Class<?> type = parameter.getType();
if (type == String.class) return;
if (type == int.class || type == Integer.class) return;
if (type == long.class || type == Long.class) return;
if (type == boolean.class || type == Boolean.class) return;
throw new UnsupportedOperationException("Values parameter must be String, int, long or boolean");
TypeWrapper type = parameter.getType();
if (type.is(String.class)) return;
if (type.is(int.class) || type.is(Integer.class)) return;
if (type.is(long.class) || type.is(Long.class)) return;
if (type.is(boolean.class) || type.is(Boolean.class)) return;
throw new HandlerException("Values parameter must be String, int, long or boolean");
}
@Override
@@ -25,7 +25,6 @@ import de.steamwar.command.PreviousArguments;
import de.steamwar.command.annotations.Name;
import de.steamwar.command.annotations.Values;
import de.steamwar.command.mapper.internal.DelegatingMapper;
import de.steamwar.command.utils.ClassEquality;
import java.lang.reflect.Parameter;
import java.util.Arrays;
@@ -37,20 +36,20 @@ public class ValuesReferenceHandler {
public static final class Impl implements Handler.HandlerParameter<Values.Reference, Object, Object> {
@Override
public void check(Values.Reference annotation, Parameter parameter, int index, DataCheckable dataCheckable) throws Exception {
public void check(Values.Reference annotation, MethodWrapper methodWrapper, ParameterWrapper parameter, int index, DataCheckable dataCheckable) throws HandlerException {
if (index == 0) {
throw new UnsupportedOperationException("Values.Reference annotation cannot be used on first parameter");
throw new HandlerException("Values.Reference annotation cannot be used on first parameter");
}
Class<?> type = parameter.getType();
TypeWrapper type = parameter.getType();
if (type.isArray()) type = type.getComponentType();
if (type != int.class && type != Integer.class && type != long.class && type != Long.class && type != String.class) {
throw new UnsupportedOperationException("Parameter " + index + " of " + parameter.getDeclaringExecutable() + " is not a String or Int or Long.");
if (!type.is(int.class) && !type.is(Integer.class) && !type.is(long.class) && !type.is(Long.class) && !type.is(String.class)) {
throw new HandlerException("Parameter " + index + " of " + methodWrapper.getName() + " is not a String or Int or Long.");
}
Parameter[] parameters = parameter.getDeclaringExecutable().getParameters();
ParameterWrapper[] parameters = methodWrapper.getParameters();
for (int i = index - 1; i >= 1; i--) {
Parameter toCheck = parameters[i];
ParameterWrapper toCheck = parameters[i];
Name name = toCheck.getAnnotation(Name.class);
String parameterName;
if (name == null) {
@@ -61,15 +60,15 @@ public class ValuesReferenceHandler {
if (!parameterName.equals(annotation.value())) {
continue;
}
Class<?> toCheckType = toCheck.getType();
TypeWrapper toCheckType = toCheck.getType();
if (toCheckType.isArray()) toCheckType = toCheckType.getComponentType();
if (!ClassEquality.classEquals(type, toCheckType)) {
throw new UnsupportedOperationException("Parameter type being referenced cannot be assigned here!");
if (!toCheckType.is(type)) {
throw new HandlerException("Parameter type being referenced cannot be assigned here!");
} else {
return;
}
}
throw new UnsupportedOperationException("Parameter with name '" + annotation.value() + "' does not exist.");
throw new HandlerException("Parameter with name '" + annotation.value() + "' does not exist.");
}
@Override