/*
 * TypedMap class.
 *
 * Copyright (C) 2011 SATOH Takayuki All Rights Reserved.
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package ts.util;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.DecimalFormat;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;

/**
 * キーに結びつけられた値オブジェクトを型変換して取得するためのメソッドを備えた
 * マップ・クラス。
 *
 * @author 佐藤隆之
 * @version 1.0, 2011/05/27.
 */
public class TypedMap<K,V> implements Map<K,V>, TypedGetter<K>
{
  /** キーと値オブジェクトのマッピングを格納する内部マップ。 */
  private final Map<K,V> innerMap = createInnerMap();

  /**
   * {@link #getBoolean(K)}メソッドにおいて<tt>true</tt>を返す場合の
   * 値オブジェクトを格納する集合オブジェクト。
   */
  private final Set<Object> trueSet;

  /** デフォルトの日時書式フォーマット。 */
  private DateFormat dateTimeFormat;

  /**
   * デフォルト・コンストラクタ。
   */
  public TypedMap()
  {
    this.trueSet = new HashSet<Object>();
    this.trueSet.add(Boolean.TRUE);
    this.trueSet.add(Boolean.TRUE.toString());

    this.dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  }

  /**
   * 内部マップを作成する。
   *
   * @return 内部マップ。
   */
  protected Map<K,V> createInnerMap()
  {
    return new HashMap<K,V>();
  }

  /**
   * このマップに格納されている要素の数を取得する。
   *
   * @return このマップに格納されている要素の数。
   */
  public int size()
  {
    return this.innerMap.size();
  }

  /**
   * このマップが空かどうかを確認する。
   *
   * @return このマップの要素数がゼロの場合は<tt>true</tt>を返す。
   */
  public boolean isEmpty()
  {
    return this.innerMap.isEmpty();
  }

  /**
   * 指定されたキーを持った要素がこのマップに存在するかどうかを確認する。
   * 
   * @param key キー。
   * @return 引数のキーがこのマップに含まれている場合は<tt>true</tt>を返す。
   */
  public boolean containsKey(Object key)
  {
    return this.innerMap.containsKey(key);
  }

  /**
   * 指定された値オブジェクトを持った要素がこのマップに存在するかどうかを確認
   * する。
   *
   * @param value 値オブジェクト。
   * @return 引数の値オブジェクトがこのマップに含まれている場合は<tt>true</tt>
   *         を返す。
   */
  public boolean containsValue(Object value)
  {
    return this.innerMap.containsValue(value);
  }

  /**
   * 指定されたキーに結びつけられている値オブジェクトを取得する。
   * <br>
   * 結びつけられた値オブジェクトが存在しない場合はヌルを返す。
   *
   * @param key キー。
   * @return 引数のキーに結びつけられている値オブジェクト。
   */
  public V get(Object key)
  {
    return this.innerMap.get(key);
  }

  /**
   * 指定されたキーと値オブジェクトの組をこのマップに登録する。
   *
   * @param key キー。
   * @param value 値オブジェクト。
   * @return 引数のキーに以前結びつけられていた値オブジェクト。
   */
  public V put(K key, V value)
  {
    return this.innerMap.put(key, value);
  }

  /**
   * 指定されたキーに結びつけられている値オブジェクトを削除する。
   *
   * @param key キー。
   * @return 引数のキーに以前結びつけらｒていた値オブジェクト。
   */
  public V remove(Object key)
  {
    return this.innerMap.remove(key);
  }

  /**
   * 指定されたマップの全ての要素をこのマップに追加する。 
   *
   * @param map 追加する要素を格納するマップ。
   */
  public void putAll(Map<? extends K, ? extends V> map)
  {
    this.innerMap.putAll(map);
  }

  /**
   * このマップを空にする。
   * <br>
   * このマップの全ての要素を、このマップから削除する。
   */
  public void clear()
  {
    this.innerMap.clear();
  }

  /**
   * このマップが格納しているキーの集合を取得する。
   *
   * @return このマップが格納しているキーの集合。
   */
  public Set<K> keySet()
  {
    return this.innerMap.keySet();
  }

  /**
   * このマップの全ての要素の値オブジェクトを格納したコレクション・オブジェクト
   * 取得する。
   *
   * @return このマップの全ての要素の値オブジェクトを格納したコレクション・
   *         オブジェクト。
   */
  public Collection<V> values()
  {
    return this.innerMap.values();
  }

  /**
   * このマップに格納されている全てのキーと値の組を、{@link Map.EntryMap}
   * オブジェクトの集合として取得する。
   *
   * @return このマップの全ての要素を格納した集合。
   */
  public Set<Map.Entry<K,V>> entrySet()
  {
    return this.innerMap.entrySet();
  }

  /**
   * 指定されたオブジェクトと内容が等しいかどうかを比較する。
   * <br>
   * 引数のオブジェクトがマップであり、このマップと引数のマップが同じマッピング
   * を持っている場合は<tt>true</tt>を返す。
   * より具体的には、このマップの{@link Map#entrySet()}メソッドの結果と引数の
   * マップの{@link Map#entrySet()}の結果が等しい場合に<tt>true</tt>を返す。
   *
   * @param obj 比較するオブジェクト。
   * @return 引数のオブジェクトがこのマップと内容が等しい場合に<tt>true</tt>。
   */
  public boolean equals(Object obj)
  {
    return this.innerMap.equals(obj);
  }

  /**
   * このマップに対するハッシュ・コードを取得する。
   *
   * @return このマップに対するハッシュ・コード。
   */
  public int hashCode()
  {
    return this.innerMap.hashCode();
  }

  /**
   * {@link #getBoolean(K)}メソッドにおいて、結果を<tt>true</tt>とする
   * オブジェクトを追加する。
   *
   * @param obj {@link #getBoolean(K)}メソッドにおいて、結果を<tt>true</tt>
   *            とするオブジェクト。
   */
  public void addTrueObject(Object obj)
  {
    this.trueSet.add(obj);
  }

  /**
   * {@link #getBoolean(K)}メソッドにおいて、結果を<tt>true</tt>とする
   * オブジェクトを削除する。
   *
   * @param obj 削除するオブジェクト。
   */
  public void removeTrueObject(Object obj)
  {
    this.trueSet.remove(obj);
  }

