/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.framework.unit;

import java.lang.reflect.Method;

import junit.framework.TestCase;

import org.seasar.framework.exception.NoSuchMethodRuntimeException;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.StringUtil;

/**
 * テンプレートメソッドパターンに従った手続きでEasyMockを利用できるようにサポートするクラスです。
 * 
 * @author koichik
 */
public abstract class EasyMockTestCase extends TestCase {

    // instance fields
    /** EasyMockとの対話をサポートするオブジェクト */
    protected EasyMockSupport easyMockSupport = new EasyMockSupport();

    /**
     * インスタンスを構築します。
     */
    public EasyMockTestCase() {
    }

    /**
     * 名前を指定してインスタンスを構築します。
     * 
     * @param name
     *            テストケースの名前
     */
    public EasyMockTestCase(final String name) {
        super(name);
    }

    @Override
    public void runBare() throws Throwable {
        easyMockSupport.clear();
        setUp();
        try {
            bindFields();
            try {
                doRunTest();
            } finally {
                unbindFields();
            }
        } finally {
            tearDown();
        }
    }

    /**
     * モックをフィールドにバインディングします。
     * 
     * @throws Throwable
     *             何らかの例外またはエラーが発生した場合
     */
    protected void bindFields() throws Throwable {
        easyMockSupport.bindMockFields(this, null);
    }

    /**
     * テストを実行します。
     * 
     * @throws Throwable
     *             何らかの例外またはエラーが発生した場合
     */
    protected void doRunTest() throws Throwable {
        final boolean recorded = doRecord();
        if (recorded) {
            easyMockSupport.replay();
        }
        runTest();
        if (recorded) {
            easyMockSupport.verify();
            easyMockSupport.reset();
        }
    }

    /**
     * モックの振る舞いを記録します。
     * 
     * @return モックの振る舞いが記録するメソッドが存在する場合<code>true</code>、存在しない場合<code>false</code>
     * @throws Throwable
     *             何らかの例外またはエラーが発生した場合
     */
    protected boolean doRecord() throws Throwable {
        final String targetName = getTargetName();
        if (!StringUtil.isEmpty(targetName)) {
            try {
                final Method method = ClassUtil.getMethod(getClass(), "record"
                        + targetName, null);
                MethodUtil.invoke(method, this, null);
                return true;
            } catch (final NoSuchMethodRuntimeException ignore) {
            }
        }
        return false;
    }

    /**
     * テストケースの名前からプレフィックス<code>test</code>を除いた名前を返します。
     * 
     * @return テストケースの名前からプレフィックス<code>test</code>を除いた名前
     */
    protected String getTargetName() {
        return getName().substring(4);
    }

    /**
     * モックとフィールドのバインディングを解除します。
     */
    protected void unbindFields() {
        easyMockSupport.unbindMockFields(this);
    }

    /**
     * デフォルトのモックを作成します。
     * 
     * @param <T>
     *            モックの型
     * @param clazz
     *            モックの対象となるクラス
     * @return 作成されたモック
     */
    protected <T> T createMock(final Class<T> clazz) {
        return easyMockSupport.createMock(clazz);
    }

    /**
     * Niceモードのモックを作成します。
     * 
     * @param <T>
     *            モックの型
     * @param clazz
     *            モックの対象となるクラス
     * @return 作成されたモック
     */
    protected <T> T createNiceMock(final Class<T> clazz) {
        return easyMockSupport.createNiceMock(clazz);
    }

    /**
     * Strictモードのモックを作成します。
     * 
     * @param <T>
     *            モックの型
     * @param clazz
     *            モックの対象となるクラス
     * @return 作成されたモック
     */
    protected <T> T createStrictMock(final Class<T> clazz) {
        return easyMockSupport.createStrictMock(clazz);
    }

    /**
     * EasyMockの利用に必要な一連のメソッド呼び出しを1つのテンプレートメソッドとして提供する抽象クラスです。
     * 
     * @author taedium
     */
    protected abstract class Subsequence {

        /**
         * テストを実行します。
         * <p>
         * テストは次の順序で行われます。
         * <ul>
         * <li>モックの振る舞いの記録する</li>
         * <li>モックのモードをreplayモードに設定する</li>
         * <li>モックとのインタラクションを再現する</li>
         * <li>モックとのインタラクションを検証する</li>
         * <li>モックをリセットする</li>
         * </ul>
         * </p>
         * 
         * @throws Exception
         *             何らかの例外が発生した場合
         */
        public void doTest() throws Exception {
            record();
            EasyMockTestCase.this.easyMockSupport.replay();
            replay();
            EasyMockTestCase.this.easyMockSupport.verify();
            EasyMockTestCase.this.easyMockSupport.reset();
        }

        /**
         * モックの振る舞いを記録します。
         * 
         * @throws Exception
         *             何らかの例外が発生した場合
         */
        protected void record() throws Exception {
        }

        /**
         * モックとのインタラクションを再現します。
         * 
         * @throws Exception
         *             何らかの例外が発生した場合
         */
        protected abstract void replay() throws Exception;
    }
}
