/*******************************************************************************
 * Copyright (c) 2015, 2016 Oracle and/or its affiliates.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     05/14/2015-2.7 Tomas Kraus
 *       - Initial API and implementation.
 ******************************************************************************/
package org.eclipse.persistence.testing.framework;

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

import sun.misc.Unsafe;

/**
 * Reflection helper methods.
 */
public class ReflectionHelper {

    private static final Unsafe unsafe;
    static
    {
        try
        {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe)field.get(null);
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }

    /**
     * Set value of private static field.
     * @param c        Class containing static field.
     * @param name     Static field name to be modified.
     * @param newValue New value to be set.
     * @throws NoSuchFieldException If a field with the specified name is not found.
     * @throws SecurityException If a security manager is present and access to the field was denied.
     * @throws IllegalArgumentException If an unwrapping conversion fails.
     * @throws IllegalAccessException If the underlying field is either inaccessible or final.
     */
    public static final void setPrivateStatic(final Class c, final String name, final Object newValue)
            throws ReflectiveOperationException {
        final Field field = c.getDeclaredField(name);
        final boolean accessible = field.isAccessible();
        field.setAccessible(true);
        field.set(null, newValue);
        field.setAccessible(accessible);
     }

    /**
     * Set value of private static final field.
     * Uses {@link sun.misc.Unsafe} which seems to be the only way to modify such a field in some cases.
     * @param c        Class containing static field.
     * @param name     Static field name to be modified.
     * @param newValue New value to be set.
     * @throws NoSuchFieldException If a field with the specified name is not found.
     * @throws SecurityException If a security manager is present and access to the field was denied.
     * @throws IllegalArgumentException If an unwrapping conversion fails.
     * @throws IllegalAccessException If the underlying field is either inaccessible or final.
     */
    public static final void setPrivateStaticFinal(final Class c, final String name, final Object newValue)
            throws ReflectiveOperationException {
        final Field field = c.getDeclaredField(name);
        final Object base = unsafe.staticFieldBase(field);
        final long offset = unsafe.staticFieldOffset(field);
        unsafe.putObject(base, offset, newValue);
     }

    /**
     * Get value of private static field.
     * @param c    Class containing static field.
     * @param name Static field name to be retrieved.
     * @return Static field value.
     * @throws NoSuchFieldException If a field with the specified name is not found.
     * @throws SecurityException If a security manager is present and access to the field was denied.
     * @throws IllegalArgumentException If an unwrapping conversion fails.
     * @throws IllegalAccessException If the underlying field is either inaccessible or final.
     */
    public static final <T> T getPrivateStatic(final Class c, final String name)
            throws ReflectiveOperationException {
        final Field field = c.getDeclaredField(name);
        final boolean accessible = field.isAccessible();
        field.setAccessible(true);
        final Object value = field.get(null);
        field.setAccessible(accessible);
        return (T)value;
     }

    /**
     * Create a new instance of the specified class.
     * @param c Class containing constructor.
     * @param parameterTypes Constructor parameter array.
     * @param args An array of objects to be passed as arguments to the constructor call
     * @throws InvocationTargetException If the underlying constructor throws an exception.
     * @throws IllegalArgumentException  If an unwrapping conversion fails.
     * @throws IllegalAccessException    If the underlying field is either inaccessible or final.
     * @throws InstantiationException    If the class that declares the underlying constructor represents
     *                                   an abstract class.
     * @throws SecurityException         If a security manager is present and access to the field was denied.
     * @throws NoSuchMethodException     If a field with the specified name is not found.
     */
    public static final <T> T getInstance(final Class<T> c, final Class<?>[] parameterTypes, final Object... args)
            throws ReflectiveOperationException {
        Constructor<T> co = c.getDeclaredConstructor(parameterTypes);
        return co.newInstance(args);
    }

    /**
     * Invoke a method of the specified class instance.
     * @param name Method name.
     * @param obj Class instance containing method to invoke.
     * @param parameterTypes Method parameter array.
     * @param args An array of objects to be passed as arguments to the method call
     * @throws InvocationTargetException If the underlying constructor throws an exception.
     * @throws IllegalArgumentException  If an unwrapping conversion fails.
     * @throws IllegalAccessException    If the underlying field is either inaccessible or final.
     * @throws SecurityException         If a security manager is present and access to the field was denied.
     * @throws NoSuchMethodException     If a field with the specified name is not found.
     */
    public static final Object invokeMethod(
            final String name, final Object obj, final Class<?>[] parameterTypes, final Object... args)
            throws ReflectiveOperationException {
        Method m = obj.getClass().getDeclaredMethod(name, parameterTypes);
        boolean accessible = m.isAccessible();
        if (!accessible) {
            m.setAccessible(true);
        }
        Object result = m.invoke(obj, args);
        if (!accessible) {
            m.setAccessible(accessible);
        }
        return result;
    }

    /**
     * Invoke static method of the specified class.
     * @param name Method name.
     * @param c Class containing method to invoke.
     * @param parameterTypes Method parameter array.
     * @param args An array of objects to be passed as arguments to the method call
     * @throws InvocationTargetException If the underlying constructor throws an exception.
     * @throws IllegalArgumentException  If an unwrapping conversion fails.
     * @throws IllegalAccessException    If the underlying field is either inaccessible or final.
     * @throws SecurityException         If a security manager is present and access to the field was denied.
     * @throws NoSuchMethodException     If a field with the specified name is not found.
     */
    public static final Object invokeStaticMethod(
            final String name, final Class c, final Class<?>[] parameterTypes, final Object... args)
            throws ReflectiveOperationException {
        Method m = c.getDeclaredMethod(name, parameterTypes);
        boolean accessible = m.isAccessible();
        if (!accessible) {
            m.setAccessible(true);
        }
        Object result = m.invoke(null, args);
        if (!accessible) {
            m.setAccessible(accessible);
        }
        return result;
    }

}
