/*
 * Copyright (c) 2009 OrangeSignal.com All rights reserved.
 * 
 * これは Apache ライセンス Version 2.0 (以下、このライセンスと記述) に
 * 従っています。このライセンスに準拠する場合以外、このファイルを使用
 * してはなりません。このライセンスのコピーは以下から入手できます。
 * 
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * 適用可能な法律がある、あるいは文書によって明記されている場合を除き、
 * このライセンスの下で配布されているソフトウェアは、明示的であるか暗黙の
 * うちであるかを問わず、「保証やあらゆる種類の条件を含んでおらず」、
 * 「あるがまま」の状態で提供されるものとします。
 * このライセンスが適用される特定の許諾と制限については、このライセンス
 * を参照してください。
 */

package jp.sf.orangesignal.ta.data;

import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;

import jp.sf.orangesignal.ta.util.Assert;
import jp.sf.orangesignal.ta.util.DateFormatUtils;
import jp.sf.orangesignal.ta.util.NumberFormatUtils;

/**
 * データ変換ユーティリティを提供します。
 * 
 * @author 杉澤 浩二
 * @since 2.1
 */
public abstract class DataConvertUtils {

	// ------------------------------------------------------------------------
	// toDate

	/**
	 * <p>指定されたオブジェクトを日時へ変換して返します。</p>
	 * 
	 * @param obj 変換するオブジェクト
	 * @return 変換された日時。または <code>null</code>
	 * @throws IllegalArgumentException オブジェクトが {@link CharSequence} の場合
	 * @throws DataConvertException オブジェクトを解析できない場合
	 * @see #toDate(Object, DateFormatConfig)
	 */
	public static Date toDate(final Object obj) throws IllegalArgumentException, DataConvertException {
		return toDate(obj, null);
	}

	/**
	 * <p>指定されたオブジェクトを日時へ変換して返します。</p>
	 * <p>この実装は以下の基準に従って変換を行います。</p>
	 * <ul>
	 * <li>オブジェクトが <code>null</code> の場合は、<code>null</code> を返します。</li>
	 * <li>オブジェクトが {@link Date} の場合は、オブジェクトのコピーを返します。</li>
	 * <li>オブジェクトが {@link Calendar} の場合は、{@link Calendar#getTime()} で得られた {@link Date} オブジェクトを返します。</li>
	 * <li>オブジェクトが {@link CharSequence} の場合は、{@link DateFormatUtils#parse(String, String[], Locale, java.util.TimeZone)} で得られた {@link Date} オブジェクトを返します。</li>
	 * <li>それ以外の場合は、オブジェクトを任意精度の符号付き数値として解析した上で long 値へ変換し、{@link Date#Date(long)} で得られた {@link Date} オブジェクトを返します。</li>
	 * </ul>
	 * 
	 * @param obj 変換するオブジェクト
	 * @param cfg 日時書式文字列情報
	 * @return 変換された日時。または <code>null</code>
	 * @throws IllegalArgumentException オブジェクトが {@link CharSequence} で日時書式文字列情報が <code>null</code> の場合
	 * @throws DataConvertException オブジェクトを解析できない場合
	 */
	public static Date toDate(final Object obj, final DateFormatConfig cfg) throws IllegalArgumentException, DataConvertException {
		if (obj == null)
			return null;

		if (obj instanceof Date) {
			return (Date) ((Date) obj).clone();
		} else if (obj instanceof Calendar) {
			return ((Calendar) obj).getTime();
		} else if (obj instanceof Number) {
			return toDate((Number) obj);
		} else if (obj instanceof CharSequence) {
			Assert.notNull(cfg, "DateFormatConfig must not be null");
			try {
				return DateFormatUtils.parse(obj.toString(), new String[]{ cfg.getPattern() }, cfg.getLocale(), cfg.getTimeZone());
			} catch (ParseException e) {
				throw new DataConvertException(e.getMessage(), e);
			}
		} else {
			try {
				return new Date(new BigDecimal(obj.toString()).longValue());
			} catch (NumberFormatException e) {
				throw new DataConvertException(e.getMessage(), e);
			}
		}
	}

	public static Date toDate(final Number number) throws IllegalArgumentException, DataConvertException {
		return toDate(number, Date.class);
	}

