/*
 * DefaultMapComparator 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.table;

import ts.util.GeneralComparator;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Map;
import java.util.LinkedHashSet;
import java.util.Collections;

/**
 * ２つのマップの大小を比較する{@link ts.util.table.MapComparator MapComparator}
 * インターフェイスのデフォルト実装クラス。
 * <br>
 * このインターフェイスの{@link #comparingKeys()}メソッドによって得られる
 * イテレータからキーを順に取り出して、それに対応するマップの値の大小を
 * 比較する。
 * <br>
 * 値の大小は、指定された2つの値オブジェクトのうちのいずれかが{@link
 * java.lang.Comparable Comparable}オブジェクトであれば、その{@link
 * java.lang.Comparable#compareTo(java.lang.Object) compareTo}メソッドを使用
 * して判定し、そうでなければ値オブジェクトを文字列に変換して比較する。
 * <br>
 * またオプションとして、値の大小を逆にして返したり、ヌルと非ヌル・オブジェクト
 * を比較したときの大小を指定したりできるようにするためのメソッドを用意して
 * いる。
 *
 * @param <K> このマップのキーのタイプ。
 * @param <V> このマップの値のタイプ。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.1.1.1 $, $Date: 2010-10-16 00:03:51 $
 */
public class DefaultMapComparator<K,V> implements MapComparator<K,V>
{
  /** 比較に使用するキーを格納するセット。 */
  private LinkedHashSet<K> keySet_ = new LinkedHashSet<K>();

  /** 値の比較に使用する{@link ts.util.GeneralComparator GeneralComparator} */
  private static GeneralComparator cmp_ = new GeneralComparator();

  /** 値の大小関係の逆にするかどうかを示す列挙型クラス。 */
  public enum Direction { NORMAL, REVERSE }

  /** ヌルと非ヌル・オブジェクトの大小関係を示す列挙型クラス。 */
  public enum NullOrder { GREATEST, LEAST }

  /** 値の大小関係を逆にするかどうかを示すフラグ。 */
  private Direction direction_ = Direction.NORMAL;

  /** ヌルと非ヌル・オブジェクトの大小関係を示すフラグ。 */
  private NullOrder nullOrder_ = NullOrder.GREATEST;

  /**
   * デフォルトコンストラクタ。
   */
  public DefaultMapComparator()
  {}

  /**
   * インデックス・キーを引数にとるコンストラクタ。 
   *
   * @param  indexKeys インデックス・キーの配列。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public DefaultMapComparator(K ... indexKeys)
  {
    for (K key : indexKeys) {
      addKey(key);
    }
  }

  /**
   * 値の大小関係を逆にするかどうかを示すフラグを設定する。
   *
   * @param  dir 値の大小関係を逆にするかどうかを示すフラグ値。
   */
  public void setDirection(Direction dir)
  {
    direction_ = dir;
  }

  /**
   * ヌルと非ヌル・オブジェクトの大小関係を示すフラグ。
   *
   * @param  nullOrder ヌルと非ヌル・オブジェクトの大小関係を示すフラグ値。
   */
  public void setNullOrder(NullOrder nullOrder)
  {
    nullOrder_ = nullOrder;
  }

  /**
   * 比較に使用するキーを追加する。
   *
   * @param  key キー。
   */
  public void addKey(K key)
  {
    keySet_.add(key);
  }

  /**
   * マップの比較に使用するキーを列挙する。
   *
   * @return マップの比較に使用するキーの列挙オブジェクト。
   */
  public Enumeration<K> comparingKeys()
  {
    return Collections.enumeration(keySet_);
  }

  /**
   * {@link #comparingKeys()}メソッドによって得られるキーに対応付けられた値を
   * 比較し、マップの大小を判定する。
   * <br>
   * 値の比較方法は、{@link ts.util.GeneralComparator GeneralComparator}に従う
   * こととする。
   *
   * @param  m1 比較対象のマップ1。
   * @param  m2 比較対象のマップ2。
   * @return m1がm2より小さい場合は負の値を、m1がm2より大きい場合は正の値を、
   *         m1とm2が等しい場合はゼロを返す。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   * @see ts.util.GeneralComparator
   */
  public int compare(Map<K,V> m1, Map<K,V> m2)
  {
    assert (m1 != null) : "@param:m1 is null.";
    assert (m2 != null) : "@param:m2 is null.";

    Enumeration<K> enm = comparingKeys();
    while (enm.hasMoreElements()) {
      K key = enm.nextElement();
      V v1 = m1.get(key);
      V v2 = m2.get(key);
      int ret = cmp_.compare(v1, v2);
      if (ret != 0) {
        if (v1 == null || v2 == null) {
          if (nullOrder_.equals(NullOrder.LEAST)) {
            ret *= -1;
          }
        }
        if (direction_.equals(Direction.REVERSE)) {
          ret *= -1;
        }
        return ret;
      }
    }

    return 0;
  }

  /**
   * 引数のオブジェクトがこのオブジェクトに等しいかどうかを判定する。
   *
   * @param  obj 比較対象のオブジェクト。
   * @return 等しい場合は<tt>true</tt>を、そうでない場合は<tt>false</tt>を返す。
   */
  public boolean equals(Object obj)
  {
    return super.equals(obj);
  }
}

