package org.seasar.extension.unit;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.servlet.Servlet;
import javax.sql.DataSource;
import javax.transaction.TransactionManager;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.seasar.extension.dataset.ColumnType;
import org.seasar.extension.dataset.DataReader;
import org.seasar.extension.dataset.DataRow;
import org.seasar.extension.dataset.DataSet;
import org.seasar.extension.dataset.DataTable;
import org.seasar.extension.dataset.DataWriter;
import org.seasar.extension.dataset.impl.SqlDeleteTableWriter;
import org.seasar.extension.dataset.impl.SqlReloadReader;
import org.seasar.extension.dataset.impl.SqlReloadTableReader;
import org.seasar.extension.dataset.impl.SqlTableReader;
import org.seasar.extension.dataset.impl.SqlWriter;
import org.seasar.extension.dataset.impl.XlsReader;
import org.seasar.extension.dataset.impl.XlsWriter;
import org.seasar.extension.dataset.types.ColumnTypes;
import org.seasar.extension.jdbc.UpdateHandler;
import org.seasar.extension.jdbc.impl.BasicUpdateHandler;
import org.seasar.extension.mock.servlet.MockHttpServletRequest;
import org.seasar.extension.mock.servlet.MockHttpServletResponse;
import org.seasar.extension.mock.servlet.MockHttpServletResponseImpl;
import org.seasar.extension.mock.servlet.MockServlet;
import org.seasar.extension.mock.servlet.MockServletConfig;
import org.seasar.extension.mock.servlet.MockServletConfigImpl;
import org.seasar.extension.mock.servlet.MockServletContext;
import org.seasar.extension.mock.servlet.MockServletContextImpl;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.ContainerConstants;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.container.impl.S2ContainerImpl;
import org.seasar.framework.exception.EmptyRuntimeException;
import org.seasar.framework.exception.NoSuchMethodRuntimeException;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.ConnectionUtil;
import org.seasar.framework.util.DataSourceUtil;
import org.seasar.framework.util.FieldUtil;
import org.seasar.framework.util.FileOutputStreamUtil;
import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.ResourceUtil;
import org.seasar.framework.util.StringUtil;

/**
 * <p>
 * JUnitのTestCaseを拡張した、S2を使ったテストを楽しくおこなうための クラスです。
 * </p>
 * 
 * <p>
 * 各テストメソッドを実行する際には、以下の順番で処理をおこないます。 テストメソッド名が関係する処理がありますので、ここではtestXxx()という
 * テストメソッドを実行すると仮定します。
 * </p>
 * 
 * <ol>
 * <li>S2Containerのインスタンスを作成します。</li>
 * <li>setUp()を実行します。</li>
 * <li>setUpXxxという名前のメソッドがあれば実行します。</li>
 * <li>S2Containerを初期化します。</li>
 * <li>"j2ee.dataSource"という名前のデータソースをコンテナから取得します。</li>
 * <li>setUpAfterContainerInit()を実行します。</li>
 * <li>フィールドにDIをおこないます。</li>
 * <li>testXxx()を実行します。</li>
 * <li>tearDownBeforeContainerDestroy()を実行します。</li>
 * <li>データソースを開放します。</li>
 * <li>S2Containerの終了処理をおこないます。</li>
 * <li>tearDownXxx()という名前のメソッドがあれば実行します。</li>
 * <li>tearDown()を実行します。</li>
 * </ol>
 * 
 * <p>
 * setUp()内、またはsetUpXxx()内でinclude("j2ee.dicon")を行い、テスト
 * メソッド名の最後に"Tx"と付けると、テストメソッドの実行前にトランザク ションを開始し、テストメソッドの終了後にトランザクションをロールバック
 * します。DBのレコードを変更するようなテストの場合にこの機能を利用すると、 クリーンアップ処理が不要になります。
 * </p>
 * 
 * @author higa
 * @see junit.framework.TestCase
 */
public class S2TestCase extends TestCase {

	private static final String DATASOURCE_NAME = "j2ee"
			+ ContainerConstants.NS_SEP + "dataSource";

	private S2Container container_;
	
	private Servlet servlet_;
	
	private MockServletConfig servletConfig_;

	private MockServletContext servletContext_;

	private MockHttpServletRequest request_;

	private MockHttpServletResponse response_;

	private DataSource dataSource_;