	public static <T extends Date> T toDate(final Number number, final Class<T> dateClass) throws IllegalArgumentException, DataConvertException {
		try {
			final Constructor<T> constructor = dateClass.getConstructor(long.class);
			return constructor.newInstance(number.longValue());
		} catch (Exception e) {
			throw new DataConvertException(e.getMessage(), e);
		}
	}

	// ------------------------------------------------------------------------
	// toNumber

	/**
	 * <p>指定されたオブジェクトを数値へ変換して返します。</p>
	 * 
	 * @param obj 変換するオブジェクト
	 * @return 変換された数値。または <code>null</code>
	 * @throws IllegalArgumentException オブジェクトが {@link CharSequence} の場合
	 * @throws DataConvertException オブジェクトを解析できない場合
	 * @see #toNumber(Object, NumberFormatConfig)
	 */
	public static Number toNumber(final Object obj) throws IllegalArgumentException, DataConvertException {
		return toNumber(obj, null);
	}

	/**
	 * <p>指定されたオブジェクトを数値へ変換して返します。</p>
	 * <p>この実装は以下の基準に従って変換を行います。</p>
	 * <ul>
	 * <li>オブジェクトが <code>null</code> の場合は、<code>null</code> を返します。</li>
	 * <li>オブジェクトが {@link Date} の場合は、{@link Date#getTime()} で得られた値を返します。</li>
	 * <li>オブジェクトが {@link Calendar} の場合は、{@link Calendar#getTimeInMillis()} で得られた値を返します。</li>
	 * <li>オブジェクトが {@link CharSequence} の場合は、{@link NumberFormatUtils#parse(String, String[], Locale, String[])} で得られた {@link Number} オブジェクトを返します。</li>
	 * <li>それ以外の場合は、オブジェクトを任意精度の符号付き数値として解析した値を返します。</li>
	 * </ul>
	 * 
	 * @param obj 変換するオブジェクト
	 * @param cfg 数値/通貨書式文字列情報
	 * @return 変換された数値。または <code>null</code>
	 * @throws IllegalArgumentException オブジェクトが {@link CharSequence} で数値/通貨書式文字列情報が <code>null</code> の場合
	 * @throws DataConvertException オブジェクトを解析できない場合
	 */
	public static Number toNumber(final Object obj, final NumberFormatConfig cfg) throws IllegalArgumentException, DataConvertException {
		if (obj == null)
			return null;

		if (obj instanceof Date) {
			return ((Date) obj).getTime();
		} else if (obj instanceof Calendar) {
			return ((Calendar) obj).getTimeInMillis();
		} else if (obj instanceof CharSequence) {
			Assert.notNull(cfg, "NumberFormatConfig must not be null");
			try {
				return NumberFormatUtils.parse(
						obj.toString(), new String[]{ cfg.getPattern() }, cfg.getLocale(),
						cfg.getCurrencyCode() != null && !cfg.getCurrencyCode().isEmpty() ? new String[]{ cfg.getCurrencyCode() } : null
					);
			} catch (ParseException e) {
				throw new DataConvertException(e.getMessage(), e);
			}
		} else {
			try {
				return new BigDecimal(obj.toString());
			} catch (NumberFormatException e) {
				throw new DataConvertException(e.getMessage(), e);
			}
		}
	}

	// ------------------------------------------------------------------------
	// toDateArray

/*
	public static Date[] toDateArray(final Object obj, final DateFormatConfig cfg) throws IllegalArgumentException, DataConvertException {
		if (obj == null)
			return null;
		if (obj instanceof Object[])
			return toDateArray((Object[]) obj, cfg);
		if (obj instanceof Collection)
			return toDateArray((Collection<?>) obj, cfg);
		throw new IllegalArgumentException("Cannot convert value of type required type Collection or Array.");
	}
*/

	/**
	 * <p>指定されたデータをすべて日時へ変換して返します。</p>
	 * 
	 * @param c データ
	 * @return 変換された日時データ。または <code>null</code>
	 * @throws IllegalArgumentException 要素が {@link CharSequence} の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toDateArray(Collection, DateFormatConfig)
	 */
	public static Date[] toDateArray(final Collection<?> c) throws IllegalArgumentException, DataConvertException {
		return toDateArray(c, null);
	}