  /**
   * デフォルトの日時書式文字列を設定する。
   * <br>
   * このメソッドで設定した日時書式文字列は、{@link #getDateTime(K)}メソッド
   * において文字列値を日時オブジェクトに変換したり、{@link #getString(K)}
   * メソッドにおいて日時オブジェクトを文字列値に変換するために使用される。
   * <br>
   * なお、このオブジェクトをインスタンス化した直後は
   * <tt>"yyyy-MM-dd HH:mm:ss"</tt>がデフォルトの日時書式文字列として使用
   * される。
   * <br>
   * 引数がヌル又は日時書式として不正の場合はデフォルトの日時書式を更新しない。
   *
   * @param formatString 日時書式文字列。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public void setDateTimeFormat(String formatString)
  {
    assert (formatString != null) : "@param:formatString is null.";
    
    try {
      this.dateTimeFormat = new SimpleDateFormat(formatString);
    }
    catch (Exception e) {}
  }

  /**
   * デフォルトの日時書式オブジェクトを設定する。
   * <br>
   * このメソッドで設定した日時書式オブジェクトは、{@link #getDateTime(K)}
   * メソッドにおいて文字列値を日時オブジェクトに変換したり、{@link
   * #getString(K)}メソッドにおいて日時オブジェクトを文字列値に変換するために
   * 使用される。
   * <br>
   * なお、このオブジェクトをインスタンス化した直後は
   * <tt>"yyyy-MM-dd HH:mm:ss"</tt>がデフォルトの日時書式文字列として使用
   * される。
   * <br>
   * 引数がヌルの場合はデフォルトの日時書式を更新しない。
   *
   * @param format 日時書式オブジェクト。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public void setDateTimeFormat(DateFormat format)
  {
    assert (format != null) : "@param:format is null.";

    if (format == null) return;
    this.dateTimeFormat = format;
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>boolean</tt>に変換して
   * 取得する。
   *
   * @param key キー。
   * @return <tt>boolean</tt>型に変換された値。
   */
  public boolean getBoolean(K key)
  {
    return this.trueSet.contains(get(key));
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>int</tt>型に変換して 
   * 取得する。
   *
   * @param key キー。
   * @return <tt>int</tt>型に指定された値。
   */
  public int getInteger(K key)
  {
    Object v = get(key);
    if (v == null) {
      return 0;
    }
    else if (v instanceof Integer) {
      return Integer.class.cast(v).intValue();
    }
    else if (v instanceof Long) {
      return (int) limit(
        Long.class.cast(v).longValue(),
        (long) Integer.MIN_VALUE,
        (long) Integer.MAX_VALUE);
    }
    else if (v instanceof BigInteger) {
      return limit(
        new BigDecimal(BigInteger.class.cast(v)),
        new BigDecimal(Integer.MIN_VALUE),
        new BigDecimal(Integer.MAX_VALUE)).intValue();
    }
    else if (v instanceof BigDecimal) {
      return limit(
        BigDecimal.class.cast(v),
        new BigDecimal(Integer.MIN_VALUE),
        new BigDecimal(Integer.MAX_VALUE)).intValue();
    }
    else if (v instanceof Float) {
      return (int) limit(
        Float.class.cast(v),
        (float) Integer.MIN_VALUE,
        (float) Integer.MAX_VALUE);
    }
    else if (v instanceof Double) {
      return (int) limit(
        Double.class.cast(v),
        (double) Integer.MIN_VALUE,
        (double) Integer.MAX_VALUE);
    }
    else if (v instanceof Number) {
      return Number.class.cast(v).intValue();
    }
    else {
      try {
        return limit(
          new BigDecimal(v.toString(), MathContext.DECIMAL32),
          new BigDecimal(Integer.MIN_VALUE),
          new BigDecimal(Integer.MAX_VALUE)).intValue();
      }
      catch (Exception e) {
        return 0;
      }
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>long</tt>型に変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は<tt>0</tt>を返す。
   *
   * @param key キー。
   * @return <tt>long</tt>型に変換された値。
   */
  public long getLong(K key)
  {
    Object v = get(key);
    if (v == null) {
      return 0L;
    }
    else if (v instanceof Integer) {
      return Integer.class.cast(v).intValue();
    }
    else if (v instanceof Long) {
      return Long.class.cast(v).longValue();
    }
    else if (v instanceof BigInteger) {
      return limit(
        new BigDecimal(BigInteger.class.cast(v)),
        new BigDecimal(Long.MIN_VALUE),
        new BigDecimal(Long.MAX_VALUE)).longValue();
    }
    else if (v instanceof BigDecimal) {
      return limit(
        BigDecimal.class.cast(v),
        new BigDecimal(Long.MIN_VALUE),
        new BigDecimal(Long.MAX_VALUE)).longValue();
    }
    else if (v instanceof Float) {
      return (long) limit(
        Float.class.cast(v),
        (float) Long.MIN_VALUE,
        (float) Long.MAX_VALUE);
    }
    else if (v instanceof Double) {
      return (long) limit(
        Double.class.cast(v),
        (double) Long.MIN_VALUE,
        (double) Long.MAX_VALUE);
    }
    else if (v instanceof Number) {
      return Number.class.cast(v).longValue();
    }
    else {
      try {
        return limit(
          new BigDecimal(v.toString(), MathContext.DECIMAL64),
          new BigDecimal(Long.MIN_VALUE),
          new BigDecimal(Long.MAX_VALUE)).intValue();
      }
      catch (Exception e) {
        return 0L;
      }
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>float</tt>型に変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は<tt>0.0</tt>を返す。 
   *
   * @param key キー。
   * @return <tt>float</tt>型に変換された値。
   */
  public float getFloat(K key)
  {
    Object v = get(key);
    if (v == null) {
      return 0.0f;
    }
    else if (v instanceof Float) {
      return Float.class.cast(v).floatValue();
    }
    else if (v instanceof Double) {
      return new BigDecimal(Double.class.cast(v).doubleValue()).floatValue();
    }
    else if (v instanceof BigInteger) {
      return new BigDecimal(BigInteger.class.cast(v)).floatValue();
    }
    else if (v instanceof BigDecimal) {
      return BigDecimal.class.cast(v).floatValue();
    }
    else if (v instanceof Number) {
      return (float) Number.class.cast(v).longValue();
    }
    else {
      try {
        return limit(
          new BigDecimal(v.toString(), MathContext.UNLIMITED),
          new BigDecimal(-Float.MIN_VALUE),
          new BigDecimal( Float.MAX_VALUE)).floatValue();
      }
      catch (Exception e) {
        return 0.0f;
      }
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>double</tt>型に変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は<tt>0.0</tt>を返す。
   *
   * @param key キー。
   * @return <tt>double</tt>型に変換された値。
   */
  public double getDouble(K key)
  {
    Object v = get(key);
    if (v == null) {
      return 0.0;
    }
    else if (v instanceof Float) {
      return (double) Float.class.cast(v).floatValue();
    }
    else if (v instanceof Double) {
      return Double.class.cast(v).doubleValue();
    }
    else if (v instanceof BigInteger) {
      return new BigDecimal(BigInteger.class.cast(v)).doubleValue();
    }
    else if (v instanceof BigDecimal) {
      return BigDecimal.class.cast(v).doubleValue();
    }
    else if (v instanceof Number) {
      return (double) Number.class.cast(v).longValue();
    }
    else {
      try {
        return limit(
          new BigDecimal(v.toString(), MathContext.UNLIMITED),
          new BigDecimal(-Double.MAX_VALUE),
          new BigDecimal( Double.MAX_VALUE)).doubleValue();
      }
      catch (Exception e) {
        return 0.0;
      }
    }
  }
 
  /**
   * 指定されたキーに結びつけられた値オブジェクトを{@link BigDecimal}
   * オブジェクトに変換して取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は値が<tt>0</tt>の{@link
   * BigDecimal}オブジェクトを返す。
   *
   * @param key キー。
   * @return {@link BigDecimal}オブジェクト。
   */
  public BigDecimal getBigDecimal(K key)
  {
    Object v = get(key);
    if (v == null) {
      return new BigDecimal(0L);
    }
    else if (v instanceof Float) {
      return new BigDecimal(Float.class.cast(v).floatValue());
    }
    else if (v instanceof Double) {
      return new BigDecimal(Double.class.cast(v).doubleValue());
    }
    else if (v instanceof BigInteger) {
      return new BigDecimal(BigInteger.class.cast(v));
    }
    else if (v instanceof BigDecimal) {
      return BigDecimal.class.cast(v);
    }
    else if (v instanceof Number) {
      return new BigDecimal(Number.class.cast(v).longValue());
    }
    else {
      try {
        return new BigDecimal(v.toString(), MathContext.UNLIMITED);
      }
      catch (Exception e) {
        return new BigDecimal(0L);
      }
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを文字列オブジェクトに変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は空文字列を返す。
   *
   * @param key キー。
   * @return 文字列オブジェクトに変換された値オブジェクト。
   */
  public String getString(K key)
  {
    Object v = get(key);
    if (v == null) {
      return "";
    }
    else if (v instanceof DateTime) {
      return dateTimeFormat.format(DateTime.class.cast(v).getDate());
    }
    else if (v instanceof Date) {
      return dateTimeFormat.format(Date.class.cast(v));
    }
    else {
      return v.toString();
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを、指定された数値書式文字列を
   * 使って文字列オブジェクトに変換して取得する。
   * <br>
   * 数値書式文字列は、{@link DecimalFormat}に指定するパターンと同じものである。
   * <br>
   * 指定されたキーに結びつけられた値オブジェクトを数値に変換できなかった場合は
   * 空文字列を返す。
   *
   * @param key キー。
   * @param format 数値書式文字列。
   * @return 文字列オブジェクトに変換された値オブジェクト。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public String getNumberString(K key, String format)
  {
    assert (format != null) : "@param:format is null.";

    try {
      Object v = get(key);
      if (v == null) {
        return "";
      }
      else if (v instanceof Float) {
        double d = Float.class.cast(v).doubleValue();
        return new DecimalFormat(format).format(d);
      }
      else if (v instanceof Double) {
        double d = Double.class.cast(v).doubleValue();
        return new DecimalFormat(format).format(d);
      }
      else if (v instanceof Number) {
        return new DecimalFormat(format).format(Number.class.cast(v));
      }
      else {
        return new DecimalFormat(format).format(new BigDecimal(v.toString()));
      }
    }
    catch (Exception e) {
      return "";
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを、指定された日時書式文字列を
   * 使って文字列オブジェクトに変換して取得する。
   * <br>
   * 日時書式文字列は、{@link SimpleDateFormat}に指定するパターンと同じもので
   * ある。
   * <br>
   * 指定されたキーに結びつけられた値オブジェクトを日時に変換できなかった場合は
   * 空文字列を返す。
   *
   * @param key キー。
   * @param format 日時書式文字列。 
   * @return 文字列オブジェクトに変換された値オブジェクト。
   */
  public String getDateTimeString(K key, String format)
  {
    assert (format != null) : "@param:format is null.";

    try {
      Object v = get(key);
      if (v == null) {
        return "";
      }
      else if (v instanceof DateTime) {
        Date d = DateTime.class.cast(v).getDate();
        return new SimpleDateFormat(format).format(d);
      }
      else if (v instanceof Date) {
        Date d = Date.class.cast(v);
        return new SimpleDateFormat(format).format(d);
      }
      else {
        Date d = dateTimeFormat.parse(v.toString());
        return new SimpleDateFormat(format).format(d);
      }
    }
    catch (Exception e) {
      return "";
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを日時オブジェクトに変換して
   * 取得する。
   * <br>
   * 指定されたキーに結びつけられた値オブジェクトを日時に変換できなかった場合は
   * 1970年1月1日0時0分0秒を表す{@link DateTime}オブジェクトを返す。
   *
   * @param key キー。
   * @return 日時オブジェクトに変換された値オブジェクト。
   */
  public DateTime getDateTime(K key)
  {
    Object v = get(key);
    if (v == null) {
      return new DateTime(0);
    }
    else if (v instanceof DateTime) {
      return DateTime.class.cast(v);
    }
    else if (v instanceof Date) {
      return new DateTime(Date.class.cast(v));
    }
    else {
      try {
        return new DateTime(dateTimeFormat.parse(v.toString()));
      }
      catch (Exception e) {
        return new DateTime(0);
      }
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトをリスト・オブジェクトに変換
   * して取得する。
   * <br>
   * 結びつけられている値オブジェクトが単値の場合は、その値オブジェクトを唯一の 
   * 要素とするリスト・オブジェクトを作成して返す。
   * <br>
   * 指定されたキーに結びつけられた値オブジェクトが存在しないかヌルの場合は
   * 空のリストを返す。
   * 
   * @param key キー。
   * @return {@link List}オブジェクトに変換された値オブジェクト。
   */
  @SuppressWarnings("unchecked")
  public List<? extends Object> getList(K key)
  {
    Object v = get(key);
    if (v == null) {
      return new ArrayList(0);
    }
    else if (v instanceof List) {
      return (List<? extends Object>) v;
    }
    else if (v instanceof Collection) {
      return new ArrayList<Object>(Collection.class.cast(v));
    }
    else if (v instanceof Object[]) {
      Object[] arr = Object[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (Object o : arr) {
        lst.add(o);
      }
      return lst;
    }
    else if (v instanceof boolean[]) {
      boolean[] arr = boolean[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (boolean b : arr) {
        lst.add(b ? Boolean.TRUE : Boolean.FALSE);
      }
      return lst;
    }
    else if (v instanceof int[]) {
      int[] arr = int[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (int i : arr) {
        lst.add(new Integer(i));
      }
      return lst;
    }
    else if (v instanceof long[]) {
      long[] arr = long[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (long l : arr) {
        lst.add(new Long(l));
      }
      return lst;
    }
    else if (v instanceof float[]) {
      float[] arr = float[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (float f : arr) {
        lst.add(new Float(f));
      }
      return lst;
    }
    else if (v instanceof double[]) {
      double[] arr = double[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (double d : arr) {
        lst.add(new Double(d));
      }
      return lst;
    }
    else if (v instanceof short[]) {
      short[] arr = short[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (short s : arr) {
        lst.add(new Short(s));
      }
      return lst;
    }
    else if (v instanceof byte[]) {
      byte[] arr = byte[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (byte b : arr) {
        lst.add(new Byte(b));
      }
      return lst;
    }
    else if (v instanceof char[]) {
      char[] arr = char[].class.cast(v);
      List lst = new ArrayList(arr.length);
      for (char c : arr) {
        lst.add(new Character(c));
      }
      return lst;
    }
    else {
      List lst = new ArrayList(1);
      lst.add(v);
      return lst;
    }
  }


  /**
   * <tt>long</tt>値を指定した最大値と最小値でリミットする。
   *
   * @param val リミットされる<tt>long</tt>値。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の<tt>long</tt>値。
   */
  protected long limit(long val, long min, long max)
  {
    return Math.max(min, Math.min(max, val));
  }

  /**
   * <tt>float</tt>値を指定した最大値と最小値でリミットする。
   *
   * @param val リミットされる<tt>float</tt>値。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の<tt>float</tt>値。
   */
  protected float limit(float val, float min, float max)
  {
    return Math.max(min, Math.min(max, val));
  }

  /**
   * <tt>double</tt>値を指定した最大値と最小値でリミットする。
   *
   * @param val リミットされる<tt>double</tt>値。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の<tt>double</tt>値。
   */
  protected double limit(double val, double min, double max)
  {
    return Math.max(min, Math.min(max, val));
  }

  /**
   * {@link BigDecimal}オブジェクトを指定した最大値と最小値でリミットする。
   *
   * @param val リミットされる{@link BigDecimal}オブジェクト。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の{@link BigDecimal}オブジェクト。
   */
  protected BigDecimal limit(BigDecimal val, BigDecimal min, BigDecimal max)
  {
    if (val.compareTo(min) < 0) {
      return min;
    }
    else if (val.compareTo(max) > 0) {
      return max;
    }
    else {
      return val;
    }
  }
}
