/*
 * File   : $Source: /usr/local/cvs/opencms/test/org/opencms/test/OpenCmsThreadedTestCaseSuite.java,v $
 * Date   : $Date: 2008-04-03 07:45:26 $
 * Version: $Revision: 1.5 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * 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.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * 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 org.opencms.test;

import org.opencms.file.CmsObject;

import java.lang.reflect.Method;

/**
 * Allows to run a specific method of an {@link org.opencms.test.OpenCmsTestCase} case concurrently.<p>
 *  
 * @author Alexander Kandzior 
 * 
 * @version $Revision: 1.5 $
 * 
 * @since 6.5.0
 */
public class OpenCmsThreadedTestCaseSuite {

    /** A not-initialized OpenCms user context. */
    public static final CmsObject PARAM_CMSOBJECT = new CmsObject(null, null);

    /** A not-initialized OpenCms user context. */
    public static final Integer PARAM_COUNTER = new Integer(Integer.MIN_VALUE + 1);

    /** The maximum allowed runtime for this test suite. */
    private long m_allowedRuntime;

    /** The number of concurrent test cases to execute. */
    private int m_count;

    /** The time the method executed. */
    private long m_runtime;

    /** The initialized threaded test cases. */
    private OpenCmsThreadedTestCase[] m_threads;

    /** Error that occurred when running the test suite. */
    private Throwable m_throwable;

    /**
     * Generates a new threaded test case suite with the selected test case method.<p>
     *
     * The <code>parameters</code> array may contain the following placeholders:<ul>
     * <li>{@link #PARAM_CMSOBJECT}: This will be replaced by a new {@link CmsObject} instance.
     * <li>{@link #PARAM_COUNTER}: This will be replaced by an integer counter with the number of the generated thread
     * </ul>
     *
     * @param count the number of concurrent test cases to execute
     * @param testCase the test case class to use
     * @param method the method to execute on the test case
     * @param parameters the parameter values for the method
     */
    public OpenCmsThreadedTestCaseSuite(int count, OpenCmsTestCase testCase, String method, Object[] parameters) {

        m_count = count;
        m_runtime = -1;
        m_allowedRuntime = m_count * 100;

        try {
            // generate class array - required for reflection
            Class[] clazz = new Class[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                clazz[i] = parameters[i].getClass();
            }
            // get the method we want to use from the test case
            Method m = testCase.getClass().getMethod(method, clazz);
            // now initialize the threads
            m_threads = initThreads(m_count, testCase, m, parameters);
        } catch (Exception e) {
            // exception may be becasue of bad method, or during initialization of threads
            m_throwable = e;
        }
    }

    /**
     * Returns a list of {@link OpenCmsThreadedTestCase} instances that will be executed concurrently by this suite.<p>
     * 
     * @param count the number of concurrent executions
     * @param testCase the base test case instance to execute a method from
     * @param method the method name from the test case to execute
     * @param parameters the parameters of the test case
     * 
     * @return a list of {@link OpenCmsThreadedTestCase} instances that will be executed concurrently by this suite
     * 
     * @throws Exception if something goes wrong
     */
    private static OpenCmsThreadedTestCase[] initThreads(
        int count,
        OpenCmsTestCase testCase,
        Method method,
        Object[] parameters) throws Exception {

        OpenCmsThreadedTestCase[] result = new OpenCmsThreadedTestCase[count];
        for (int i = 0; i < count; i++) {
            // create a copy of the parameters, initialize any placeholders
            Object[] params = new Object[parameters.length];
            for (int j = 0; j < parameters.length; j++) {
                if (parameters[j] == PARAM_CMSOBJECT) {
                    // initialize a new CmsObject instance
                    params[j] = testCase.getCmsObject();
                } else if (parameters[j] == PARAM_COUNTER) {
                    // add a new counter parameter
                    params[j] = new Integer(i);
                } else {
                    // just use the given parameter
                    params[j] = parameters[j];
                }
            }
            result[i] = new OpenCmsThreadedTestCase(testCase, method, params);
        }

        return result;
    }

    /**
     * Returns the maximum allowed runtime of this test suite.<p>
     * 
     * If the given limit is exceeded, then this suite will stop waiting for 
     * the running test cases to finish and generate an internal exception.<p> 
     *
     * The default is the number of threads multiplied by 100 milliseconds.<p>
     * 
     * @return the maximum allowed runtime of this test suite
     */
    public long getAllowedRuntime() {

        return m_allowedRuntime;
    }

    /**
     * Returns the runtime of this test suite.<p>
     * 
     * @return the runtime of this test suite
     */
    public long getRuntime() {

        return m_runtime;
    }

    /**
     * Returns the error that that may have been caused while initializing or running this suite,
     * or <code>null</code> in case no error was thrown.<p>
     *
     * @return the error that that may have been caused while initializing or running this suite,
     * or <code>null</code> in case no error was thrown
     */
    public Throwable getThrowable() {

        return m_throwable;
    }

    /**
     * Runs the initialized test concurrent test cases.<p>
     *
     * Returns the array of generated {@link OpenCmsThreadedTestCase} instances, which contain information 
     * about the individual test results.<p>*
     * 
     * @return the array of generated {@link OpenCmsThreadedTestCase} instances
     */
    public OpenCmsThreadedTestCase[] run() {

        long startTime = System.currentTimeMillis();

        if (m_throwable == null) {
            // there may have been an exception acessing the selected method, so the test cases can not run at all

            try {
                for (int i = 0; i < m_count; i++) {
                    // start all the test cases
                    m_threads[i].start();
                }

                // give the tests some time to finish
                long runTime;
                int runCount;
                long loopTime = System.currentTimeMillis();
                do {
                    runCount = 0;
                    for (int i = 0; i < m_count; i++) {
                        // check each test if it is still alive
                        if (m_threads[i].isAlive()) {
                            // this thread is still running
                            runCount++;
                        }
                    }
                    if (runCount > 0) {
                        // give the running threads some time to finish
                        Thread.sleep(50);
                    }
                    // calculate the runtime
                    runTime = System.currentTimeMillis() - loopTime;
                    // limit the run time to avoid total hangup of test
                } while ((runCount > 0) && (runTime < m_allowedRuntime));

                if ((runCount > 0) && (runTime >= m_allowedRuntime)) {
                    // allowed runtime was exceeded, generate exception that is stored be the member later
                    throw new RuntimeException("There where "
                        + runCount
                        + " threads still running after "
                        + (m_allowedRuntime / 1000.0)
                        + " seconds");
                }

            } catch (Throwable e) {
                // store the exception
                m_throwable = e;
            }
        }

        // calculate the final runtime
        m_runtime = System.currentTimeMillis() - startTime;
        return m_threads;
    }

    /**
     * Sets the maximum allowed runtime of this test suite.<p>
     * 
     * @param allowedRuntime the maximum allowed runtime of this test suite to set
     */
    public void setAllowedRuntime(long allowedRuntime) {

        m_allowedRuntime = allowedRuntime;
    }
}