	private Connection connection_;

	private DatabaseMetaData dbMetaData_;

	private List bindedFields_;

	/**
	 * <p>
	 * 指定された名前でテストケースを作成します。
	 * </p>
	 * 
	 * @param name
	 *            テストケースの名前
	 */
	public S2TestCase(String name) {
		super(name);
	}

	/**
	 * <p>
	 * テストケースが保有するS2コンテナを取得します。
	 * </p>
	 * 
	 * @return S2コンテナ
	 */
	public S2Container getContainer() {
		return container_;
	}

	/**
	 * <p>
	 * コンポーネント名を指定してS2コンテナからコンポーネントを取得 します。
	 * </p>
	 * <p>
	 * 指定した名前を持つコンポーネントが登録されていない場合はnullを 返します。
	 * </p>
	 * 
	 * @param componentName
	 *            取得するコンポーネント名
	 * @return 指定した名前を持つコンポーネント
	 * @see org.seasar.framework.container.S2Container#getComponent(java.lang.Object)
	 */
	public Object getComponent(String componentName) {
		return container_.getComponent(componentName);
	}

	/**
	 * <p>
	 * クラスを指定してS2コンテナからコンポーネントを取得します。
	 * </p>
	 * <p>
	 * インターフェースを指定した場合はそのインターフェースを実装して いるコンポーネント、クラスを指定した場合はそのクラスかそのクラスを
	 * 親に持つ子クラスのコンポーネントを取得します。
	 * </p>
	 * <p>
	 * 指定したクラスのコンポーネントが登録されていない場合はnullを 返します。
	 * </p>
	 * 
	 * @param componentClass
	 *            取得するクラス
	 * @return 指定したクラスのコンポーネント
	 * @see org.seasar.framework.container.S2Container#getComponent(java.lang.Object)
	 */
	public Object getComponent(Class componentClass) {
		return container_.getComponent(componentClass);
	}

	/**
	 * <p>
	 * コンポーネント名を指定してS2コンテナからコンポーネント定義を 取得します。
	 * </p>
	 * 
	 * @param componentName
	 *            取得するコンポーネント名
	 * @return 指定した名前を持つコンポーネント定義
	 * @see org.seasar.framework.container.S2Container#getComponentDef(java.lang.Object)
	 */
	public ComponentDef getComponentDef(String componentName) {
		return container_.getComponentDef(componentName);
	}

	/**
	 * <p>
	 * クラスを指定してS2コンテナからコンポーネント定義を取得します。
	 * </p>
	 * 
	 * @param componentClass
	 *            取得するクラス
	 * @return 指定したクラスのコンポーネント定義
	 * @see org.seasar.framework.container.S2Container#getComponentDef(java.lang.Object)
	 */
	public ComponentDef getComponentDef(Class componentClass) {
		return container_.getComponentDef(componentClass);
	}

	/**
	 * <p>
	 * クラスをS2コンテナにコンポーネント定義として登録します。
	 * </p>
	 * 
	 * @param componentClass
	 *            コンポーネントのクラス
	 * @see org.seasar.framework.container.S2Container#register(java.lang.Class)
	 */
	public void register(Class componentClass) {
		container_.register(componentClass);
	}

	/**
	 * <p>
	 * クラスをS2コンテナに名前付きコンポーネント定義として登録 します。
	 * </p>
	 * 
	 * @param componentClass
	 *            コンポーネントのクラス
	 * @param componentName
	 *            コンポーネントの名前
	 * @see org.seasar.framework.container.S2Container#register(java.lang.Class,
	 *      java.lang.String)
	 */
	public void register(Class componentClass, String componentName) {
		container_.register(componentClass, componentName);
	}

	/**
	 * <p>
	 * オブジェクトをS2コンテナにコンポーネントとして登録します。
	 * </p>
	 * <p>
	 * キーはオブジェクトのクラスになります。
	 * </p>
	 * 
	 * @param component
	 *            コンポーネントとして登録するオブジェクト
	 * @see org.seasar.framework.container.S2Container#register(java.lang.Object)
	 */
	public void register(Object component) {
		container_.register(component);
	}

