All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xyz.luan.console.parser.ControllerRef Maven / Gradle / Ivy

Go to download

Java library to easily parse console input directly into controller classes with elegance.

There is a newer version: 1.0.4
Show newest version
package xyz.luan.console.parser;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

import xyz.luan.console.parser.actions.Action;
import xyz.luan.console.parser.actions.Arg;
import xyz.luan.console.parser.actions.InvalidAction;
import xyz.luan.console.parser.actions.InvalidCall;
import xyz.luan.console.parser.actions.InvalidHandler;
import xyz.luan.console.parser.actions.InvalidParameter;
import xyz.luan.console.parser.actions.Optional;
import xyz.luan.console.parser.actions.parser.ArgumentParser;
import xyz.luan.console.parser.call.CallResult;
import xyz.luan.console.parser.util.ClassMap;

public class ControllerRef> {

	private String name;
    private T controller;
    private Map actions;
    private Map, Method> handlers;

    public ControllerRef(String name, T controller) throws InvalidAction, InvalidHandler {
    	this.name = name;
        this.controller = controller;
        this.actions = new HashMap<>();
        this.handlers = new HashMap<>();
        for (Method method : controller.getClass().getMethods()) {
            parseAction(method);
            parseHandler(method);
        }
    }

    public void forEachAction(Consumer consumer) {
        for (Map.Entry entry : actions.entrySet()) {
            consumer.accept(entry.getValue());
        }
    }

	private void parseHandler(Method method) throws InvalidHandler {
		ExceptionHandler handler = method.getAnnotation(ExceptionHandler.class);
		if (handler != null) {
			assertValidHandlerMethod(method, handler);
			for (Class c : handler.value()) {
				if (handlers.get(c) != null) {
					throw new InvalidHandler("Trying to register two handlers to the same exception type: " + c.getSimpleName());
				}
				handlers.put(c, method);
			}
		}
	}

	private void assertValidHandlerMethod(Method method, ExceptionHandler handler) throws InvalidHandler {
    	if (Modifier.isStatic(method.getModifiers())) {
    		throw new InvalidHandler(method, "should not be static");
    	}
		if (method.getParameters().length != 1) {
			throw new InvalidHandler(method, "must take exactly one parameter: the exception to be handle");
		}
		Class parameterType = method.getParameters()[0].getType();
		for (Class c : handler.value()) {
			if (!parameterType.isAssignableFrom(c)) {
				throw new InvalidHandler(method, String.format("the parameter must be a supertype common to all @ExceptionHandler exceptions; type '%s' can't be cast from '%s'.", c.getSimpleName(), parameterType.getSimpleName()));
			}	
		}
        if (!method.getReturnType().equals(CallResult.class)) {
            throw new InvalidHandler(method, "must always return Output object");
        }
	}

	private void parseAction(Method method) throws InvalidAction {
		Action action = method.getAnnotation(Action.class);
		if (action != null) {
		    assertValidateActionMethod(method);
		    if (this.actions.get(action.value()) != null) {
		        throw new InvalidAction("Two actions on the same controller same action name: " + action.value());
		    }
		    this.actions.put(action.value(), method);
		}
	}

    public CallResult call(String actionName, Map rawParams) {
        try {
            Method method = actions.get(actionName);
            if (method == null) {
                throw new InvalidCall(String.format("Action name '%s' not found in controller '%s'", actionName, controller.getClass().getCanonicalName()));
            }
            Object[] actualParamValues = getActualParameterValues(name + ":" + actionName, method, rawParams);
            method.setAccessible(true);
            return (CallResult) method.invoke(controller, actualParamValues);
        } catch (Throwable e) {
        	if (e instanceof InvocationTargetException) {
        		e = e.getCause();
        	}
        	Method handler = ClassMap.getFromClassMap(handlers, e.getClass());
        	if (handler == null) {
        		throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
        	}
        	try {
				return (CallResult) handler.invoke(controller, e);
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
				throw new RuntimeException("Exception handler threw an exception! Run for your lives!", e1);
			}
        }
    }

    private Object[] getActualParameterValues(String ref, Method method, Map rawParamValues) throws InvalidCall {
        Parameter[] paramData = method.getParameters();
        Object[] actualParamValues = new Object[paramData.length];
        assert paramData.length == actualParamValues.length;
        for (int i = 0; i < actualParamValues.length; i++) {
            boolean required;
            String name;

            Arg arg = paramData[i].getAnnotation(Arg.class);
            if (arg != null) {
                required = arg.required();
                name = arg.value();                
            } else {
                if (!paramData[i].isNamePresent()) {
                    throw new InvalidCall(ref, "Unless you use @Arg on every param, you must turn on -parameters on Java so we can access the names of your parameters.");
                }
                required = paramData[i].getAnnotation(Optional.class) == null;
                name = paramData[i].getName();
            }

            String value = rawParamValues.get(name);
            if (value == null) {
                if (required) {
                    throw new InvalidCall(ref, "The parameter " + name + " is required.");
                }
                actualParamValues[i] = null;
            } else {
                try {
                    actualParamValues[i] = ArgumentParser.parse(value, paramData[i].getType());
                } catch (InvalidParameter e) {
                    throw new InvalidCall(ref, e);
                }
            }
        }
        return actualParamValues;
    }
    
    private void assertValidateActionMethod(Method method) throws InvalidAction {
    	if (Modifier.isStatic(method.getModifiers())) {
    		throw new InvalidAction(method, "should not be static");
    	}

        for (Parameter param : method.getParameters()) {
            if (!ArgumentParser.hasParser(param.getType())) {
                String message = String.format("invalid parameter type '%s'; not registered on ArgumentParser", param.getType());
                throw new InvalidAction(method, message);
            }
        }

        if (!method.getReturnType().equals(CallResult.class)) {
            throw new InvalidAction(method, "must always return Output object");
        }
    }

    public String getName() {
        return this.name;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy