/*
 * Record 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 java.io.Serializable;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.HashMap;

/**
 * テーブルのレコードを表すクラス。
 * <br>
 * {@link java.util.Map Map}クラスを継承し、コンストラクタで指定されたヘッダ
 * が保持するカラム・キーのセットを、このオブジェクトのキー・セットとして使用
 * する。
 * <br>
 * このヘッダは他のレコードとも共用するので、{@link #remove(java.lang.Object)}
 * メソッドや{@link #clear()}メソッドを実行しても、値がヌルに設定されるだけで
 * カラム・キーは削除されない。
 * その後に{@link #containsKey(java.lang.Object)}メソッドや{@link #keySet()}
 * メソッドを実行しても結果は変わらない。
 *
 * @param <C> カラム・キーのタイプ。
 * @param <V> カラムの値のタイプ。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.1.1.1 $, $Date: 2010-10-16 00:03:51 $
 */
public abstract class Record<C,V> implements Map<C,V>, Serializable
{
  /**
   * デフォルト・コンストラクタ。
   */
  public Record()
  {}

  /**
   * ヘッダを取得する。
   *
   * @return ヘッダ・オブジェクト。
   */
  protected abstract Header<C> header();

  /**
   * カラム数を取得する。
   *
   * @return カラム数。
   */
  public int size()
  {
    return header().columnCount();
  }

  /**
   * カラム数がゼロかどうかを確認する。
   *
   * @return カラム数がゼロの場合は<tt>true</tt>、そうでなければ<tt>false</tt>
   *         を返す。
   */
  public boolean isEmpty()
  {
    return (size() == 0);
  }

  /**
   * 指定されたカラム・キーを持つ要素が存在するかどうかを確認する。
   *
   * @param  column 確認対象のカラム・キー。
   * @return 引数のカラム・キーを持つ要素が存在する場合は<tt>true</tt>、
   *         そうでなければ<tt>false</tt>を返す。
   * @throws ClassCastException 引数のカラム・キーのクラスが、このマップに
   *           不適当な場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルのキーを許可
   *           していない場合。
   */
  public boolean containsKey(Object column)
  {
    return header().hasColumn(column);
  }

  /**
   * 指定された値を持つ要素が存在するかどうかを確認する。
   *
   * @param  value 確認対象の値。
   * @return 引数の値を持つ要素が存在する場合は<tt>true</tt>、そうでなければ
   *         <tt>false</tt>を返す。
   * @throws ClassCastException 引数の値のクラスが、このマップに不適当な場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルの値を許可して
   *           いない場合。
   */
  public boolean containsValue(Object value)
  {
    return values().contains(value);
  }

  /**
   * 指定されたカラム・キーに対応付けられた値を取得する。
   *
   * @param  column カラム・キー。
   * @return 引数のカラム・キーに対応付けられた値。
   * @throws ClassCastException 引数のカラム・キーのクラスが、このマップに
   *           不適当な場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルのキーを許可
   *           していない場合。
   */
  public V get(Object column)
  {
    if (! header().hasColumn(column)) {
      return null;
    }
    return getValue(column);
  }

  /**
   * 指定されたカラム・キーと値の組を、このマップに設定する。
   *
   * @param  column カラム・キー。
   * @param  value  カラム・キーに対応付けられる値。
   * @return 引数のカラム・キーにそれまで対応付けられていた前回値。
   * @throws UnsupportedOperationException このマップがこのメソッドの処理を
   *           サポートしていない場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルのキーや値を 
   *           許可していない場合。
   */
  public V put(C column, V value)
  {
    header().addColumn(column);
    return putValue(column, value);
  }

  /**
   * 指定されたカラム・キーに対する値をヌルに設定する。
   * <br>
   * カラム・キーの削除は行わない。
   *
   * @param  column カラム・キー。
   * @return 引数のカラム・キーにそれまで対応付けられていた前回値。
   * @throws ClassCastException 引数のカラム・キーのクラスが、このマップに
   *           不適当な場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルのキーを許可
   *           していない場合。
   */
  public V remove(Object column)
  {
    if (! header().hasColumn(column)) {
      removeValue(column);
      return null;
    }
    else {
      return removeValue(column);
    }
  }

  /**
   * 指定されたマップに格納されているキーと値の組を、このマップにコピーする。
   *
   * @param  m コピー元のマップ。
   * @throws UnsupportedOperationException このマップがこのメソッドの処理を
   *           サポートしていない場合。
   * @throws NullPointerException 引数がヌルの場合、または引数のマップにヌルの
   *           キーや値が含まれていて、このマップがそれらを許可しない場合。
   */
  public void putAll(Map<? extends C, ? extends V> m)
  {
    for (Map.Entry<? extends C, ? extends V> entry : m.entrySet()) {
      header().addColumn(entry.getKey());
      putValue(entry.getKey(), entry.getValue());
    }
  }

  /**
   * このマップの内容をクリアする。
   * <br>
   * このマップに格納されている要素の値を全てヌルに設定する。
   * カラム・キーの削除は行わない。
   *
   * @throws UnsupportedOperationException このマップがこのメソッドの処理を
   *           サポートしていない場合。
   */
  public void clear()
  {
    for (C column : keySet()) {
      removeValue(column);
    }
  }

  /**
   * このマップのキー集合を取得する。
   *
   * @return このマップのキー集合。
   */
  public Set<C> keySet()
  {
    return new AbstractSet<C>() {
      public Iterator<C> iterator() {
        return new Iterator<C>() {
          private Enumeration<C> it_ = header().columns();
          private Object column_ = this;
          public boolean hasNext() { return it_.hasMoreElements(); }
          public C next() { C c = it_.nextElement(); column_ = c; return c; }
          public void remove() { Record.this.remove(column_); }
        };
      }
      public int size() {
        return header().columnCount();
      }
    };
  }

  /**
   * このマップに格納されている値のコレクションを取得する。
   *
   * @return このマップに格納されている値のコレクション。
   */
  public Collection<V> values()
  {
    return new AbstractCollection<V>() {
      public Iterator<V> iterator() {
        return new Iterator<V>() {
          private Enumeration<C> it_ = header().columns();
          private Object column_ = this;
          public boolean hasNext() { return it_.hasMoreElements(); }
          public V next() {
            C c = it_.nextElement(); column_ = c;
            return Record.this.getValue(c);
          }
          public void remove() { Record.this.remove(column_); }
        };
      }
      public int size() {
        return header().columnCount();
      }
    };
  }

  /**
   * このマップに格納されているキーと値の組を表すエントリの集合を取得する。
   *
   * @return このマップに格納されているキーと値の組を表すエントリの集合。
   */
  public Set<Map.Entry<C,V>> entrySet()
  {
    return new AbstractSet<Map.Entry<C,V>>() {
      public Iterator<Map.Entry<C,V>> iterator() {
        return new Iterator<Map.Entry<C,V>>() {
          private Enumeration<C> it_ = header().columns();
          private Object column_ = this;
          public boolean hasNext() { return it_.hasMoreElements(); }
          public Map.Entry<C,V> next() {
            C c = it_.nextElement(); column_ = c;
            return new SimpleEntry<C,V>(c, Record.this.getValue(c));
          }
          public void remove() { Record.this.remove(column_); }
        };
      }
      public int size() {
        return header().columnCount();
      }
    };
  }

  /**
   * 指定されたマップと、このマップの内容が等しいかどうかを比較する。
   * <br>
   * それぞれのマップに格納されているキーと値の組み合わせが一致する場合に
   * <tt>true</tt>を返す。
   *
   * @param  o 比較対象マップ。
   * @return 等しい場合は<tt>true</tt>、等しくない場合や引数がマップでなかった
   *         場合は<tt>false</tt>を返す。
   */
  @SuppressWarnings("unchecked")
  public boolean equals(Object o) 
  {
    if (o == this) {
      return true;
    }
    
    if (o == null || !(o instanceof Map)) {
      return false;
    }

    Map<C,V> m = _cast_to_map(o);
    if (m.size() != size()) {
      return false;
    }

    Enumeration<C> enm = header().columns();
    while (enm.hasMoreElements()) {
      C k = enm.nextElement();
      Object v1 = getValue(k);
      Object v2 = m.get(k);

      if (v1 == null) {
        if (v2 != null || !m.containsKey(k)) {
          return false;
        }
      }
      else {
        if (! v1.equals(v2)) {
          return false;
        }
      }
    }

    return true;
  }

  @SuppressWarnings("unchecked")
  private Map<C,V> _cast_to_map(Object o)
  {
    return (Map<C,V>) o;
  }

  /**
   * このマップのハッシュ・コードを取得する。
   *
   * @return このマップのハッシュ・コード。
   */
  public int hashCode()
  {
    int h = 0;
    Enumeration<C> enm = header().columns();
    while (enm.hasMoreElements()) {
      C k = enm.nextElement();
      Object v = getValue(k);
      h += ((k==null ? 0 : k.hashCode()) ^ (v==null ? 0 : v.hashCode()));
    }
    return h;
  }

  /**
   * このマップを表す文字列を取得する。
   * <br>
   * この文字列は、このマップに格納されているキーと値の組を等号('=')で結合して
   * 表現し、その集合をカンマ(',')区切りで連結して両端を括弧("{}")で囲んだ形で
   * 表される。
   * キーと値は、{@link java.lang.String#valueOf(java.lang.Object)}によって
   * 文字列化される。
   *
   * @return このマップを表す文字列。
   */
  public String toString()
  {
    Enumeration<C> enm = header().columns();
    if (! enm.hasMoreElements()) {
      return "{}";
    }

    StringBuilder buf = new StringBuilder();
    buf.append("{");

    C k; Object v;
    for (k=enm.nextElement(); enm.hasMoreElements(); k=enm.nextElement()) {
      v = getValue(k);
      buf.append(k == this ? "(this Map)" : String.valueOf(k));
      buf.append('=');
      buf.append(v == this ? "(this Map)" : String.valueOf(v));
      buf.append(", ");
    }

    v = getValue(k);
    buf.append(k == this ? "(this Map)" : String.valueOf(k));
    buf.append('=');
    buf.append(v == this ? "(this Map)" : String.valueOf(v));
    buf.append("}");
    return buf.toString();
  }


  /**
   * このレコードの、指定されたカラムの値を取得する。
   *
   * @param  column カラム・キー。
   * @return カラムの値。
   * @throws ClassCastException 引数のカラム・キーのクラスが、このマップに
   *           不適当な場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルのキーを許可
   *           していない場合。
   */
  protected abstract V getValue(Object column);

  /**
   * 指定されたカラム・キーと値の組を、このマップに設定する。
   *
   * @param  column カラム・キー。
   * @param  value  カラム・キーに対応付けられる値。
   * @return 引数のカラム・キーにそれまで対応付けられていた前回値。
   * @throws UnsupportedOperationException このマップがこのメソッドの処理を
   *           サポートしていない場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルのキーや値を 
   *           許可していない場合。
   */
  protected abstract V putValue(C column, V value);

  /**
   * 指定されたカラム・キーに対する値をヌルに設定する。
   *
   * @param  column カラム・キー。
   * @return 引数のカラム・キーにそれまで対応付けられていた前回値。
   * @throws ClassCastException 引数のカラム・キーのクラスが、このマップに
   *           不適当な場合。
   * @throws NullPointerException 引数がヌルで、このマップがヌルのキーを許可
   *           していない場合。
   */
  protected abstract V removeValue(Object column);

  /**
   * このレコードが、指定された条件を満たすかどうかを判定する。
   * <br>
   * 条件である引数のマップが保持する要素のキーと値の組み合わせが、
   * このレコードが保持する組み合わせと一致する場合に<tt>true</tt>を返す。
   * <br>
   * 条件が保持するキーがこのレコードのカラム・キーにない場合や、同じキーに
   * 対する値が条件とレコードで異なる場合は<tt>false</tt>を返す。
   * <br>
   * 反対に、条件にないキーがこのレコードのカラム・キーに存在しても、判定結果
   * には影響を与えない。
   *
   * @param  condition 条件とするカラムとその値を格納したマップ・オブジェクト。
   * @return このレコードが、指定された条件を満たす場合は<tt>true</tt>を返す。
   * @throws AssertionError 引数がヌルの場合（デバッグ・モードのみ）。
   */
  public boolean satisfyCondition(Map<C,V> condition)
  {
    assert (condition != null) : "@param:condition is null.";

    for (Map.Entry cndEntry : condition.entrySet()) {
      Object k = cndEntry.getKey();
      Object v = cndEntry.getValue();
      if (! header().hasColumn(k)) {
        return false;
      }

      V value = getValue(k);
      if (v == null) {
        if (value != null) {
          return false;
        }
      }
      else {
        if (! v.equals(value)) {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * キーと値の組を表す{@link java.util.Map.Entry Map.Entry}クラスを単純な実装
   * クラス。
   */
  public static class SimpleEntry<C,V> implements Entry<C,V>, Serializable
  {
    /** シリアル・バージョン番号。 */
    static final long serialVersionUID = -6771737537141833033L;

    /** キー。 */
    private final C key_;

    /** 値。 */
    private V value_;

    /**
     * キーと値を引数にとるコンストラクタ。
     *
     * @param  key キー。
     * @param  value 値。
     */
    public SimpleEntry(C key, V value)
    {
      key_ = key;
      value_ = value;
    }

    /**
     * {@link java.util.Map.Entry Map.Entry}オブジェクトを引数にとる
     * コンストラクタ。
     *
     * @param  entry {@link java.util.Map.Entry Map.Entry}オブジェクト。
     */
    public SimpleEntry(Entry<? extends C, ? extends V> entry)
    {
      key_ = entry.getKey();
      value_ = entry.getValue();
    }

    /**
     * キーを取得する。
     *
     * @return キー。
     */
    public C getKey()
    {
      return key_;
    }

    /**
     * 値を取得する。
     *
     * @return 値。
     */
    public V getValue()
    {
      return value_;
    }

    /**
     * 指定された値を設定する。
     *
     * @param  value 設定される新しい値。
     * @return 以前の値。
     */
    public V setValue(V value)
    {
      V oldValue = value_;
      value_ = value;
      return oldValue;
    }

    /**
     * 指定されたオブジェクトと内容が同じかどうかを判定する。
     * <br>
     * 指定されたオブジェクトのクラスが{@link java.util.Map.Entry Map.Entry}
     * クラスの派生クラスで、かつキーと値がそれぞれ等しい場合に<tt>true</tt>
     * を返す。
     *
     * @param  o 比較対象のオブジェクト。
     * @return 引数のオブジェクトの内容が、このオブジェクトの内容と等しい場合は
     *         <tt>true</tt>を返し、それ以外の場合は<tt>false</tt>を返す。
     */
    @SuppressWarnings("unchecked")
    public boolean equals(Object o)
    {
      if (! (o instanceof Map.Entry)) {
        return false;
      }

      Map.Entry e = (Map.Entry) o;

      if (key_ == e.getKey()) {
        ;
      }
      else if (key_ != null && key_.equals(e.getKey())) {
        ;
      }
      else {
        return false;
      }

      if (value_ == e.getValue()) {
        ;
      }
      else if (value_ != null && value_.equals(e.getValue())) {
        ;
      }
      else {
        return false;
      }

      return true;
    }

    /**
     * このオブジェクトのハッシュ・コードを取得する。 
     *
     * @return  このオブジェクトのハッシュ・コード。
     */
    public int hashCode()
    {
      return (key_   == null ? 0 : key_.hashCode()) ^
             (value_ == null ? 0 : value_.hashCode());
    }

    /**
     * このオブジェクトの内容を表す文字列を取得する。
     * <br>
     * この実装では、キーと値を等号("<tt>=</tt>")でつなげた文字列を返す。
     *
     * @return  このオブジェクトの内容を表す文字列。
     */
    public String toString()
    {
      return key_ + "=" + value_;
    }
  }
}