	/**
	 * <p>
	 * オブジェクトをS2コンテナに名前付きコンポーネントとして登録 します。
	 * </p>
	 * 
	 * @param component
	 *            コンポーネントとして登録するオブジェクト
	 * @param componentName
	 *            コンポーネント名
	 * @see org.seasar.framework.container.S2Container#register(java.lang.Object,
	 *      java.lang.String)
	 */
	public void register(Object component, String componentName) {
		container_.register(component, componentName);
	}

	/**
	 * <p>
	 * S2コンテナにコンポーネント定義を登録します。
	 * </p>
	 * 
	 * @param componentDef
	 *            登録するコンポーネント定義
	 * @see org.seasar.framework.container.S2Container#register(org.seasar.framework.container.ComponentDef)
	 */
	public void register(ComponentDef componentDef) {
		container_.register(componentDef);
	}

	/**
	 * <p>
	 * 設定ファイルのパスを指定して子コンテナをincludeします。
	 * </p>
	 * <p>
	 * パスはCLASSPATHで指定されているディレクトリをルートとする 設定ファイルの絶対パスか、ファイル名のみを指定します。
	 * </p>
	 * <p>
	 * ファイル名のみの場合、テストケースと同じパッケージにあるもの とします。
	 * </p>
	 * 
	 * @param path
	 *            子コンテナの設定ファイルのパス
	 */
	public void include(String path) {
		S2ContainerFactory.include(container_, convertPath(path));
	}

	private String convertPath(String path) {
		if (ResourceUtil.getResourceNoException(path) != null) {
			return path;
		} else {
			String prefix = getClass().getPackage().getName().replace('.', '/');
			return prefix + "/" + path;
		}
	}

	/**
	 * <p>
	 * データソースを取得します。
	 * </p>
	 * 
	 * @return データソース
	 */
	public DataSource getDataSource() {
		if (dataSource_ == null) {
			throw new EmptyRuntimeException("dataSource");
		}
		return dataSource_;
	}

	/**
	 * <p>
	 * データソースからコネクションを取得します。
	 * </p>
	 * 
	 * @return データソースから取得したコネクション
	 */
	public Connection getConnection() {
		if (connection_ != null) {
			return connection_;
		}
		connection_ = DataSourceUtil.getConnection(getDataSource());
		return connection_;
	}

	/**
	 * <p>
	 * コネクションのデータベースメタデータを取得します。
	 * </p>
	 * 
	 * @return コネクションのデータベースメタデータ
	 */
	public DatabaseMetaData getDatabaseMetaData() {
		if (dbMetaData_ != null) {
			return dbMetaData_;
		}
		dbMetaData_ = ConnectionUtil.getMetaData(getConnection());
		return dbMetaData_;
	}

	/**
	 * <p>
	 * Excelファイルを読み、DataSetを作成します。
	 * </p>
	 * <p>
	 * シート名をテーブル名、一行目をカラム名、二行目以降をデータ として読み込みます。
	 * </p>
	 * 
	 * <p>
	 * パスはCLASSPATHで指定されているディレクトリをルートとする 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名
	 * のみの場合、テストケースと同じパッケージにあるものとします。
	 * </p>
	 * 
	 * @param path
	 *            Excelファイルのパス
	 * @return Excelファイルの内容から作成したDataSet
	 * @see org.seasar.extension.dataset.impl.XlsReader#read()
	 */
	public DataSet readXls(String path) {
		DataReader reader = new XlsReader(convertPath(path));
		return reader.read();
	}

	/**
	 * <p>
	 * DataSetの内容から、Excelファイルを作成します。
	 * </p>
	 * <p>
	 * シート名にテーブル名、一行目にカラム名、二行目以降にデータ を書き込みます。
	 * </p>
	 * 
	 * <p>
	 * パスはCLASSPATHで指定されているディレクトリをルートとする 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル
	 * 名のみの場合、テストケースと同じパッケージにあるものとします。
	 * </p>
	 * 
	 * @param path
	 *            Excelファイルのパス
	 * @param dataSet
	 *            Excelファイルに書き込む内容のDataSet
	 * @see org.seasar.extension.dataset.impl.XlsWriter#write(DataSet)
	 */
	public void writeXls(String path, DataSet dataSet) {
		File dir = ResourceUtil.getBuildDir(getClass());
		File file = new File(dir, convertPath(path));
		DataWriter writer = new XlsWriter(FileOutputStreamUtil.create(file));
		writer.write(dataSet);
	}