	/**
	 * <p>指定されたデータをすべて日時へ変換して返します。</p>
	 * <p>
	 * データに <code>null</code> が指定された場合は、<code>null</code> を返します。<br />
	 * それ以外の場合はデータを {@link Collection#toArray()} で配列へ変換して {@link #toDateArray(Object[], DateFormatConfig)} を呼出すだけです。
	 * </p>
	 * 
	 * @param c データ
	 * @param cfg 日時書式文字列情報
	 * @return 変換された日時データ。または <code>null</code>
	 * @throws IllegalArgumentException 要素が {@link CharSequence} で日時書式文字列情報が <code>null</code> の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toDateArray(Object[], DateFormatConfig)
	 */
	public static Date[] toDateArray(final Collection<?> c, final DateFormatConfig cfg) throws IllegalArgumentException, DataConvertException {
		if (c == null)
			return null;
		return toDateArray(c.toArray(), cfg);
	}

	/**
	 * <p>指定されたデータをすべて日時へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param array データ
	 * @return 変換された日時データ。または <code>null</code>
	 * @throws IllegalArgumentException 要素が {@link CharSequence} の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toDateArray(Object[], DateFormatConfig)
	 */
	public static Date[] toDateArray(final Object[] array) throws IllegalArgumentException, DataConvertException {
		return toDateArray(array, null);
	}

	/**
	 * <p>指定されたデータをすべて日時へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param array データ
	 * @param cfg 日時書式文字列情報
	 * @return 変換された日時データ。または <code>null</code>
	 * @throws IllegalArgumentException 要素が {@link CharSequence} で日時書式文字列情報が <code>null</code> の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toDate(Object, DateFormatConfig)
	 */
	public static Date[] toDateArray(final Object[] array, final DateFormatConfig cfg) throws IllegalArgumentException, DataConvertException {
		if (array == null)
			return null;

		final int size = array.length;
		final Date[] results = new Date[size];
		for (int i = 0; i < size; i++) {
			results[i] = toDate(array[i], cfg);
		}
		return results;
	}

	// ------------------------------------------------------------------------
	// toNumberArray

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArray(final int[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++)
			results[i] = data[i];
		return results;
	}

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArray(final long[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++)
			results[i] = data[i];
		return results;
	}

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArray(final float[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++)
			results[i] = data[i];
		return results;
	}

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArray(final double[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++)
			results[i] = data[i];
		return results;
	}

	// ------------------------------------------------------------------------

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param c データ
	 * @return 変換された数値データ。または <code>null</code>
	 * @throws IllegalArgumentException データのコンポーネント型が {@link CharSequence} の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toNumberArray(Collection, NumberFormatConfig)
	 */
	public static Number[] toNumberArray(final Collection<?> c) throws IllegalArgumentException, DataConvertException {
		return toNumberArray(c, null);
	}

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param c データ
	 * @param cfg 数値/通貨書式文字列情報
	 * @return 変換された数値データ。または <code>null</code>
	 * @throws IllegalArgumentException データのコンポーネント型が {@link CharSequence} で数値/通貨書式文字列情報が <code>null</code> の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toNumberArray(Object[], NumberFormatConfig)
	 */
	public static Number[] toNumberArray(final Collection<?> c, final NumberFormatConfig cfg) throws IllegalArgumentException, DataConvertException {
		if (c == null)
			return null;
		return toNumberArray(c.toArray(), cfg);
	}

	// ------------------------------------------------------------------------

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 * @throws IllegalArgumentException データのコンポーネント型が {@link CharSequence} の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toNumberArray(Object[], NumberFormatConfig)
	 */
	public static Number[] toNumberArray(final Object[] data) throws IllegalArgumentException, DataConvertException {
		return toNumberArray(data, null);
	}

	/**
	 * <p>指定されたデータをすべて数値へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @param cfg 数値/通貨書式文字列情報
	 * @return 変換された数値データ。または <code>null</code>
	 * @throws IllegalArgumentException データのコンポーネント型が {@link CharSequence} で数値/通貨書式文字列情報が <code>null</code> の場合
	 * @throws DataConvertException 要素を解析できない場合
	 * @see #toNumber(Object, NumberFormatConfig)
	 */
	public static Number[] toNumberArray(final Object[] data, final NumberFormatConfig cfg) throws IllegalArgumentException, DataConvertException {
		if (data == null)
			return null;

		final int size = data.length;
		final Number[] results = new Number[size];
		for (int i = 0; i < size; i++) {
			results[i] = toNumber(data[i], cfg);
		}
		return results;
	}

	// ------------------------------------------------------------------------
	// toNumberArrayWithNullIfZero

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を <code>null</code> として変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithNullIfZero(final int[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++) {
			if (data[i] != 0)
				results[i] = data[i];
		}
		return results;
	}

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を <code>null</code> として変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithNullIfZero(final long[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++) {
			if (data[i] != 0)
				results[i] = data[i];
		}
		return results;
	}

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を <code>null</code> として変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithNullIfZero(final float[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++) {
			if (data[i] != 0)
				results[i] = data[i];
		}
		return results;
	}

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を <code>null</code> として変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithNullIfZero(final double[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++) {
			if (data[i] != 0)
				results[i] = data[i];
		}
		return results;
	}

	// ------------------------------------------------------------------------
	// toNumberArrayWithPreviousIfZero

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を一つ前の値で置き換えて変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithPreviousIfZero(final int[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		Number last = null;
		for (int i = 0; i < length; i++) {
			if (data[i] != 0)
				last = data[i];
			results[i] = last;
		}
		return results;
	}

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を一つ前の値で置き換えて変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithPreviousIfZero(final long[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		Number last = null;
		for (int i = 0; i < length; i++) {
			if (data[i] != 0L)
				last = data[i];
			results[i] = last;
		}
		return results;
	}

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を一つ前の値で置き換えて変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithPreviousIfZero(final float[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		Number last = null;
		for (int i = 0; i < length; i++) {
			if (data[i] != 0F)
				last = data[i];
			results[i] = last;
		}
		return results;
	}

	/**
	 * <p>指定されたデータの値が <code>0</code> の物を一つ前の値で置き換えて変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] toNumberArrayWithPreviousIfZero(final double[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		Number last = null;
		for (int i = 0; i < length; i++) {
			if (data[i] != 0D)
				last = data[i];
			results[i] = last;
		}
		return results;
	}

	// ------------------------------------------------------------------------
	// zeroIfNull

	/**
	 * <p>指定されたデータの値が <code>null</code> の物を <code>0</code> として変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] zeroIfNull(final Number[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		for (int i = 0; i < length; i++) {
			if (data[i] == null)
				results[i] = 0;
			else
				results[i] = data[i].doubleValue();
		}
		return results;
	}

	// ------------------------------------------------------------------------
	// previousIfNull

	/**
	 * <p>指定されたデータの値が <code>null</code> の物を一つ前の値で置き換えて変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data データ
	 * @return 変換された数値データ。または <code>null</code>
	 */
	public static Number[] previousIfNull(final Number[] data) {
		if (data == null)
			return null;

		final int length = data.length;
		final Number[] results = new Number[length];
		Number last = null;
		for (int i = 0; i < length; i++) {
			if (data[i] != null)
				last = data[i].doubleValue();
			results[i] = last;
		}
		return results;
	}

	// ------------------------------------------------------------------------
	// toSortedMap

	/**
	 * <p>指定された日時データの配列と値データの配列を日時をキーとして自然順序付けされたマップへ変換して返します。</p>
	 * <p>日時データの配列と値データの配列の長さが異なる場合、返されるマップのマッピング数は長さが短い配列と同じになります。</p>
	 * 
	 * @param date 日時データの配列
	 * @param data 値データの配列
	 * @return 日時をキーとして自然順序付けされたマップ
	 * @throws IllegalArgumentException 日時データの配列に <code>null</code> が指定された場合
	 */
	public static <T> SortedMap<Date, T> toSortedMap(final Date[] date, final T[] data) {
		// 入力パラメータを検証します。
		Assert.notNull(date, "Date must not be null.");

		final SortedMap<Date, T> results = new TreeMap<Date, T>();
		if (data == null) {
			final int length = date.length;
			for (int i = 0; i < length; i++)
				results.put(date[i], null);
		} else {
			final int length = Math.min(date.length, data.length);
			for (int i = 0; i < length; i++)
				results.put(date[i], data[i]);
		}
		return results;
	}

	// ------------------------------------------------------------------------
	// merge

	/**
	 * デフォルトの日時の突合せ方法です。
	 */
	public static final MergeMatchType DEFAULT_MERGE_MATCH_TYPE = MergeMatchType.CURRENT;

	/**
	 * <p>指定された基準日時を使用して指定されたデータをマージして返します。</p>
	 * <p>
	 * このメソッドは利便性の為に提供しています。<br />
	 * 実装は単に、デフォルトの日時の突合せ方法で {@link #merge(Date[], Date[], Object[], MergeMatchType, MergeGapFillType, Object)} を呼出すだけです。
	 * </p>
	 * 
	 * @param base 基準日時の配列
	 * @param date 日時データの配列
	 * @param data 値データの配列
	 * @return マージされたデータ
	 * @throws IllegalArgumentException 基準日時の配列や日時データの配列に <code>null</code> が指定された場合
	 * @see #merge(Date[], Date[], Object[], MergeMatchType, MergeGapFillType, Object)
	 */
	public static <T> SortedMap<Date, T> merge(final Date[] base, final Date[] date, final T[] data) {
		return merge(base, date, data, DEFAULT_MERGE_MATCH_TYPE, null, null);
	}

	/**
	 * <p>指定された基準日時を使用して指定されたデータをマージして返します。</p>
	 * 
	 * @param base 基準日時の配列
	 * @param date 日時データの配列
	 * @param data 値データの配列
	 * @param matchType 日時の突合せ方法
	 * @param fillType 隙間調整の種類
	 * @param fill 隙間調整値
	 * @return マージされたデータ
	 * @throws IllegalArgumentException 基準日時の配列や日時データの配列、日時の突合せ方法に <code>null</code> が指定された場合
	 * @see #merge(Collection, SortedMap, MergeMatchType, MergeGapFillType, Object)
	 */
	public static <T> SortedMap<Date, T> merge(final Date[] base, final Date[] date, final T[] data, final MergeMatchType matchType, final MergeGapFillType fillType, final T fill) {
		Assert.notNull(base, "Base date must not be null");
		return merge(new TreeSet<Date>(Arrays.asList(base)), toSortedMap(date, data), matchType, fillType, fill);
	}

	/**
	 * <p>指定された基準日時を使用して指定されたデータをマージして返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * <p>
	 * このメソッドは利便性の為に提供しています。<br />
	 * 実装は単に、デフォルトの日時の突合せ方法で {@link #merge(Collection, SortedMap, MergeMatchType, MergeGapFillType, Object)} を呼出すだけです。
	 * </p>
	 * 
	 * @param base 重複要素のない自然順序付けされた基準日時のコレクション
	 * @param data 日時をキーとして自然順序付けされたデータのマップ
	 * @return マージされたデータ。または <code>null</code>
	 * @throws IllegalArgumentException 基準日時のコレクションに <code>null</code> が指定された場合
	 * @see #merge(Collection, SortedMap, MergeMatchType, MergeGapFillType, Object)
	 */
	public static <T> SortedMap<Date, T> merge(final Collection<Date> base, final SortedMap<Date, T> data) {
		return merge(base, data, DEFAULT_MERGE_MATCH_TYPE, null, null);
	}

	/**
	 * <p>指定された基準日時を使用して指定されたデータをマージして返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param base 重複要素のない自然順序付けされた基準日時のコレクション
	 * @param data 日時をキーとして自然順序付けされたデータのマップ
	 * @param matchType 日時の突合せ方法
	 * @param fillType 隙間調整の種類
	 * @param fill 隙間調整値
	 * @return マージされたデータ。または <code>null</code>
	 * @throws IllegalArgumentException 基準日時のコレクションや日時の突合せ方法に <code>null</code> が指定された場合
	 */
	public static <T> SortedMap<Date, T> merge(final Collection<Date> base, final SortedMap<Date, T> data, final MergeMatchType matchType, final MergeGapFillType fillType, final T fill) {
		Assert.notNull(base, "Base date must not be null");

		final Map<Date, T> map = new HashMap<Date, T>();
		for (final Date _date : base)
			map.put(_date, null);

		return merge(map, data, matchType, fillType, fill);
	}

	/**
	 * <p>指定された基準日時を使用して指定されたデータをマージして返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param base 基準日時をキーとしたデータのマップ
	 * @param data 重複要素のない自然順序付けされた日時をキーとしたデータのマップ
	 * @param matchType 日時の突合せ方法
	 * @param fillType 隙間調整の種類
	 * @param fill 隙間調整値
	 * @return マージされたデータ。または <code>null</code>
	 * @throws IllegalArgumentException 基準日時のコレクションや日時の突合せ方法に <code>null</code> が指定された場合
	 */
	public static <T> SortedMap<Date, T> merge(final Map<Date, T> base, final SortedMap<Date, T> data, final MergeMatchType matchType, final MergeGapFillType fillType, final T fill) {
		// 入力パラメータを検証します。
		Assert.notNull(base, "Base date must not be null");
		Assert.notNull(matchType, "MergeType must not be null");

		if (data == null)
			return null;

		// 結果の雛形を構築します。
		final TreeMap<Date, T> results = new TreeMap<Date, T>(base);

		// マージ(マッチング)処理を行います。
		for (final Map.Entry<Date, T> _data : data.entrySet()) {
			final Date _date = _data.getKey();
			if (_date == null)
				continue;

			// マスタから該当日を見つけ処理します。
			Date match = null;
			if (matchType == MergeMatchType.INSERT || results.containsKey(_date)) {
				results.put(_date, _data.getValue());
				match = _date;
			} else if (matchType == MergeMatchType.CURRENT) {
				final Map.Entry<Date, T> result = results.floorEntry(_date);
				if (result != null) {
					results.put(result.getKey(), _data.getValue());
					match = result.getKey();
				}
			} else if (matchType == MergeMatchType.NEXT) {
				final Map.Entry<Date, T> result = results.ceilingEntry(_date);
				if (result != null) {
					results.put(result.getKey(), _data.getValue());
					match = result.getKey();
				}
			}
			if (fillType != null && match != null) {
				switch (fillType) {
					case PREVIOUS:
						// 後続期間を前の値で埋めます。
						final T prev = results.get(match);
						for (final Map.Entry<Date, T> result : results.tailMap(match, false).entrySet()) {
							result.setValue(prev);
						}
						break;
					case PARAMETER:
						for (final Map.Entry<Date, T> result : results.tailMap(match, false).entrySet()) {
							result.setValue(fill);
						}
						break;
				}
			}
		}

		return results;
	}

	// ------------------------------------------------------------------------
	// compress

	/**
	 * <p>指定されたデータを指定された日時精度単位へ変換して返します。</p>
	 * <p>
	 * このメソッドは利便性の為に提供しています。<br />
	 * 実装はデータを {@link #toSortedMap(Date[], Object[])} で変換して {@link #compress(SortedMap, CompressType, int, Calendar, DateTruncater)} を呼出すだけです。
	 * </p>
	 * 
	 * @param date 日時データの配列
	 * @param data 値データの配列
	 * @param compressType 日時精度単位変換方法の種類
	 * @param truncateUnit 変換する日時精度単位
	 * @return 変換されたデータ
	 * @throws IllegalArgumentException パラメータに <code>null</code> が指定された場合。
	 * @see #toSortedMap(Date[], Object[])
	 * @see #compress(SortedMap, CompressType, int, Calendar, DateTruncater)
	 */
	public static SortedMap<Date, Number> compress(final Date[] date, final Number[] data, final CompressType compressType, final int truncateUnit) {
		return compress(toSortedMap(date, data), compressType, truncateUnit, Calendar.getInstance(), new DateTruncater());
	}

	/**
	 * <p>指定されたデータを指定された日時精度単位へ変換して返します。</p>
	 * <p>
	 * このメソッドは利便性の為に提供しています。<br />
	 * 実装はデータを {@link #toSortedMap(Date[], Object[])} で変換して {@link #compress(SortedMap, CompressType, int, Calendar, DateTruncater)} を呼出すだけです。
	 * </p>
	 * 
	 * @param date 日時データの配列
	 * @param data 値データの配列
	 * @param compressType 日時精度単位変換方法の種類
	 * @param truncateUnit 変換する日時精度単位
	 * @param calendar 日時の基準とするカレンダーオブジェクト
	 * @return 変換されたデータ
	 * @throws IllegalArgumentException パラメータに <code>null</code> が指定された場合。
	 * @see #toSortedMap(Date[], Object[])
	 * @see #compress(SortedMap, CompressType, int, Calendar, DateTruncater)
	 */
	public static SortedMap<Date, Number> compress(final Date[] date, final Number[] data, final CompressType compressType, final int truncateUnit, final Calendar calendar) {
		return compress(toSortedMap(date, data), compressType, truncateUnit, calendar, new DateTruncater());
	}

	/**
	 * <p>指定されたデータを指定された日時精度単位へ変換して返します。</p>
	 * <p>
	 * このメソッドは利便性の為に提供しています。<br />
	 * 実装はデータを {@link #toSortedMap(Date[], Object[])} で変換して {@link #compress(SortedMap, CompressType, int, Calendar, DateTruncater)} を呼出すだけです。
	 * </p>
	 * 
	 * @param date 日時データの配列
	 * @param data 値データの配列
	 * @param compressType 日時精度単位変換方法の種類
	 * @param truncateUnit 変換する日時精度単位
	 * @param calendar 日時の基準とするカレンダーオブジェクト
	 * @param truncater 日時切捨て用オブジェクト
	 * @return 変換されたデータ
	 * @throws IllegalArgumentException パラメータに <code>null</code> が指定された場合。
	 * @see #toSortedMap(Date[], Object[])
	 * @see #compress(SortedMap, CompressType, int, Calendar, DateTruncater)
	 */
	public static SortedMap<Date, Number> compress(final Date[] date, final Number[] data, final CompressType compressType, final int truncateUnit, final Calendar calendar, final DateTruncater truncater) {
		return compress(toSortedMap(date, data), compressType, truncateUnit, calendar, truncater);
	}

	/**
	 * <p>指定されたデータを指定された日時精度単位へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * <p>
	 * このメソッドは利便性の為に提供しています。<br />
	 * 実装は単に、{@link #compress(SortedMap, CompressType, int, Calendar, DateTruncater)} を呼出すだけです。
	 * </p>
	 * 
	 * @param data 日時をキーとして自然順序付けされたデータのマップ
	 * @param compressType 日時精度単位変換方法の種類
	 * @param truncateUnit 変換する日時精度単位
	 * @return 変換されたデータ。または <code>null</code>
	 * @throws IllegalArgumentException パラメータに <code>null</code> が指定された場合。
	 * @see #compress(SortedMap, CompressType, int, Calendar, DateTruncater)
	 */
	public static SortedMap<Date, Number> compress(final SortedMap<Date, Number> data, final CompressType compressType, final int truncateUnit) {
		return compress(data, compressType, truncateUnit, Calendar.getInstance(), new DateTruncater());
	}

	/**
	 * <p>指定されたデータを指定された日時精度単位へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * <p>
	 * このメソッドは利便性の為に提供しています。<br />
	 * 実装は単に、{@link #compress(SortedMap, CompressType, int, Calendar, DateTruncater)} を呼出すだけです。
	 * </p>
	 * 
	 * @param data 日時をキーとして自然順序付けされたデータのマップ
	 * @param compressType 日時精度単位変換方法の種類
	 * @param truncateUnit 変換する日時精度単位
	 * @param calendar 日時の基準とするカレンダーオブジェクト
	 * @return 変換されたデータ。または <code>null</code>
	 * @throws IllegalArgumentException パラメータに <code>null</code> が指定された場合。
	 * @see #compress(SortedMap, CompressType, int, Calendar, DateTruncater)
	 */
	public static SortedMap<Date, Number> compress(
		final SortedMap<Date, Number> data,
		final CompressType compressType,
		final int truncateUnit,
		final Calendar calendar)
	{
		return compress(data, compressType, truncateUnit, calendar, new DateTruncater());
	}

	/**
	 * <p>指定されたデータを指定された日時精度単位へ変換して返します。</p>
	 * <p>データに <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param data 日時をキーとして自然順序付けされたデータのマップ
	 * @param compressType 日時精度単位変換方法の種類
	 * @param truncateUnit 変換する日時精度単位
	 * @param calendar 日時の基準とするカレンダーオブジェクト
	 * @param truncater 日時切捨て用オブジェクト
	 * @return 変換されたデータ。または <code>null</code>
	 * @throws IllegalArgumentException パラメータに <code>null</code> が指定された場合。
	 */
	public static SortedMap<Date, Number> compress(
		final SortedMap<Date, Number> data,
		final CompressType compressType,
		final int truncateUnit,
		final Calendar calendar,
		final DateTruncater truncater)
	{
		// 入力パラメータを検証します。
		Assert.notNull(compressType, "CompressType must not be null");
		Assert.notNull(calendar, "Calendar must not be null");
		Assert.notNull(truncater, "DateTruncater must not be null");

		if (data == null)
			return null;

		final SortedMap<Date, Number> results = new TreeMap<Date, Number>();

		final Calendar _calendar = (Calendar) calendar.clone();
		for (final Map.Entry<Date, Number> _data : data.entrySet()) {
			final Date _date = _data.getKey();
			if (_date == null)
				continue;

			// 日時を切捨て変換単位と揃えます。
			_calendar.setTime(_date);
			final Date truncatedDate = truncater.truncate(_calendar, truncateUnit).getTime();
			final Number _value = _data.getValue();

			if (results.containsKey(truncatedDate)) {
				final Number value = results.get(truncatedDate);
				switch (compressType) {
					case FIRST:
						// 既に値があるので何もしない
						break;
					case HIGHEST:
						if (value == null || (_value != null && value.doubleValue() < _value.doubleValue()))
							results.put(truncatedDate, _value);
						break;
					case LOWEST:
						if (value == null || (_value != null && value.doubleValue() > _value.doubleValue()))
							results.put(truncatedDate, _value);
						break;
					case LAST:
						// 常に更新
						results.put(truncatedDate, _value);
						break;
					case SUM:
						// 加算する
						if (value != null || _value != null) {
							results.put(truncatedDate, (value == null ? 0D : value.doubleValue()) + (_value == null ? 0D : _value.doubleValue()));
						}
						break;
					default:
						throw new UnsupportedOperationException("Unknown CompressType [" + compressType.toString() + "]");
				}
			} else {
				results.put(truncatedDate, _value);
			}
		}

		return results;
	}

	// ------------------------------------------------------------------------
	// toString

	/**
	 * <p>指定された日時を文字列へ変換して返します。</p>
	 * <p>日時に <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param date 変換する日時
	 * @param cfg 日時書式文字列情報
	 * @return 変換された日時。または <code>null</code>
	 */
	public static String toString(final Date date, final DateFormatConfig cfg) {
		if (date == null)
			return null;
		if (cfg == null)
			return date.toString();
		return DateFormatUtils.format(date, cfg.getPattern(), cfg.getLocale(), cfg.getTimeZone());
	}

	/**
	 * <p>指定された数値を文字列へ変換して返します。</p>
	 * <p>数値に <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param number 変換する数値
	 * @param cfg 数値/通貨書式文字列情報
	 * @return 変換された数値。または <code>null</code>
	 */
	public static String toString(final Number number, final NumberFormatConfig cfg) {
		if (number == null)
			return null;
		if (cfg == null)
			return number.toString();
		return NumberFormatUtils.format(number, cfg.getPattern(), cfg.getLocale(), cfg.getCurrencyCode());
	}

	// ------------------------------------------------------------------------
	// toStringArray

	/**
	 * <p>指定された日時の配列をすべて文字列へ変換して返します。</p>
	 * <p>日時の配列に <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param array 変換する日時の配列
	 * @param cfg 日時書式文字列情報
	 * @return 変換された日時の配列。または <code>null</code>
	 */
	public static String[] toStringArray(final Date[] array, final DateFormatConfig cfg) {
		if (array == null)
			return null;
		final int size = array.length;
		final String[] results = new String[size];
		for (int i = 0; i < size; i++) {
			results[i] = toString(array[i], cfg);
		}
		return results;
	}

	/**
	 * <p>指定された数値の配列をすべて文字列へ変換して返します。</p>
	 * <p>数値の配列に <code>null</code> が指定された場合は、<code>null</code> を返します。</p>
	 * 
	 * @param array 変換する数値の配列
	 * @param cfg 数値/通貨書式文字列情報
	 * @return 変換された数値の配列。または <code>null</code>
	 */
	public static String[] toStringArray(final Number[] array, final NumberFormatConfig cfg) {
		if (array == null)
			return null;
		final int size = array.length;
		final String[] results = new String[size];
		for (int i = 0; i < size; i++) {
			results[i] = toString(array[i], cfg);
		}
		return results;
	}

}
