// jTDS JDBC Driver for Microsoft SQL Server and Sybase
// Copyright (C) 2004 The jTDS Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
package net.sourceforge.jtds.jdbc;

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

import junit.framework.Assert;

/**
 * Base class for unit tests which do not connect to a database.
 *
 * @author David D. Kilzer
 * @version $Id: UnitTestBase.java,v 1.12 2005-12-22 17:24:07 ddkilzer Exp $
 */
public abstract class UnitTestBase extends DatabaseTestCase {

    /**
     * Constructor.
     *
     * @param name The name of the test.
     */
    public UnitTestBase(final String name) {
        super(name);
    }


    /**
     * Invoke a constructor on a class using reflection.
     *
     * @param klass The class.
     * @param classes The classes in the parameter list.
     * @param objects The objects to be used as parameters.
     * @return The object constructed.
     */
    public static Object invokeConstructor(final Class klass, final Class[] classes, final Object[] objects) {
        try {
            Constructor constructor;
            try {
                constructor = klass.getDeclaredConstructor(classes);
            }
            catch (NoSuchMethodException e) {
                constructor = klass.getConstructor(classes);
            }
            constructor.setAccessible(true);
            return constructor.newInstance(objects);
        }
        catch (NoSuchMethodException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (InstantiationException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (IllegalAccessException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (InvocationTargetException e) {
            RuntimeException runtimeException = new RuntimeException(e.getTargetException().getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
    }


    /**
     * Get the value of an instance field on an object using reflection.
     *
     * @param instance The instance of the object.
     * @param fieldName The name of the field.
     * @return The object returned by getting the field.
     */
    public static Object invokeGetInstanceField(final Object instance, final String fieldName) {
        try {
            Field field;
            try {
                field = instance.getClass().getField(fieldName);
            }
            catch (NoSuchFieldException e) {
                field = instance.getClass().getDeclaredField(fieldName);
            }
            field.setAccessible(true);
            return field.get(instance);
        }
        catch (NoSuchFieldException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (IllegalAccessException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
    }


    /**
     * Set the value of an instance field on an object using reflection.
     *
     * @param instance The instance of the object.
     * @param fieldName The name of the field.
     * @param fieldValue The value to set the field to.
     */
    public static void invokeSetInstanceField(final Object instance, final String fieldName, final Object fieldValue) {
        try {
            Field field;
            try {
                field = instance.getClass().getField(fieldName);
            }
            catch (NoSuchFieldException e) {
                field = instance.getClass().getDeclaredField(fieldName);
            }
            field.setAccessible(true);
            field.set(instance, fieldValue);
        }
        catch (NoSuchFieldException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (IllegalAccessException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
    }


    /**
     * Invoke an instance method on an object using reflection.
     *
     * @param instance The instance of the object.
     * @param methodName The name of the method.
     * @param classes The classes in the parameter list.
     * @param objects The objects to be used as parameters.
     * @return The object returned by invoking the method.
     */
    public static Object invokeInstanceMethod(
            final Object instance, final String methodName, final Class[] classes, final Object[] objects) {

        try {
            Method method;
            try {
                method = instance.getClass().getDeclaredMethod(methodName, classes);
            }
            catch (NoSuchMethodException e) {
                method = instance.getClass().getMethod(methodName, classes);
            }
            method.setAccessible(true);
            return method.invoke(instance, objects);
        }
        catch (NoSuchMethodException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (IllegalAccessException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (InvocationTargetException e) {
            RuntimeException runtimeException = new RuntimeException(e.getTargetException().getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
    }


    /**
     * Invoke a static method on a class using reflection.
     *
     * @param klass The class.
     * @param methodName The name of the method.
     * @param classes The classes in the parameter list.
     * @param objects The objects to be used as parameters.
     * @return The object returned by invoking the method.
     */
    public static Object invokeStaticMethod(
            final Class klass, final String methodName, final Class[] classes, final Object[] objects) {

        try {
            Method method;
            try {
                method = klass.getDeclaredMethod(methodName, classes);
            }
            catch (NoSuchMethodException e) {
                method = klass.getMethod(methodName, classes);
            }
            method.setAccessible(true);
            return method.invoke(klass, objects);
        }
        catch (NoSuchMethodException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (IllegalAccessException e) {
            RuntimeException runtimeException = new RuntimeException(e.getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
        catch (InvocationTargetException e) {
            RuntimeException runtimeException = new RuntimeException(e.getTargetException().getMessage());
            Support.linkException(runtimeException, e);
            throw runtimeException;
        }
    }


    /**
     * Compare two arrays element-by-element.
     * <p/>
     * The default JUnit {@link Assert#assertEquals(String, Object, Object)} method
     * does not handle them properly.
     *
     * @param message The message to print upon failure.
     * @param expected The expected value.
     * @param actual The actual value.
     */
    protected void assertEquals(String message, Object[] expected, Object[] actual) {

        if (expected == null && actual == null) {
            return;
        }

        if (expected == null || actual == null) {
            failNotEquals(message, expected, actual);
        }

        if (expected.length != actual.length) {
            failNotEquals(message, expected, actual);
        }

        for (int i = 0; i < expected.length; i++) {
            if (expected[i] == null || !expected[i].equals(actual[i])) {
                failNotEquals(message, expected, actual);
            }
        }
    }


    /**
     * @see Assert#failNotEquals(java.lang.String, java.lang.Object, java.lang.Object)
     */
    private void failNotEquals(String message, Object[] expected, Object[] actual) {
        fail((String) invokeStaticMethod(Assert.class, "format",
                                         new Class[]{String.class, Object.class, Object.class},
                                         new Object[]{message, format(expected), format(actual)}));
    }


    /**
     * Format an <code>Object[]</code> object to a <code>String</code>.
     *
     * @param object The object to be formatted.
     * @return Formatted string representing the object.
     */
    private String format(Object[] object) {
        StringBuilder buf = new StringBuilder();
        if (object == null || object.length < 1) {
            buf.append(object);
        } else {
            buf.append('{');
            buf.append(object[0]);
            for (int i = 1; i < object.length; i++) {
                buf.append(',');
                if (object[i] instanceof Object[]) {
                    buf.append(format((Object[]) object[i]));
                }
                else {
                    buf.append(object[i]);
                }
            }
            buf.append('}');
        }
        return buf.toString();
    }


    /**
     * Changes the first character of a string to uppercase.
     *
     * @param s The string to be processed.
     * @return The value of <code>s</code> if it is <code>null</code> or zero length,
     *         else the string with the first character changed to uppercase.
     */
    protected static String ucFirst(String s) {
        if (s == null || s.length() == 0) return s;
        if (s.length() == 1) {
            return s.toUpperCase();
        }
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }

}
