/*
 * AbstractTypedGetter 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 ts.util.text.StringSequence;
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 $Id: AbstractTypedGetter.java,v 1.5 2011-11-09 16:15:43 tayu Exp $
 */
public abstract class AbstractTypedGetter<K,V> implements TypedGetter<K>
{
  /** このクラスで発生しうる例外の列挙型。 */
  public enum Error {
    /** 数値の型キャストに失敗した場合。 */
    FailToCastNumber,

    /** 日時の型キャストに失敗した場合。 */
    FailToCastDateTime,
  }

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

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

  /**
   * デフォルト・コンストラクタ。
   */
  public AbstractTypedGetter()
  {
    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");
  }

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

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

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

  /**
   * デフォルトの日時書式文字列を設定する。
   * <br>
   * このメソッドで設定した日時書式文字列は、{@link #getDateTime(Object)}
   * メソッドにおいて文字列値を日時オブジェクトに変換したり、{@link
   * #getString(Object)}メソッドにおいて日時オブジェクトを文字列値に変換する
   * ために使用される。
   * <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(Object)}
   * メソッドにおいて文字列値を日時オブジェクトに変換したり、{@link
   * #getString(Object)}メソッドにおいて日時オブジェクトを文字列値に変換する
   * ために使用される。
   * <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>型に変換された値。
   */
  @Override
  public boolean getBoolean(K key)
  {
    return this.trueSet.contains(get(key));
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>int</tt>型に変換して 
   * 取得する。
   *
   * @param key キー。
   * @return <tt>int</tt>型に指定された値。
   * @throws ReasonedRuntimeException <tt>int</tt>型への変換に失敗した場合。
   */
  @Override
  public int getInteger(K key) throws ReasonedRuntimeException
  {
    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 {
      return limit(
        toBigDecimal(v.toString()),
        new BigDecimal(Integer.MIN_VALUE),
        new BigDecimal(Integer.MAX_VALUE)).intValue();
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>long</tt>型に変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は<tt>0</tt>を返す。
   *
   * @param key キー。
   * @return <tt>long</tt>型に変換された値。
   * @throws ReasonedRuntimeException <tt>long</tt>型への変換に失敗した場合。
   */
  @Override
  public long getLong(K key)
  {
    Object v = get(key);
    if (v == null) {
      return 0L;
    }
    else if (v instanceof Integer) {
      return (long) 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 {
      return limit(
        toBigDecimal(v.toString()),
        new BigDecimal(Long.MIN_VALUE),
        new BigDecimal(Long.MAX_VALUE)).longValue();
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>float</tt>型に変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は<tt>0.0</tt>を返す。 
   *
   * @param key キー。
   * @return <tt>float</tt>型に変換された値。
   * @throws ReasonedRuntimeException <tt>float</tt>型への変換に失敗した場合。
   */
  @Override
  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 {
      return limit(
        toBigDecimal(v.toString()),
        new BigDecimal(-Float.MAX_VALUE),
        new BigDecimal( Float.MAX_VALUE)).floatValue();
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを<tt>double</tt>型に変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は<tt>0.0</tt>を返す。
   *
   * @param key キー。
   * @return <tt>double</tt>型に変換された値。
   * @throws ReasonedRuntimeException <tt>double</tt>型への変換に失敗した場合。
   */
  @Override
  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 {
      return limit(
        toBigDecimal(v.toString()),
        new BigDecimal(-Double.MAX_VALUE),
        new BigDecimal( Double.MAX_VALUE)).doubleValue();
    }
  }
 
  /**
   * 指定されたキーに結びつけられた値オブジェクトを{@link BigDecimal}
   * オブジェクトに変換して取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は値が<tt>0</tt>の{@link
   * BigDecimal}オブジェクトを返す。
   *
   * @param key キー。
   * @return {@link BigDecimal}オブジェクト。
   * @throws ReasonedRuntimeException {@link BigDecimal}への変換に失敗した場合。
   */
  @Override
  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 {
      return toBigDecimal(v.toString());
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを文字列オブジェクトに変換して
   * 取得する。
   * <br>
   * 値オブジェクトが存在しない場合やヌルの場合は空文字列を返す。
   *
   * @param key キー。
   * @return 文字列オブジェクトに変換された値オブジェクト。
   */
  @Override
  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 引数がヌルの場合（デバッグ・モードのみ）。
   */
  @Override
  public String getNumberString(K key, String format)
  {
    assert (format != null) : "@param:format is null.";

    Object v = get(key);
    try {
      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) {
      throw new ReasonedRuntimeException(Error.FailToCastNumber,
        "[number string] <- [" + v + "]", e);
    }
  }

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

    Object v = get(key);
    try {
      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) {
      throw new ReasonedRuntimeException(Error.FailToCastDateTime,
        "[date string] <- [" + v + "]", e);
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトを日時オブジェクトに変換して
   * 取得する。
   * <br>
   * 指定されたキーに結びつけられた値オブジェクトを日時に変換できなかった場合は
   * 1970年1月1日0時0分0秒を表す{@link DateTime}オブジェクトを返す。
   *
   * @param key キー。
   * @return 日時オブジェクトに変換された値オブジェクト。
   */
  @Override
  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) {
        throw new ReasonedRuntimeException(Error.FailToCastDateTime,
          "[DateTime] <- [" + v + "]", e);
      }
    }
  }

  /**
   * 指定されたキーに結びつけられた値オブジェクトをリスト・オブジェクトに変換
   * して取得する。
   * <br>
   * 結びつけられている値オブジェクトが単値の場合は、その値オブジェクトを唯一の 
   * 要素とするリスト・オブジェクトを作成して返す。
   * <br>
   * 指定されたキーに結びつけられた値オブジェクトが存在しないかヌルの場合は
   * 空のリストを返す。
   * 
   * @param key キー。
   * @return {@link List}オブジェクトに変換された値オブジェクト。
   */
  @Override
  @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>値を指定した最大値と最小値で制限する。
   * <br>
   * 制限される値が、最小値から最大値の範囲外にある場合は例外をスローする。
   *
   * @param val 制限される<tt>long</tt>値。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の<tt>long</tt>値。
   * @throws ReasonedRuntimeException <tt>val</tt>が最小値から最大値の範囲外の
   *           場合。
   */
  protected long limit(long val, long min, long max)
  {
    if (val < min || val > max)
      throw new ReasonedRuntimeException(Error.FailToCastNumber,
        "[long][val:" + val + "][min:" + min + "][max:" + max + "]");

    return val;
  }

  /**
   * <tt>float</tt>値を指定した最大値と最小値で制限する。
   * <br>
   * 制限される値が、最小値から最大値の範囲外にある場合は例外をスローする。
   *
   * @param val リミットされる<tt>float</tt>値。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の<tt>float</tt>値。
   * @throws ReasonedRuntimeException <tt>val</tt>が最小値から最大値の範囲外の
   *           場合。
   */
  protected float limit(float val, float min, float max)
  {
    if (val < min || val > max)
      throw new ReasonedRuntimeException(Error.FailToCastNumber,
        "[float][val:" + val + "][min:" + min + "][max:" + max + "]");

    return val;
  }

  /**
   * <tt>double</tt>値を指定した最大値と最小値で制限する。
   * <br>
   * 制限される値が、最小値から最大値の範囲外にある場合は例外をスローする。
   *
   * @param val リミットされる<tt>double</tt>値。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の<tt>double</tt>値。
   * @throws ReasonedRuntimeException <tt>val</tt>が最小値から最大値の範囲外の
   *           場合。
   */
  protected double limit(double val, double min, double max)
  {
    if (val < min || val > max)
      throw new ReasonedRuntimeException(Error.FailToCastNumber,
        "[double][val:" + val + "][min:" + min + "][max:" + max + "]");

    return val;
  }

  /**
   * {@link BigDecimal}オブジェクトを指定した最大値と最小値で制限する。
   * <br>
   * 制限される値が、最小値から最大値の範囲外にある場合は例外をスローする。
   *
   * @param val リミットされる{@link BigDecimal}オブジェクト。
   * @param min 最小値。
   * @param max 最大値。
   * @return リミット後の{@link BigDecimal}オブジェクト。
   * @throws ReasonedRuntimeException <tt>val</tt>が最小値から最大値の範囲外の
   *           場合。
   */
  protected BigDecimal limit(BigDecimal val, BigDecimal min, BigDecimal max)
  {
    if (val.compareTo(min) < 0 || val.compareTo(max) > 0) {
      throw new ReasonedRuntimeException(Error.FailToCastNumber,
        "[BigDecimal][val:" + val + "][min:" + min + "][max:" + max + "]");
    }

    return val;
  }

  /**
   * 数値文字列を{@link BigDecimal}オブジェクトに変換する。
   * <br>
   * 数字や正負記号で構成された数値文字列を変換の対象とし、空白詰めや左ゼロ詰め
   * なども考慮して変換を行う。
   * <br>
   * 引数がヌルや空白の場合はゼロを示す{@link BigDecimal}オブジェクトを返す。
   * 引数を数値に変換できない場合は例外をスローする。
   *
   * @param str 変換される文字列。
   * @return {@link BigDecimal}オブジェクト。
   * @throws ReasonedRuntimeException 数値への変換に失敗した場合。
   */
  protected BigDecimal toBigDecimal(String str) throws ReasonedRuntimeException
  {
    if (str == null) return new BigDecimal(0);

    String v = str.trim();
    if (v.isEmpty()) return new BigDecimal(0);

    StringSequence seq = new StringSequence(v);
    String sign = "";
    if (seq.character().equals("-")) {
      sign = "-";
      seq.next();
    }
    else if (seq.character().equals("+")) {
      seq.next();
    }

    seq.skipWhitespaces();
    for ( ; seq.validIndex(); seq.next()) {
      if (! seq.character().equals("0")) break;
    }

    String numStr = seq.substring();
    if (numStr.isEmpty()) return new BigDecimal(0);

    try {
      return new BigDecimal(sign + numStr, MathContext.UNLIMITED);
    }
    catch (Exception e) {
      throw new ReasonedRuntimeException(Error.FailToCastNumber,
        "[BigDecimal] <- [" + str + "]", e);
    }
  }
}