	/**
	 * <p>
	 * DataSetをDBに書き込みます。
	 * </p>
	 * 
	 * @param dataSet
	 *            データベースに書き込む内容のDataSet
	 * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet)
	 */
	public void writeDb(DataSet dataSet) {
		DataWriter writer = new SqlWriter(getDataSource());
		writer.write(dataSet);
	}

	/**
	 * <p>
	 * DBからレコードを読み込み、DataTableを作成します。
	 * </p>
	 * 
	 * @param table
	 *            読み込むテーブル名
	 * @return 読み込んだ内容から作成したDataTable
	 * @see org.seasar.extension.dataset.impl.SqlTableReader#read()
	 */
	public DataTable readDbByTable(String table) {
		return readDbByTable(table, null);
	}

	/**
	 * <p>
	 * DBからレコードを読み込み、DataTableを作成します。
	 * </p>
	 * <p>
	 * 読み込むレコードはconditionの条件を満たすレコードです。 conditionには" WHERE "より後ろをセットしてください。
	 * </p>
	 * 
	 * @param table
	 *            読み込むテーブル名
	 * @param condition
	 *            条件句(WHEREの後ろ)
	 * @return 読み込んだ内容から作成したDataTable
	 * @see org.seasar.extension.dataset.impl.SqlTableReader#read()
	 */
	public DataTable readDbByTable(String table, String condition) {
		SqlTableReader reader = new SqlTableReader(getDataSource());
		reader.setTable(table, condition);
		return reader.read();
	}

	/**
	 * <p>
	 * DBからSQL文の実行結果を取得し、DataTableを作成します。
	 * </p>
	 * <p>
	 * 作成したDataTableのテーブル名はtableNameになります。
	 * </p>
	 * 
	 * @param sql
	 *            実行するSQL文
	 * @param tableName
	 *            作成するDataTableのテーブル名
	 * @return 読み出した内容のDataTable
	 * @see org.seasar.extension.dataset.impl.SqlTableReader#read()
	 */
	public DataTable readDbBySql(String sql, String tableName) {
		SqlTableReader reader = new SqlTableReader(getDataSource());
		reader.setSql(sql, tableName);
		return reader.read();
	}

	/**
	 * <p>
	 * Excelファイルを読み込み、DBに書き込みます。
	 * </p>
	 * <p>
	 * シート名をテーブル名、一行目をカラム名、二行目以降をデータ として読み込みます。
	 * </p>
	 * 
	 * <p>
	 * パスはCLASSPATHで指定されているディレクトリをルートとする 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名
	 * のみの場合、テストケースと同じパッケージにあるものとします。
	 * </p>
	 * 
	 * @param path
	 *            Excelファイルのパス
	 * @see org.seasar.extension.dataset.impl.XlsReader#read()
	 * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet)
	 */
	public void readXlsWriteDb(String path) {
		writeDb(readXls(path));
	}

	/**
	 * <p>
	 * Excelファイルを読み込み、DBに書き込みます。
	 * </p>
	 * <p>
	 * シート名をテーブル名、一行目をカラム名、二行目以降をデータ として読み込みます。
	 * </p>
	 * 
	 * <p>
	 * Excelの内容とDBのレコードとで主キーが一致するものがあれば、 そのレコードを削除した後に書き込みます。
	 * </p>
	 * 
	 * <p>
	 * パスはCLASSPATHで指定されているディレクトリをルートとする 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名
	 * のみの場合、テストケースと同じパッケージにあるものとします。
	 * </p>
	 * 
	 * @param path
	 *            Excelファイルのパス
	 * @see org.seasar.extension.dataset.impl.XlsReader#read()
	 * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet)
	 */
	public void readXlsReplaceDb(String path) {
		DataSet dataSet = readXls(path);
		deleteDb(dataSet);
		writeDb(dataSet);
	}

