/*
 * Range class.
 *
 * Copyright (C) 2007 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.io.Serializable;

/**
 * 範囲クラス。
 * <br>
 * 開始値と終了値の二つの値を持ち、引数が範囲内にあるかどうかを調べるメソッド
 * や、範囲の差分を求めるメソッドなどを用意する。
 * <br>
 * 開始値及び終了値にヌルを設定してはいけない。
 *
 * @author  佐藤隆之. 
 * @version $Revision: 1.2 $, $Date: 2011-08-01 15:29:30 $
 */
public class Range<T extends Comparable<T>> implements Serializable
{
  /** シリアル・バージョン番号。 */
  static final long serialVersionUID = 6469174627650758718L;

  /** 開始値。 */
  private T start_ ;

  /** 終了値。 */
  private T end_ ;

  /**
   * 開始値と終了値を引数にとるコンストラクタ。
   * <br>
   * 開始値と終了値の大小を逆に指定された場合は、これを入れ替えて設定する。
   *
   * @param  start 開始値。
   * @param  end 終了値。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public Range(T start, T end)
  {
    setRange(start, end);
  }

  /**
   * 開始値と終了値を設定する。
   * <br>
   * 開始値と終了値の大小を逆に指定された場合は、これを入れ替えて設定する。
   *
   * @param  start 開始値。
   * @param  end 終了値。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public void setRange(T start, T end)
  {
    assert (start != null) : "@param:start is null.";
    assert (end != null) : "@param:end is null.";

    if (start.compareTo(end) <= 0) {
      start_ = start;
      end_ = end;
    }
    else {
      start_ = end;
      end_ = start;
    }
  }

  /**
   * 範囲の開始値を取得する。
   *
   * @return 開始値。
   */
  public T getStart()
  {
    return start_ ;
  }

  /**
   * 範囲の終了値を取得する。
   *
   * @return 終了値。
   */
  public T getEnd()
  {
    return end_ ;
  }

  /**
   * このオブジェクトの内容が指定されたオブジェクトと等しいかどうかを判定する。
   * <br>
   * 等しい場合は<tt>true</tt>、異なる場合は<tt>false</tt>を返す。
   *
   * @param  obj 判定するオブジェクト。
   * @return 等しい場合は<tt>true</tt>、異なる場合は<tt>false</tt>。
   */
  public boolean equals(Object obj)
  {
    if (obj == null || !(obj instanceof Range)) {
      return false;
    }

    Range range = Range.class.cast(obj);

    if (! getStart().equals(range.getStart())) {
      return false;
    }

    if (! getEnd().equals(range.getEnd())) {
      return false;
    }

    return true;
  }

  /**
   * オブジェクトの内容を示す文字列を取得する。
   *
   * @return オブジェクトの内容を示す文字列。
   */
  public String toString()
  {
    StringBuilder buf = new StringBuilder();
    buf.append("{");
    buf.append(getStart().toString());
    buf.append("~");
    buf.append(getEnd().toString());
    buf.append("}");
    return buf.toString();
  }

  /**
   * 指定された値が範囲内にあるかどうかを判定する。
   *
   * @param  value 判定される値。
   * @return 範囲内にある場合はtrue、そうでなければfalseを返す。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public boolean encompass(T value)
  {
    assert (value != null) : "@param:value is null.";

    if (getStart().compareTo(value) > 0) {
      return false;
    }
    else if (getEnd().compareTo(value) < 0) {
      return false;
    }
    else {
      return true;
    }
  }

  /**
   * 指定された範囲が、このオブジェクトの示す範囲に含まれているかどうかを判定
   * する。
   *
   * @param range 判定される範囲。
   * @return 範囲内にある場合はtrue、そうでなければfalseを返す。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public boolean encompass(Range<T> range)
  {
    assert (range != null) : "@param:range is null.";

    if (getStart().compareTo(range.getStart()) > 0) {
      return false;
    }
    else if (getEnd().compareTo(range.getEnd()) < 0) {
      return false;
    }
    else {
      return true;
    }
  }

  /**
   * 指定された範囲とこのオブジェクトの示す範囲が重なる範囲を取得する。
   * <br>
   * 重なる範囲がなかった場合は、ヌルを返す。
   *
   * @param  range 範囲。
   * @return 引数の範囲とこのオブジェクトが重なる範囲。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public Range<T> intersectsWith(Range<T> range)
  {
    assert (range != null) : "@param:range is null.";

    T start = (getStart().compareTo(range.getStart()) > 0) ?
      getStart() : range.getStart();
    T end = (getEnd().compareTo(range.getEnd()) < 0) ?
      getEnd() : range.getEnd();

    if (start.compareTo(end) > 0) {
      return null;
    }

    return new Range<T>(start, end);
  }

  /**
   * 指定された値を含むように、このオブジェクトの示す範囲を拡張した範囲を取得
   * する。
   * <br>
   * 元から指定された値を含んでいた場合は、このオブジェクトと同じ範囲を返す。
   *
   * @param  value 値。
   * @return 指定された値を含むように拡張された範囲。
   * @throws AssertionError 引数がヌルの場合（デバッグモードのみ）。
   */
  public Range<T> expandsWith(T value)
  {
    assert (value != null) : "@param:value is null.";

    if (getStart().compareTo(value) > 0) {
      return new Range<T>(value, getEnd());
    }
    else if (getEnd().compareTo(value) < 0) {
      return new Range<T>(getStart(), value);
    }
    else {
      return new Range<T>(getStart(), getEnd());
    }
  }

  /**
   * 指定された範囲を含むように、このオブジェクトの示す範囲を拡張した範囲を取得
   * する。
   * <br>
   * 元から指定された範囲を含んでいた場合は、このオブジェクトと同じ範囲を返す。
   *
   * @param  range 範囲。
   * @return 指定された範囲を含むように拡張された範囲。
   * @throws AssertionError 引数がヌルの場合(デバッグモードのみ）。
   */
  public Range<T> expandsWith(Range<T> range)
  {
    assert (range != null) : "@param:range is null.";

    T start = (getStart().compareTo(range.getStart()) < 0) ?
      getStart() : range.getStart();
    T end = (getEnd().compareTo(range.getEnd()) > 0) ?
      getEnd() : range.getEnd(); 

    return new Range<T>(start, end);
  }
}
