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

ru.yandex.qatools.camelot.common.AnnotatedMethodDispatcher Maven / Gradle / Ivy

There is a newer version: 2.5.4
Show newest version
package ru.yandex.qatools.camelot.common;

import ru.yandex.qatools.camelot.error.DispatchException;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import static java.lang.String.format;
import static java.util.Arrays.asList;

/**
 * @author Ilya Sadykov (mailto: [email protected])
 */
public class AnnotatedMethodDispatcher {

    private final Object instance;
    private final MetadataClassInfo cache;

    public AnnotatedMethodDispatcher(Object instance, MetadataClassInfo meta) {
        this.cache = meta;
        this.instance = instance;
    }

    public Map dispatch(Class annClass, boolean singleCall, Object... params) throws Exception { //NOSONAR
        final List paramTypes = new ArrayList<>();
        final List paramList = new ArrayList<>();
        for (Object value : params) {
            paramTypes.add(value.getClass());
            paramList.add(value);
        }
        if (paramTypes.isEmpty()) {
            throw new DispatchException(format("Failed to invoke methods annotated with @%s: parameters are empty!", annClass));
        }
        final Map called = new HashMap<>();
        for (int i = 0; i <= paramTypes.size(); ++i) {
            for (int j = paramTypes.size(); j >= i; --j) {
                final List typesSubList = paramTypes.subList(i, j);
                final Deque typesStack = new ArrayDeque<>();
                typesStack.push(typesSubList.toArray(new Class[typesSubList.size()]));
                call(annClass, typesStack, paramList.subList(i, j), singleCall, called, 0);
                if (singleCall && !called.isEmpty()) {
                    return called;
                }
            }
        }
        return called;
    }


    protected void call(Class annClass, Deque typesStack, List params,
                        boolean singleCall, Map called, int paramIdx) throws Exception { //NOSONAR
        try {
            if (singleCall && !called.isEmpty()) {
                return;
            }
            List paramTypes = new ArrayList<>();
            paramTypes.addAll(asList(typesStack.peek()));
            if (paramIdx >= paramTypes.size() || paramTypes.get(paramIdx) == Object.class) {
                return;
            }
            for (Class paramType : cache.getSuperClasses(paramTypes.get(paramIdx))) {
                paramTypes.set(paramIdx, paramType);

                findSuitableMethodAndCall(annClass, paramTypes, params, singleCall, called);
                typesStack.push(paramTypes.toArray(new Class[paramTypes.size()]));
                call(annClass, typesStack, params, singleCall, called, paramIdx + 1);
                typesStack.pop();
                if (singleCall && !called.isEmpty()) {
                    return;
                }
            }
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw wrapThrowableIfRequired(e, e.getCause());
        } catch (InvocationTargetException e) {
            throw wrapThrowableIfRequired(e, e.getTargetException());
        }
    }

    private Exception wrapThrowableIfRequired(Throwable e, Throwable cause) throws Exception { //NOSONAR
        if (cause == null) {
            return wrapThrowableIfRequired(e, null);
        }
        return (cause instanceof Exception) ? (Exception) cause : new Exception(cause);
    }

    @SuppressWarnings("unchecked")
    private void findSuitableMethodAndCall(Class annClass, List paramTypes, List params,
                                           boolean singleCall, Map called) throws Exception { //NOSONAR
        final Class[] mParamTypes = paramTypes.toArray(new Class[paramTypes.size()]);
        final Collection methods = cache.getMethodsByParamTypes(annClass, mParamTypes);
        for (Method method : methods) {
            if (checkMethodParams(method.getParameterTypes(), paramTypes)) {
                callMethod(method, paramTypes, params, called);
                if (singleCall && !called.isEmpty()) {
                    return;
                }
            }
        }
    }

    private void callMethod(Method method, List types, List params, Map called) throws IllegalAccessException, InvocationTargetException {
        if (types.size() == method.getParameterTypes().length && !called.containsKey(method)) {
            called.put(
                    method,
                    method.invoke(instance, params.toArray(new Object[types.size()]))
            );
        }
    }

    private boolean checkMethodParams(Class[] paramTypes, List types) {
        if (types.size() > paramTypes.length) {
            return false;
        }
        for (int i = 0; i < types.size(); ++i) {
            if (!paramTypes[i].equals(types.get(i))) {
                return false;
            }
        }
        return true;
    }
}