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
@@ -0,0 +1,32 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 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/>.
*/
plugins {
steamwar.java
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
dependencies {
implementation(project(":CommandFramework:CommandFrameworkBase", "default"))
annotationProcessor(project(":CommandFramework:CommandFrameworkBase", "default"))
}
@@ -0,0 +1,5 @@
de.steamwar.command.annotationprocessor.impl.CachedAnnotationProcessor
de.steamwar.command.annotationprocessor.impl.MapperAnnotationProcessor
de.steamwar.command.annotationprocessor.impl.RegisterAnnotationProcessor
de.steamwar.command.annotationprocessor.impl.SupplierAnnotationProcessor
de.steamwar.command.annotationprocessor.impl.ValidatorAnnotationProcessor
@@ -0,0 +1,65 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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 javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.List;
public class HandlerForExecutableElement implements Handler.MethodWrapper {
private final ExecutableElement method;
private final Types types;
private final Elements elements;
public HandlerForExecutableElement(ExecutableElement method, Types types, Elements elements) {
this.method = method;
this.types = types;
this.elements = elements;
}
@Override
public String getName() {
return method.getSimpleName().toString();
}
@Override
public int getParameterCount() {
return method.getParameters().size();
}
@Override
public Handler.TypeWrapper getReturnType() {
return new HandlerForTypeMirror(method.getReturnType(), types, elements);
}
@Override
public Handler.ParameterWrapper[] getParameters() {
Handler.ParameterWrapper[] parameters = new Handler.ParameterWrapper[method.getParameters().size()];
List<? extends VariableElement> variableElements = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
VariableElement variableElement = variableElements.get(i);
parameters[i] = new HandlerForVariableElement(variableElement, types, elements, i == parameters.length - 1 && method.isVarArgs());
}
return parameters;
}
}
@@ -0,0 +1,107 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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 javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
public class HandlerForTypeMirror implements Handler.TypeWrapper {
private final TypeMirror type;
private final Types types;
private final Elements elements;
public HandlerForTypeMirror(TypeMirror type, Types types, Elements elements) {
this.type = type;
this.types = types;
this.elements = elements;
}
@Override
public boolean isAssignableTo(Class<?> clazz) {
return types.isAssignable(types.erasure(type), types.erasure(elements.getTypeElement(clazz.getTypeName()).asType()));
}
@Override
public boolean isAssignableTo(Handler.TypeWrapper type) {
if (type instanceof ForClass) {
return isAssignableTo(((ForClass) type).getClazz());
} else if (type instanceof HandlerForTypeMirror) {
return types.isAssignable(types.erasure(this.type), types.erasure(((HandlerForTypeMirror) type).type));
}
return false;
}
@Override
public boolean isPrimitive() {
return type.getKind().isPrimitive();
}
@Override
public boolean isArray() {
return type instanceof ArrayType;
}
@Override
public boolean isEnum() {
System.out.println(type);
return false;
}
@Override
public boolean is(Class<?> clazz) {
if (type.getKind() == TypeKind.VOID && clazz == Void.TYPE) {
return true;
} else if (type.getKind().isPrimitive() && clazz.isPrimitive()) {
return type.toString().equals(clazz.getTypeName());
} else if (!type.getKind().isPrimitive() && !clazz.isPrimitive()) {
return types.isSameType(types.erasure(type), types.erasure(elements.getTypeElement(clazz.getTypeName()).asType()));
} else {
return false;
}
}
@Override
public boolean is(Handler.TypeWrapper type) {
if (type instanceof ForClass) {
return is(((ForClass) type).getClazz());
} else if (type instanceof HandlerForTypeMirror) {
return types.isSameType(types.erasure(this.type), types.erasure(((HandlerForTypeMirror) type).type));
}
return false;
}
@Override
public Handler.TypeWrapper getComponentType() {
if (type instanceof ArrayType) {
return new HandlerForTypeMirror(((ArrayType) type).getComponentType(), types, elements);
} else {
return new HandlerForTypeMirror(null, types, elements);
}
}
@Override
public String getName() {
return type.toString();
}
}
@@ -0,0 +1,77 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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 de.steamwar.command.handler.RegisterHandler;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;
public class HandlerForVariableElement implements Handler.ParameterWrapper {
private final VariableElement parameter;
private final Types types;
private final Elements elements;
private final boolean varArgs;
public HandlerForVariableElement(VariableElement variableElement, Types types, Elements elements, boolean varArgs) {
this.parameter = variableElement;
this.types = types;
this.elements = elements;
this.varArgs = varArgs;
}
@Override
public Handler.TypeWrapper getType() {
return new HandlerForTypeMirror(parameter.asType(), types, elements);
}
@Override
public boolean isVarArgs() {
return varArgs;
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
return parameter.getAnnotation(annotation) != null;
}
@Override
public <A extends Annotation> A getAnnotation(Class<A> annotation) {
return parameter.getAnnotation(annotation);
}
@Override
public String getName() {
return parameter.getSimpleName().toString();
}
@Override
public List<Annotation> getAnnotations() {
parameter.getAnnotationMirrors().forEach(annotationMirror -> {
System.out.println(annotationMirror);
});
return Collections.emptyList();
}
}
@@ -0,0 +1,176 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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.annotationprocessor;
import de.steamwar.command.Handler;
import de.steamwar.command.HandlerForExecutableElement;
import de.steamwar.command.HandlerForVariableElement;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public abstract class AbstractAnnotationProcessor extends AbstractProcessor {
private Messager messager;
private Types types;
private Elements elements;
public abstract Class<? extends Annotation> getAnnotationClass();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.types = processingEnv.getTypeUtils();
this.elements = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(this.getAnnotationClass().getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
List<ExecutableElement> elements = roundEnv.getElementsAnnotatedWith(getAnnotationClass())
.stream()
.filter(element -> element.getKind() == ElementKind.METHOD)
.map(ExecutableElement.class::cast)
.collect(Collectors.toList());
if (elements.isEmpty()) return false;
elements.forEach(element -> {
checkMethodAnnotation(element, element.getAnnotation(getAnnotationClass()));
List<? extends VariableElement> parameters = element.getParameters();
for (int index = 0; index < parameters.size(); index++) {
VariableElement parameter = parameters.get(index);
for (AnnotationMirror annotationMirror : parameter.getAnnotationMirrors()) {
Class<?> clazz = getClass(annotationMirror.getAnnotationType().toString());
if (clazz == null) return;
checkParameterAnnotation(element, parameter, index, parameter.getAnnotation((Class<? extends Annotation>) clazz));
}
}
});
return false;
}
private Class<?> getClass(String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException e) {
// Ignore
}
if (!name.contains(".")) return null;
Class<?> outerClass = getClass(name.substring(0, name.lastIndexOf('.')));
if (outerClass == null) return null;
name = name.substring(name.lastIndexOf('.') + 1);
for (Class<?> declaredClass : outerClass.getDeclaredClasses()) {
if (declaredClass.getSimpleName().equals(name)) {
return declaredClass;
}
}
return null;
}
private Handler getHandler(Annotation annotation, Element element) {
Handler.Implementation implementation = annotation.annotationType().getAnnotation(Handler.Implementation.class);
if (implementation == null) return null;
try {
return implementation.value().getConstructor().newInstance();
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "Handler " + implementation.value().getName() + " cannot be used to check the argument validity", element);
return null;
}
}
private void checkMethodAnnotation(ExecutableElement method, Annotation annotation) {
Handler handler = getHandler(annotation, method);
if (handler == null) return;
if (!(handler instanceof Handler.HandlerMethod)) {
messager.printMessage(Diagnostic.Kind.ERROR, "Handler " + handler.getClass().getName() + " is not a HandlerMethod", method);
return;
}
Handler.HandlerMethod handlerMethod = (Handler.HandlerMethod) handler;
try {
handlerMethod.check(annotation, new HandlerForExecutableElement(method, types, elements), DataCheckableImpl.INSTANCE);
} catch (Handler.HandlerException e) {
messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), method);
}
}
private void checkParameterAnnotation(ExecutableElement method, VariableElement parameter, int index, Annotation annotation) {
Handler handler = getHandler(annotation, parameter);
if (handler == null) return;
if (!(handler instanceof Handler.HandlerParameter)) {
messager.printMessage(Diagnostic.Kind.ERROR, "Handler " + handler.getClass().getName() + " is not a HandlerParameter", method);
return;
}
Handler.HandlerParameter handlerParameter = (Handler.HandlerParameter) handler;
try {
handlerParameter.check(annotation, new HandlerForExecutableElement(method, types, elements), new HandlerForVariableElement(parameter, types, elements, index == method.getParameters().size() - 1 && method.isVarArgs()), index, DataCheckableImpl.INSTANCE);
} catch (Handler.HandlerException e) {
messager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), parameter);
}
}
private static final class DataCheckableImpl implements Handler.DataCheckable {
private static final DataCheckableImpl INSTANCE = new DataCheckableImpl();
@Override
public boolean hasExecutorMapper(Class<?> clazz) {
return true;
}
@Override
public boolean hasMapper(String key) {
return true;
}
@Override
public boolean hasValidator(String key) {
return true;
}
@Override
public boolean hasSupplier(String key) {
return true;
}
}
}
@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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.annotationprocessor.impl;
import de.steamwar.command.annotationprocessor.AbstractAnnotationProcessor;
import de.steamwar.command.annotations.Cached;
import java.lang.annotation.Annotation;
public class CachedAnnotationProcessor extends AbstractAnnotationProcessor {
@Override
public Class<? extends Annotation> getAnnotationClass() {
return Cached.class;
}
}
@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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.annotationprocessor.impl;
import de.steamwar.command.annotationprocessor.AbstractAnnotationProcessor;
import de.steamwar.command.annotations.Mapper;
import java.lang.annotation.Annotation;
public class MapperAnnotationProcessor extends AbstractAnnotationProcessor {
@Override
public Class<? extends Annotation> getAnnotationClass() {
return Mapper.class;
}
}
@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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.annotationprocessor.impl;
import de.steamwar.command.annotationprocessor.AbstractAnnotationProcessor;
import de.steamwar.command.annotations.Register;
import java.lang.annotation.Annotation;
public class RegisterAnnotationProcessor extends AbstractAnnotationProcessor {
@Override
public Class<? extends Annotation> getAnnotationClass() {
return Register.class;
}
}
@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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.annotationprocessor.impl;
import de.steamwar.command.annotationprocessor.AbstractAnnotationProcessor;
import de.steamwar.command.annotations.Supplier;
import java.lang.annotation.Annotation;
public class SupplierAnnotationProcessor extends AbstractAnnotationProcessor {
@Override
public Class<? extends Annotation> getAnnotationClass() {
return Supplier.class;
}
}
@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 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.annotationprocessor.impl;
import de.steamwar.command.annotationprocessor.AbstractAnnotationProcessor;
import de.steamwar.command.annotations.Validator;
import java.lang.annotation.Annotation;
public class ValidatorAnnotationProcessor extends AbstractAnnotationProcessor {
@Override
public Class<? extends Annotation> getAnnotationClass() {
return Validator.class;
}
}
@@ -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
@@ -31,4 +31,6 @@ dependencies {
testImplementation(libs.hamcrest)
testImplementation(project(":CommandFramework:CommandFrameworkBase", "default"))
testAnnotationProcessor(project(":CommandFramework:CommandFrameworkAnnotationProcessor", "default"))
}
+1
View File
@@ -190,6 +190,7 @@ include(
include(
"CommandFramework",
"CommandFramework:CommandFrameworkBase",
"CommandFramework:CommandFrameworkAnnotationProcessor",
"CommandFramework:CommandFrameworkSpigot",
"CommandFramework:CommandFrameworkTests",
"CommandFramework:CommandFrameworkVelocity",