	/**
	 * <p>
	 * Excelファイルを読み込み、DBに書き込みます。
	 * </p>
	 * <p>
	 * シート名をテーブル名、一行目をカラム名、二行目以降をデータ として読み込みます。
	 * </p>
	 * 
	 * <p>
	 * 対象となるテーブルのレコードを全て削除した後に書き込みます。
	 * </p>
	 * 
	 * <p>
	 * パスはCLASSPATHで指定されているディレクトリをルートとする 設定ファイルの絶対パスか、ファイル名のみを指定します。ファイル名
	 * のみの場合、テストケースと同じパッケージにあるものとします。
	 * </p>
	 * 
	 * @see org.seasar.extension.dataset.impl.XlsReader#read()
	 * @see org.seasar.extension.dataset.impl.SqlWriter#write(DataSet)
	 */
	public void readXlsAllReplaceDb(String path) {
		DataSet dataSet = readXls(path);
		for (int i = dataSet.getTableSize() - 1; i >= 0; --i) {
			deleteTable(dataSet.getTable(i).getTableName());
		}
		writeDb(dataSet);
	}

	/**
	 * <p>
	 * DataSetに対応するDBのレコードを読み込み、DataSetを作成します 。
	 * </p>
	 * 
	 * @param dataSet
	 *            対象DBに対応するDataSet
	 * @return 最新状態のDataSet
	 * @see org.seasar.extension.dataset.impl.SqlReloadReader#read()
	 */
	public DataSet reload(DataSet dataSet) {
		return new SqlReloadReader(getDataSource(), dataSet).read();
	}

	/**
	 * <p>
	 * DataTableに対応するDBのレコードを読み込み、DataTableを作成 します。
	 * </p>
	 * 
	 * @param table
	 *            対象DBに対応するDataTable
	 * @return 最新状態のDataTable
	 * @see org.seasar.extension.dataset.impl.SqlReloadTableReader#read()
	 */
	public DataTable reload(DataTable table) {
		return new SqlReloadTableReader(getDataSource(), table).read();
	}

	/**
	 * <p>
	 * DataSetに対応するDBのレコードを削除します。
	 * </p>
	 * 
	 * @param dataSet
	 *            対象DBに対応するDataSet
	 * @see org.seasar.extension.dataset.impl.SqlDeleteTableWriter#write(DataTable)
	 */
	public void deleteDb(DataSet dataSet) {
		SqlDeleteTableWriter writer = new SqlDeleteTableWriter(getDataSource());
		for (int i = dataSet.getTableSize() - 1; i >= 0; --i) {
			writer.write(dataSet.getTable(i));
		}
	}

	/**
	 * <p>
	 * DBから指定するテーブルの全レコードを削除します。
	 * </p>
	 * 
	 * @param tableName
	 *            削除対象のテーブル名
	 */
	public void deleteTable(String tableName) {
		UpdateHandler handler = new BasicUpdateHandler(getDataSource(),
				"DELETE FROM " + tableName);
		handler.execute(null);
	}

	/**
	 * <p>
	 * DataSet同士を比較します。
	 * </p>
	 * <p>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param expected
	 *            予測値
	 * @param actual
	 *            実際値
	 */
	public void assertEquals(DataSet expected, DataSet actual) {
		assertEquals(null, expected, actual);
	}

	/**
	 * <p>
	 * DataSet同士を比較します。
	 * </p>
	 * <p>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param message
	 *            assert失敗時のメッセージ
	 * @param expected
	 *            予測値
	 * @param actual
	 *            実際値
	 */
	public void assertEquals(String message, DataSet expected, DataSet actual) {
		message = message == null ? "" : message;
		assertEquals(message + ":TableSize", expected.getTableSize(), actual
				.getTableSize());
		for (int i = 0; i < expected.getTableSize(); ++i) {
			assertEquals(message, expected.getTable(i), actual.getTable(i));
		}
	}

	/**
	 * <p>
	 * DataTable同士を比較します。
	 * </p>
	 * <p>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param expected
	 *            予測値
	 * @param actual
	 *            実際値
	 */
	public void assertEquals(DataTable expected, DataTable actual) {
		assertEquals(null, expected, actual);
	}

	/**
	 * </p>
	 * DataTable同士を比較します。
	 * </p>
	 * <p>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param message
	 *            assert失敗時のメッセージ
	 * @param expected
	 *            予測値
	 * @param actual
	 *            実際値
	 */
	public void assertEquals(String message, DataTable expected,
			DataTable actual) {

		message = message == null ? "" : message;
		assertEquals(message + ":RowSize", expected.getRowSize(), actual
				.getRowSize());
		for (int i = 0; i < expected.getRowSize(); ++i) {
			DataRow expectedRow = expected.getRow(i);
			DataRow actualRow = actual.getRow(i);

			for (int j = 0; j < expected.getColumnSize(); ++j) {
				String columnName = expected.getColumnName(j);
				Object expectedValue = expectedRow.getValue(columnName);
				ColumnType ct = ColumnTypes.getColumnType(expectedValue);
				Object actualValue = actualRow.getValue(columnName);
				if (!ct.equals(expectedValue, actualValue)) {
					assertEquals(message + ":Row=" + i + ":columnName="
							+ columnName, expectedValue, actualValue);
				}
			}
		}
	}

