
/*
 *  Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
*/
package oracle.sample;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class ReflectionUtil {
    public ReflectionUtil() {
        super();
    }


    public static Object invoke(Object obj, String method) throws Throwable {
        return invoke(obj, method, new Object[]{null});
    }

    public static Object invoke(Object obj, String method,
                                Object nonNull) throws Throwable {
        return invoke(obj, method, new Object[]{nonNull});
    }

    public static Object invoke(Object obj, String method, Class cls,
                                Object arg) throws Throwable {
        return invoke(obj, method, new Class[]{cls}, new Object[]{arg});
    }

    public static Object invoke(Object obj, String method, Object[] nonNulls,
                                ClassLoader loader) throws Throwable {
        Class[] classes = getClasses(nonNulls);
        if (classes == null) {
            nonNulls = null;
        }
        return invoke(obj, method, classes, nonNulls, loader);
    }

    public static Object invoke(Object obj, String method,
                                Object[] nonNulls) throws Throwable {
        Class[] classes = getClasses(nonNulls);
        if (classes == null) {
            nonNulls = null;
        }
        return invoke(obj, method, classes, nonNulls);
    }

    public static Object invoke(Object obj, String methodName,
                                Class[] argTypes,
                                Object[] nonNulls) throws Throwable {
        return invoke(obj, methodName, argTypes, nonNulls, null);
    }

    public static Object invoke(Object obj, String methodName,
                                Class[] argTypes, Object[] nonNulls,
                                ClassLoader loader) throws Throwable {
        if (obj == null) {
            throw new Exception("Object cant be null;");
        }
        ClassLoader original = Thread.currentThread().getContextClassLoader();
        if (loader == null)
            loader = original;

        try {
            Thread.currentThread().setContextClassLoader(loader);

            Class cls = obj.getClass();
            Method method = cls.getMethod(methodName, argTypes);
            return method.invoke(obj, nonNulls);
        } catch (InvocationTargetException it) {
            throw it.getCause();
        } catch (Throwable t) {
            throw new Exception("Invocation failed:", t);
        } finally {
            Thread.currentThread().setContextClassLoader(original);
        }
    }

    public static Object invokeStatic(String clsStr, String method,
                                      Object[] nonNulls) throws Throwable {
        Class[] classes = getClasses(nonNulls);
        if (classes == null) {
            nonNulls = null;
        }
        return invokeStatic(clsStr, method, classes, nonNulls);
    }

    public static Object invokeStatic(String clsStr, String method,
                                      Object[] nonNulls, ClassLoader loader) throws Throwable {
        Class[] classes = getClasses(nonNulls);
        if (classes == null) {
            nonNulls = null;
        }
        return invokeStatic(clsStr, method, classes, nonNulls, loader);
    }

    public static Class[] getClasses(Object[] objs) throws Exception {
        if (objs == null)
            return null;
        Class[] classes = new Class[objs.length];
        int i = 0;
        for (Object obj : objs) {
            if (obj == null) {
                if (objs.length == 1) {
                    classes = null; // setter with no arg
                } else {
                    classes[i] = Object.class;
                }
            } else {
                classes[i] = obj.getClass();
            }
            i++;
        }
        return classes;
    }

    public static Object invokeStatic(String clsStr, String methodName,
                                      Class[] argTypes,
                                      Object[] nonNulls) throws Throwable {
        return invokeStatic(clsStr, methodName, argTypes, nonNulls, null);
    }

    public static Object invokeStatic(String clsStr, String methodName,
                                      Class[] argTypes, Object[] nonNulls,
                                      ClassLoader loader) throws Throwable {
        ClassLoader original = Thread.currentThread().getContextClassLoader();
        if (loader == null)
            loader = original;
        Class cls =
                loader != null ? loader.loadClass(clsStr) : Class.forName(clsStr);
        Method method = cls.getMethod(methodName, argTypes);
        if (!Modifier.isStatic(method.getModifiers())) {
            throw new Exception("Not a static method ..." + methodName +
                    " of " + clsStr);
        }


        try {
            Thread.currentThread().setContextClassLoader(loader);

            return method.invoke(null, nonNulls);
        } catch (InvocationTargetException it) {
            throw it.getCause();
        } catch (Throwable t) {
            throw t;
        } finally {
            Thread.currentThread().setContextClassLoader(original);
        }

    }

    public static Object instantiate(String clsStr) throws Throwable {
        return instantiate(clsStr, (ClassLoader) null);
    }


    public static Object instantiate(String clsStr,
                                     Object[] nonNulls) throws Throwable {
        Class[] classes = getClasses(nonNulls);
        if (classes == null) {
            nonNulls = null;
        }
        return instantiate(clsStr, classes, nonNulls);
    }

    public static Object instantiate(String clsStr, Class[] classes,
                                     Object[] values) throws Throwable {
        return instantiate(clsStr, classes, values, null);
    }


    public static Object instantiate(String clsStr, Class[] classes,

                                     Object[] values, ClassLoader loader) throws Throwable {
        ClassLoader original = Thread.currentThread().getContextClassLoader();
        if (loader == null)
            loader = original;
        Class cls = loader.loadClass(clsStr);
        Constructor con = cls.getConstructor(classes);

        try {
            Thread.currentThread().setContextClassLoader(loader);

            return con.newInstance(values);
        } catch (InvocationTargetException it) {
            throw it.getCause();
        } catch (Throwable t) {
            throw t;
        } finally {
            Thread.currentThread().setContextClassLoader(original);
        }
    }

    public static Object instantiate(String clsStr,
                                     ClassLoader loader) throws Throwable {

        ClassLoader original = Thread.currentThread().getContextClassLoader();
        if (loader == null)
            loader = original;
        Class cls = loader.loadClass(clsStr);
        Constructor con = cls.getConstructor();

        try {
            Thread.currentThread().setContextClassLoader(loader);

            return con.newInstance();
        } catch (InvocationTargetException it) {
            throw it.getCause();
        } catch (Throwable t) {
            throw t;
        } finally {
            Thread.currentThread().setContextClassLoader(original);
        }
    }
}