	/**
	 * <p>
	 * オブジェクトをDataSetと比較します。
	 * </p>
	 * <p>
	 * オブジェクトは、Bean、Map、BeanのList、MapのListのいずれか でなければなりません。
	 * </p>
	 * 
	 * </p>
	 * Beanの場合はプロパティ名を、Mapの場合はキーをカラム名として 比較します。 <br>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param expected
	 *            予測値
	 * @param actual
	 *            実際値
	 */
	public void assertEquals(DataSet expected, Object actual) {
		assertEquals(null, expected, actual);
	}

	/**
	 * <p>
	 * オブジェクトをDataSetと比較します。
	 * </p>
	 * <p>
	 * オブジェクトは、Bean、Map、BeanのList、MapのListのいずれか でなければなりません。
	 * </p>
	 * 
	 * <p>
	 * Beanの場合はプロパティ名を、Mapの場合はキーをカラム名として 比較します。 <br>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param message
	 *            assert失敗時のメッセージ
	 * @param expected
	 *            予測値
	 * @param actual
	 *            実際値
	 */
	public void assertEquals(String message, DataSet expected, Object actual) {
		if (expected == null || actual == null) {
			Assert.assertEquals(message, expected, actual);
			return;
		}
		if (actual instanceof List) {
			List actualList = (List) actual;
			Assert.assertFalse(actualList.isEmpty());
			Object actualItem = actualList.get(0);
			if (actualItem instanceof Map) {
				assertMapListEquals(message, expected, actualList);
			} else {
				assertBeanListEquals(message, expected, actualList);
			}
		}  else if (actual instanceof Object[]) {
		     assertEquals(message, expected, Arrays.asList((Object[]) actual));
		} else {
			if (actual instanceof Map) {
				assertMapEquals(message, expected, (Map) actual);
			} else {
				assertBeanEquals(message, expected, actual);
			}
		}
	}

	/**
	 * <p>
	 * MapをDataSetと比較します。
	 * </p>
	 * <p>
	 * Mapのキーをカラム名として比較します。 <br>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param message
	 *            assert失敗時のメッセージ
	 * @param expected
	 *            予測値
	 * @param map
	 *            実際値
	 */
	protected void assertMapEquals(String message, DataSet expected, Map map) {

		MapReader reader = new MapReader(map);
		assertEquals(message, expected, reader.read());
	}

	/**
	 * <p>
	 * MapのListをDataSetと比較します。
	 * </p>
	 * <p>
	 * Mapのキーをカラム名として比較します。 <br>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param message
	 *            assert失敗時のメッセージ
	 * @param expected
	 *            予測値
	 * @param list
	 *            実際値
	 */
	protected void assertMapListEquals(String message, DataSet expected,
			List list) {

		MapListReader reader = new MapListReader(list);
		assertEquals(message, expected, reader.read());
	}

	/**
	 * <p>
	 * BeanをDataSetと比較します。
	 * </p>
	 * <p>
	 * Beanのプロパティ名をカラム名として比較します。 <br>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param message
	 *            assert失敗時のメッセージ
	 * @param expected
	 *            予測値
	 * @param bean
	 *            実際値
	 */
	protected void assertBeanEquals(String message, DataSet expected,
			Object bean) {

		BeanReader reader = new BeanReader(bean);
		assertEquals(message, expected, reader.read());
	}

	/**
	 * <p>
	 * BeanのListをDataSetと比較します。
	 * </p>
	 * <p>
	 * Beanのプロパティ名をカラム名として比較します。 <br>
	 * カラムの並び順は比較に影響しません。 <br>
	 * 数値は全てBigDecimalとして比較します。
	 * </p>
	 * 
	 * @param message
	 *            assert失敗時のメッセージ
	 * @param expected
	 *            予測値
	 * @param list
	 *            実際値
	 */
	protected void assertBeanListEquals(String message, DataSet expected,
			List list) {

		BeanListReader reader = new BeanListReader(list);
		assertEquals(message, expected, reader.read());
	}

	/**
	 * @see junit.framework.TestCase#runBare()
	 */
	public void runBare() throws Throwable {
		setUpContainer();
		setUp();
		try {
			setUpForEachTestMethod();
			try {
				container_.init();
				try {
					setupDataSource();
					try {
						setUpAfterContainerInit();
						bindFields();
						setUpAfterBindFields();
						try {
							runTestTx();
						} finally {
							tearDownBeforeUnbindFields();
							unbindFields();
						}
						tearDownBeforeContainerDestroy();
					} finally {
						tearDownDataSource();
					}
				} finally {
					tearDownContainer();
				}
			} finally {
				tearDownForEachTestMethod();
			}

		} finally {
			for (int i = 0; i < 5; ++i) {
				System.runFinalization();
				System.gc();
			}
			tearDown();
		}
	}

	/**
	 * <p>
	 * コンテナをセットアップするメソッドです。
	 * </p>
	 * <p>
	 * 必要な場合にオーバーライドしてください。
	 * </p>
	 * 
	 * @throws Throwable
	 */
	protected void setUpContainer() throws Throwable {
		container_ = new S2ContainerImpl();
		servletContext_ = new MockServletContextImpl("s2jsf-example");
		request_ = servletContext_.createRequest("/hello.html");
		response_ = new MockHttpServletResponseImpl(request_);
		servletConfig_ = new MockServletConfigImpl();
		servletConfig_.setServletContext(servletContext_);
		servlet_ = new MockServlet();
		servlet_.init(servletConfig_);
		container_.setServletContext(servletContext_);
		container_.setRequest(request_);
		container_.setResponse(response_);
		SingletonS2ContainerFactory.setContainer(container_);
	}
	
	protected void tearDownContainer() throws Throwable {
		container_.destroy();
		SingletonS2ContainerFactory.setContainer(null);
		container_ = null;
		servletContext_ = null;
		request_ = null;
		response_ = null;
		servletConfig_ = null;
		servlet_ = null;
	}

	/**
	 * <p>
	 * コンテナ初期化後に実行されるセットアップメソッドです。
	 * </p>
	 * <p>
	 * 必要な場合にオーバーライドしてください。
	 * </p>
	 * 
	 * @throws Throwable
	 */
	protected void setUpAfterContainerInit() throws Throwable {
	}
	
	/**
	 * <p>
	 * フィールドが設定された後に実行されるセットアップメソッドです。
	 * </p>
	 * <p>
	 * 必要な場合にオーバーライドしてください。
	 * </p>
	 * 
	 * @throws Throwable
	 */
	protected void setUpAfterBindFields() throws Throwable {
	}
	
	/**
	 * <p>
	 * フィールドの設定が解除される前に実行される処理終了メソッドです。
	 * </p>
	 * <p>
	 * 必要な場合にオーバーライドしてください。
	 * </p>
	 * 
	 * @throws Throwable
	 */
	protected void tearDownBeforeUnbindFields() throws Throwable {
	}

	/**
	 * <p>
	 * setup() 後に実行されるテストメソッドごとのセットアップ メソッドです。
	 * </p>
	 * <p>
	 * testXxx() というメソッドの場合、setUpXxx() という名前で セットアップメソッドを作成しておくと、自動的に実行されます。
	 * </p>
	 * 
	 * @throws Throwable
	 */
	protected void setUpForEachTestMethod() throws Throwable {
		invoke("setUp" + getTargetName());
	}

	/**
	 * <p>
	 * コンテナ終了処理前に実行される終了処理メソッドです。
	 * </p>
	 * <p>
	 * 必要な場合にオーバーライドしてください。
	 * </p>
	 * 
	 * @throws Throwable
	 */
	protected void tearDownBeforeContainerDestroy() throws Throwable {
	}

	/**
	 * <p>
	 * tearDown() 前に実行されるテストメソッドごとの終了処理メソッド です。
	 * </p>
	 * <p>
	 * testXxx() というメソッドの場合、tearDownXxx() という名前で 終了処理メソッドを作成しておくと、自動的に実行されます。
	 * </p>
	 * 
	 * @throws Throwable
	 */
	protected void tearDownForEachTestMethod() throws Throwable {
		invoke("tearDown" + getTargetName());
	}
	
	protected Servlet getServlet() {
		return servlet_;
	}
	
	protected void setServlet(Servlet servlet) {
		servlet_ = servlet;
	}
	
	protected MockServletConfig getServletConfig() {
		return servletConfig_;
	}
	
	protected void setServletConfig(MockServletConfig servletConfig) {
		servletConfig_ = servletConfig;
	}

	protected MockServletContext getServletContext() {
		return servletContext_;
	}

	protected void setServletContext(MockServletContext servletContext) {
		servletContext_ = servletContext;
	}

	protected MockHttpServletRequest getRequest() {
		return request_;
	}

	protected void setRequest(MockHttpServletRequest request) {
		request_ = request;
	}

	protected MockHttpServletResponse getResponse() {
		return response_;
	}

	protected void setResponse(MockHttpServletResponse response) {
		response_ = response;
	}

	private String getTargetName() {
		return getName().substring(4);
	}

	private void invoke(String methodName) throws Throwable {
		try {
			Method method = ClassUtil.getMethod(getClass(), methodName, null);
			MethodUtil.invoke(method, this, null);
		} catch (NoSuchMethodRuntimeException ignore) {
		}
	}

	private void bindFields() throws Throwable {
		bindedFields_ = new ArrayList();
		for (Class clazz = getClass(); clazz != S2TestCase.class
				&& clazz != null; clazz = clazz.getSuperclass()) {

			Field[] fields = clazz.getDeclaredFields();
			for (int i = 0; i < fields.length; ++i) {
				bindField(fields[i]);
			}
		}
	}

	private void bindField(Field field) {
		if (isAutoBindable(field)) {
			field.setAccessible(true);
			if (FieldUtil.get(field, this) != null) {
				return;
			}
			String name = normalizeName(field.getName());
			Object component = null;
			if (getContainer().hasComponentDef(name)) {
				Class componentClass = getComponentDef(name).getComponentClass();
				if (componentClass == null) {
					component = getComponent(name);
					if (component != null) {
						componentClass = component.getClass();
					}
				}
				if (componentClass != null && field.getType().isAssignableFrom(componentClass)) {
					if (component == null) {
						component = getComponent(name);
					}
				} else {
					component = null;
				}
			}
			if (component == null && getContainer().hasComponentDef(field.getType())) {
				component = getComponent(field.getType());
			}
			if (component != null) {
				FieldUtil.set(field, this, component);
				bindedFields_.add(field);
			}
		}
	}

	private String normalizeName(String name) {
		return StringUtil.replace(name, "_", "");
	}

	private boolean isAutoBindable(Field field) {
		int modifiers = field.getModifiers();
		return !Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)
				&& !field.getType().isPrimitive();
	}

	private void unbindFields() {
		for (int i = 0; i < bindedFields_.size(); ++i) {
			Field field = (Field) bindedFields_.get(i);
			try {
				field.set(this, null);
			} catch (IllegalArgumentException e) {
				System.err.println(e);
			} catch (IllegalAccessException e) {
				System.err.println(e);
			}
		}
	}

	private void runTestTx() throws Throwable {
		TransactionManager tm = null;
		if (needTransaction()) {
			try {
				tm = (TransactionManager) getComponent(TransactionManager.class);
				tm.begin();
			} catch (Throwable t) {
				System.err.println(t);
			}
		}
		try {
			runTest();
		} finally {
			if (tm != null) {
				tm.rollback();
			}
		}
	}

	private boolean needTransaction() {
		return getName().endsWith("Tx");
	}

	private void setupDataSource() {
		try {
			if (container_.hasComponentDef(DATASOURCE_NAME)) {
				dataSource_ = (DataSource) container_
						.getComponent(DATASOURCE_NAME);
			} else if (container_.hasComponentDef(DataSource.class)) {
				dataSource_ = (DataSource) container_
						.getComponent(DataSource.class);
			}
		} catch (Throwable t) {
			System.err.println(t);
		}
	}

	private void tearDownDataSource() {
		dbMetaData_ = null;
		if (connection_ != null) {
			ConnectionUtil.close(connection_);
			connection_ = null;
		}
		dataSource_ = null;
	}
}