/*
 * IndexTest 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.tester.function.coverage.FunctionTester;
import ts.tester.function.print.*;
import ts.tester.function.ObjectInspector;
import ts.util.*;
import java.util.*;

/**
 * {@link ts.util.table.Index Index}クラスの機能試験クラス。
 *
 * @author  佐藤隆之.
 * @version $Revision: 1.4 $, $Date: 2011-10-29 14:55:21 $
 */
public class IndexTest extends FunctionTester
{
  public static void main(String[] args)
  {
    try {
      PrinterGroup group = new PrinterGroup();
      group.addPrinter(new ConsolePrinter());
      group.addPrinter(new HtmlPrinter("SATOH Takayuki"));
      setPrinter(group);

      run(IndexTest.class, (args.length == 0) ? null : args[0]);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  protected void preTesting()
  {
    MSG("Index クラスの機能試験を実行する。");
  }

  /* -- inner class -- */

  static class TableEx<C,V> extends AbstractTable<C,V> {
    final static long serialVersionUID = 0L;
    public TableEx() {}
    public TableEx(int nrec, int ncol) { super(nrec, ncol); }
    public TableEx(Header<C> h) { super(h); }
    @Override
    protected Header<C> createHeader(int nc) {
      return new Header<C>() {
        static final long serialVersionUID = 0L;
        Set<C> columnSet_ = new HashSet<C>();
        public int columnCount() { return columnSet_.size(); }
        public Enumeration<C> columns() {
          return Collections.enumeration(columnSet_);
        }
        public boolean hasColumn(Object c) { return columnSet_.contains(c); }
        public void addColumn(C c) { columnSet_.add(c); }
      };
    }
    @Override
    protected Collection<Record<C,V>> createRecordCollection(int nc) {
      return new LinkedList<Record<C,V>>();
    }
    @Override
    protected Record<C,V> createRecord(int nc) {
      return new Record<C,V>() {
        static final long serialVersionUID = 0L;
        private Map<C,V> map_ = new HashMap<C,V>();
        protected Header<C> header() { return TableEx.this.header(); } 
        protected V getValue(Object c) { return map_.get(c); }
        protected V putValue(C c, V v) { return map_.put(c, v); }
        protected V removeValue(Object c) { return map_.remove(c); }
      };
    }
  }

  class IndexEx<C,V> extends Index<C,V> {
    static final long serialVersionUID = -1L;
    private Table<C,V>  table_;
    public IndexEx(Table<C,V> t, C[] keys) {
      super(keys);
      table_ = t;
    }
    protected Table<C,V> getBaseTable() {
      return table_ ;
    }
    public boolean exists(Map<C,V> condition) {
      throw new UnsupportedOperationException();
    }
    public Map<C,V> selectFirst(Map<C,V> condition) {
      throw new UnsupportedOperationException();
    }
    public List<Map<C,V>> select(Map<C,V> condition) {
      throw new UnsupportedOperationException();
    }
    public List<Map<C,V>> delete(Map<C,V> condition) {
      throw new UnsupportedOperationException();
    }
    public List<Map<C,V>> update(Map<C,V> condition, Map<C,V> destination) {
      throw new UnsupportedOperationException();
    }
  }

  /* -- test case -- */

  public void constructor()
  {
    MSG("コンストラクタの確認。");

    TableEx<String,String> tab = new TableEx<String,String>();
    String[] arr = {"c2","c0","c1"};

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    NOTNULL(ind);
    EQUAL(ind.getBaseTable(), tab);
  }

  public void constructor_1()
  {
    MSG("引数がヌルの場合。");

    TableEx<String,String> tab = new TableEx<String,String>();

    try {
      IndexEx<String,String> ind = new IndexEx<String,String>(tab, null);
      NG();
    } catch (AssertionError e) {
      OK(e);
    }
  }

  public void constructor_2()
  {
    MSG("引数のインデックス・キー・リストが空の場合。");

    TableEx<String,String> tab = new TableEx<String,String>();
    String[] arr = {};

    try {
      IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
      NG();
    } catch (IllegalArgumentException e) {
      OK(e);
    }
  }

  public void columnCount()
  {
    MSG("カラム数を取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    EQUAL(tab.columnCount(), 0);

    String[] arr = {"c2","c0","c1"};

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);

    Map<String,String> rec = tab.appendNew();
    rec.put("c0", null);
    rec.put("c1", null);
    rec.put("c2", null);
    rec.put("c3", null);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);

    Map<String,String> rec1 = ind.appendNew();
    rec1.put("c4", null);
    EQUAL(tab.columnCount(), 5);
    EQUAL(ind.columnCount(), 5);
  }

  public void recordCount()
  {
    MSG("レコード数を取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    EQUAL(tab.columnCount(), 0);

    String[] arr = {"c2","c0","c1"};

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);

    Map<String,String> rec = tab.appendNew();
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);

    Map<String,String> rec1 = ind.appendNew();
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
  }

  public void indexKeyCount()
  {
    MSG("インデックス・キーの数を取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    EQUAL(tab.columnCount(), 0);

    String[] arr = {"c2","c0","c1"};

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);
    EQUAL(ind.indexKeyCount(), 3);

    Map<String,String> rec = tab.appendNew();
    rec.put("c0", null);
    rec.put("c1", null);
    rec.put("c2", null);
    rec.put("c3", null);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    EQUAL(ind.indexKeyCount(), 3);

    Map<String,String> rec1 = ind.appendNew();
    rec1.put("c4", null);
    EQUAL(tab.columnCount(), 5);
    EQUAL(ind.columnCount(), 5);
    EQUAL(ind.indexKeyCount(), 3);
  }

  public void columns()
  {
    MSG("カラム・キーを列挙する。");

    TableEx<String,String> tab = new TableEx<String,String>();
    EQUAL(tab.columnCount(), 0);

    String[] arr = {"c2","c0","c1"};

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);
    FALSE(ind.columns().hasMoreElements());

    Map<String,String> rec = tab.appendNew();
    rec.put("c0", null);
    rec.put("c1", null);
    rec.put("c2", null);
    rec.put("c3", null);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    EQUAL(ind.indexKeyCount(), 3);
    Enumeration enm = ind.columns();
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    FALSE(enm.hasMoreElements());

    Map<String,String> rec1 = ind.appendNew();
    rec1.put("c4", null);
    EQUAL(tab.columnCount(), 5);
    EQUAL(ind.columnCount(), 5);
    EQUAL(ind.indexKeyCount(), 3);
    enm = ind.columns();
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    TRUE(enm.hasMoreElements());
    TRUE(rec.containsKey(enm.nextElement()));
    FALSE(enm.hasMoreElements());
  }

  public void records()
  {
    MSG("レコードの列挙。");

    TableEx<String,String> tab = new TableEx<String,String>();

    String[] arr = {"c2","c0","c1"};

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    FALSE(ind.records().hasNext());

    Map<String,String> rec = tab.appendNew();
    rec.put("c0", "v0");
    rec.put("c1", "v1");
    rec.put("c2", "v2");
    rec.put("c3", "v3");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);

    MapIterator<String,String> it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    rec = tab.appendNew();
    rec.put("c0", "v0");
    rec.put("c1", "v1");
    rec.put("c2", "v2");
    rec.put("c3", "v3");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);

    it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    tab.deleteAll();
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
  }

  public void records_comparator()
  {
    MSG("レコードをソートして列挙。");

    TableEx<String,String> tab = new TableEx<String,String>();

    String[] arr = {"c2","c0","c1"};

    DefaultMapComparator<String,String> comp =
      new DefaultMapComparator<String,String>();
    comp.addKey("c1");
    comp.addKey("c2");

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    FALSE(ind.records(comp).hasNext());

    Map<String,String> rec = tab.appendNew();
    rec.put("c0", "v0");
    rec.put("c1", "v1");
    rec.put("c2", "v2");
    rec.put("c3", "v3");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    MapIterator<String,String> it = ind.records(comp);
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    Map<String,String> rec1 = tab.appendNew();
    rec1.put("c0", "v4");
    rec1.put("c1", "v0");
    rec1.put("c2", "v2");
    rec1.put("c3", "v3");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    it = ind.records(comp);
    TRUE(it.hasNext());
    EQUAL(it.next(), rec1);
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    tab.delete(rec1);
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    it = ind.records(comp);
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());
  }

  public void records_comparator_1()
  {
    MSG("引数がヌルの場合。");

    TableEx<String,String> tab = new TableEx<String,String>();
    try {
      tab.records(null);
      NG();
    } catch (AssertionError e) {
      OK(e);
    }
  }

  public void indexKeys()
  {
    MSG("インデックス・キーを列挙する。");

    TableEx<String,String> tab = new TableEx<String,String>();

    String[] arr = {"c2","c0","c1"};

    IndexEx<String,String> ind = new IndexEx<String,String>(tab, arr);
    EQUAL(ind.indexKeyCount(), 3);

    Enumeration<String> enm = ind.indexKeys();
    TRUE(enm.hasMoreElements());
    EQUAL(enm.nextElement(), "c2");
    TRUE(enm.hasMoreElements());
    EQUAL(enm.nextElement(), "c0");
    TRUE(enm.hasMoreElements());
    EQUAL(enm.nextElement(), "c1");
    FALSE(enm.hasMoreElements());

    try {
      enm.nextElement();
      NG();
    } catch (NoSuchElementException e) {
      OK(e);
    }
  }

  public void getIndex()
  {
    MSG("インデックスを取得する。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);

    EQUAL(tab.getIndex("c2", "c0", "c1"), ind);

    Map<String,String> rec = tab.appendNew();
    rec.put("c0", "v0");
    rec.put("c1", "v1");
    rec.put("c2", "v2");
    rec.put("c3", "v3");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    MapIterator<String,String> it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    Map<String,String> rec1 = tab.appendNew();
    rec1.put("c0", "v4");
    rec1.put("c1", "v0");
    rec1.put("c2", "v2");
    rec1.put("c3", "v3");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    TRUE(it.hasNext());
    EQUAL(it.next(), rec1);
    FALSE(it.hasNext());

    tab.delete(rec1);
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    tab.deleteAll();
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    it = ind.records();
    FALSE(it.hasNext());

    EQUAL(tab.getIndex("c2", "c0", "c1"), ind);
  }

  public void getIndex_1()
  {
    MSG("引数がヌルの場合。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");

    try {
      ind.getIndex((String[]) null);
      NG();
    } catch (AssertionError e) {
      OK(e);
    }
  }

  public void getIndex_2()
  {
    MSG("引数のセットの要素数が空の場合。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");

    try {
      ind.getIndex(new String[0]);
      NG();
    } catch (IllegalArgumentException e) {
      OK(e);
    }
  }

  public void addTrigger()
  {
    MSG("トリガを追加する。");

    Trigger<String,String> trigger = new Trigger<String,String>() {
      final static long serialVersionUID = 0L;
    };

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");
    ind.addTrigger(trigger);

    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);

    Map<String,String> rec = tab.appendNew();
    rec.put("c0", "v0");
    rec.put("c1", "v1");
    rec.put("c2", "v2");
    rec.put("c3", "v3");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    MapIterator<String,String> it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    Map<String,String> rec1 = tab.appendNew();
    rec1.put("c0", "v4");
    rec1.put("c1", "v0");
    rec1.put("c2", "v2");
    rec1.put("c3", "v3");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    TRUE(it.hasNext());
    EQUAL(it.next(), rec1);
    FALSE(it.hasNext());

    tab.delete(rec1);
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    it = ind.records();
    TRUE(it.hasNext());
    EQUAL(it.next(), rec);
    FALSE(it.hasNext());

    tab.deleteAll();
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    EQUAL(tab.columnCount(), 4);
    EQUAL(ind.columnCount(), 4);
    it = ind.records();
    FALSE(it.hasNext());
  }

  public void addTrigger_1()
  {
    MSG("引数がヌルの場合。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");

    try {
      ind.addTrigger(null);
      NG();
    }
    catch (AssertionError e) {
      OK(e);
    }
  }

  public void getIndexedCollectionByForce()
  {
    MSG("インデックス・ツリーにおいてレコードを格納するセットの作成と取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    String[] arr = {"c2"};
    Index<String,String> ind = tab.getIndex(arr);

    Map<String,Object> root = new HashMap<String,Object>();

    ObjectInspector oi = new ObjectInspector(this);
    oi.ignore("indexKeys_");
    oi.expect("rootMap_", root);
    oi.expect("serialVersionUID",
      AbstractTable.AbstractTableIndex.serialVersionUID);
    oi.inspect(ind);

    Map<String,String> rec = tab.appendNew();
    IdentityHashSet<Map<String,String>> child1_null =
      new IdentityHashSet<Map<String,String>>();
    child1_null.add(rec);
    root.put(null, child1_null);
    oi.inspect(ind);

    rec.put("c0", "v0");
    oi.inspect(ind);

    rec.put("c2", "v2");
    child1_null.remove(rec);
    IdentityHashSet<Map<String,String>> child1_v2 =
      new IdentityHashSet<Map<String,String>>();
    child1_v2.add(rec);
    root.put("v2", child1_v2);
    oi.inspect(ind);

    rec.put("c2", null);
    child1_v2.remove(rec);
    child1_null.add(rec);
    oi.inspect(ind);

    rec.put("c2", "vv");
    IdentityHashSet<Map<String,String>> child1_vv =
      new IdentityHashSet<Map<String,String>>();
    child1_vv.add(rec);
    child1_null.remove(rec);
    root.put("vv", child1_vv);
    oi.inspect(ind);
  }

  public void getIndexedCollection()
  {
    MSG("インデックス・ツリーにおいてレコードを格納するセットの取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    String[] arr = {"c2"};
    Index<String,String> ind = tab.getIndex(arr);

    Map<String,Object> root = new HashMap<String,Object>();

    ObjectInspector oi = new ObjectInspector(this);
    oi.ignore("indexKeys_");
    oi.expect("rootMap_", root);
    oi.expect("serialVersionUID", 
      AbstractTable.AbstractTableIndex.serialVersionUID);
    oi.inspect(ind);

    Map<String,String> rec = tab.appendNew();
    IdentityHashSet<Map<String,String>> child1_null =
      new IdentityHashSet<Map<String,String>>();
    child1_null.add(rec);
    root.put(null, child1_null);
    oi.inspect(ind);

    Collection<Record<String,String>> coll0 = ind.getIndexedCollection(
      (Record<String,String>)rec);
    EQUAL(coll0.size(), 1);
    EQUAL(coll0.iterator().next(), rec);

    rec.put("c0", "v0");
    oi.inspect(ind);

    Collection<Record<String,String>> coll1 = ind.getIndexedCollection(
      (Record<String,String>)rec);
    EQUAL(coll1.size(), 1);
    EQUAL(coll1.iterator().next(), rec);
    TRUE(coll0 == coll1);

    rec.put("c2", "v2");
    child1_null.remove(rec);
    IdentityHashSet<Map<String,String>> child1_v2 =
      new IdentityHashSet<Map<String,String>>();
    child1_v2.add(rec);
    root.put("v2", child1_v2);
    oi.inspect(ind);

    Collection<Record<String,String>> coll2 = ind.getIndexedCollection(
      (Record<String,String>)rec);
    EQUAL(coll2.size(), 1);
    EQUAL(coll2.iterator().next(), rec);
    TRUE(coll0 != coll2);
    EQUAL(coll0.size(), 0);

    rec.put("c2", null);
    child1_v2.remove(rec);
    child1_null.add(rec);
    oi.inspect(ind);

    Collection<Record<String,String>> coll3 = ind.getIndexedCollection(
      (Record<String,String>)rec);
    EQUAL(coll3.size(), 1);
    EQUAL(coll3.iterator().next(), rec);
    TRUE(coll0 == coll3);
    EQUAL(coll2.size(), 0);

    rec.put("c2", "vv");
    IdentityHashSet<Map<String,String>> child1_vv =
      new IdentityHashSet<Map<String,String>>();
    child1_vv.add(rec);
    child1_null.remove(rec);
    root.put("vv", child1_vv);
    oi.inspect(ind);

    Collection<Record<String,String>> coll4 = ind.getIndexedCollection(
      (Record<String,String>)rec);
    EQUAL(coll4.size(), 1);
    EQUAL(coll4.iterator().next(), rec);
    TRUE(coll0 != coll4);
    EQUAL(coll0.size(), 0);
    EQUAL(coll2.size(), 0);
  }

  public void getIndexedCollection_1()
  {
    MSG("引数がヌルの場合。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2");

    try {
      ind.getIndexedCollection(null);
      NG();
    } catch (AssertionError e) {
      OK(e);
    }
  }

  public void getIndexedCollection_2()
  {
    MSG("カラムの値が異なるレコードを引数とした場合。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2");

    Map<String,String> rec = ind.appendNew();
    rec.put("c0", "v0");
    rec.put("c1", "v1");
    rec.put("c2", "v2");
    TRUE(ind.getIndexedCollection((Record<String,String>)rec).iterator().next()
      == rec);

    TableEx<String,String> tab2 = new TableEx<String,String>();
    Map<String,String> rec2 = tab2.appendNew();
    rec2.put("c0", "xx");
    rec2.put("c1", "yy");
    rec2.put("c2", "zz");

    EQUAL(ind.getIndexedCollection((Record<String,String>)rec2).size(), 0);

    Index<String,String> ind2 = tab.getIndex("c1", "c2");
    EQUAL(ind2.getIndexedCollection((Record<String,String>)rec2).size(), 0);
  }

  public void collectRecords()
  {
    MSG("インデックス・ツリーにおいてレコードを格納するセットの取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    String[] arr = {"c2"};
    Index<String,String> ind = tab.getIndex(arr);

    Map<String,Object> root = new HashMap<String,Object>();

    ObjectInspector oi = new ObjectInspector(this);
    oi.ignore("indexKeys_");
    oi.expect("rootMap_", root);
    oi.expect("serialVersionUID",
      AbstractTable.AbstractTableIndex.serialVersionUID);
    oi.inspect(ind);

    Map<String,String> cnd = new HashMap<String,String>();

    Collection<Record<String,String>> coll = ind.collectRecords(cnd);
    EQUAL(coll.size(), 0);

    Map<String,String> rec = tab.appendNew();
    IdentityHashSet<Map<String,String>> child1_null =
      new IdentityHashSet<Map<String,String>>();
    child1_null.add(rec);
    root.put(null, child1_null);
    oi.inspect(ind);

    Collection<Record<String,String>> coll0 = ind.collectRecords(cnd);
    EQUAL(coll0.size(), 1);
    EQUAL(coll0.iterator().next(), rec);

    cnd.put("c0", null);
    coll0 = ind.collectRecords(cnd);
    EQUAL(coll0.size(), 1);
    EQUAL(coll0.iterator().next(), rec);

    cnd.put("c1", null);
    coll0 = ind.collectRecords(cnd);
    EQUAL(coll0.size(), 1);
    EQUAL(coll0.iterator().next(), rec);

    cnd.put("c2", null);
    coll0 = ind.collectRecords(cnd);
    EQUAL(coll0.size(), 1);
    EQUAL(coll0.iterator().next(), rec);

    rec.put("c0", "v0");
    oi.inspect(ind);

    Collection<Record<String,String>> coll1 = ind.collectRecords(cnd);
    EQUAL(coll1.size(), 1);
    EQUAL(coll1.iterator().next(), rec);

    cnd.put("c0", "v0");
    coll1 = ind.collectRecords(cnd);
    EQUAL(coll1.size(), 1);
    EQUAL(coll1.iterator().next(), rec);

    cnd.put("c1", null);
    coll1 = ind.collectRecords(cnd);
    EQUAL(coll1.size(), 1);
    EQUAL(coll1.iterator().next(), rec);

    cnd.put("c2", null);
    coll1 = ind.collectRecords(cnd);
    EQUAL(coll1.size(), 1);
    EQUAL(coll1.iterator().next(), rec);

    cnd.put("c0", null);
    coll1 = ind.collectRecords(cnd);
    EQUAL(coll1.size(), 1);
    EQUAL(coll1.iterator().next(), rec);

    cnd.put("c2", "xx");
    coll1 = ind.collectRecords(cnd);
    EQUAL(coll1.size(), 0);

    rec.put("c2", "v2");
    child1_null.remove(rec);
    IdentityHashSet<Map<String,String>> child1_v2 =
      new IdentityHashSet<Map<String,String>>();
    child1_v2.add(rec);
    root.put("v2", child1_v2);
    oi.inspect(ind);

    cnd.clear();
    cnd.put("c2", "v2");
    Collection<Record<String,String>> coll2 = ind.collectRecords(cnd);
    EQUAL(coll2.size(), 1);
    EQUAL(coll2.iterator().next(), rec);

    cnd.put("c2", null);
    coll2 = ind.collectRecords(cnd);
    EQUAL(coll2.size(), 0);

    cnd.put("c2", "xx");
    coll2 = ind.collectRecords(cnd);
    EQUAL(coll2.size(), 0);
  }

  public void deleteAll()
  {
    MSG("全てのレコードを削除。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);

    ind.deleteAll();
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);

    ind.appendNew();
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);

    ind.appendNew();
    ind.appendNew();
    ind.appendNew();
    EQUAL(tab.recordCount(), 4);
    EQUAL(ind.recordCount(), 4);

    ind.deleteAll();
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);

    ind.appendNew();
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
  }

  public void appendNew()
  {
    MSG("レコードの追加。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");
    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);

    Map<String,String> rec0 = ind.appendNew();
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);

    rec0.put("c0", "v0");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 1);
    EQUAL(ind.columnCount(), 1);

    rec0.put("c1", "v1");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    rec0.clear();
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    Map<String,String> rec1 = ind.appendNew();
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    rec1.put("c0", "v0");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    rec1.put("c2", "v2");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 3);
    EQUAL(ind.columnCount(), 3);
  }

  public void appendNew_int()
  {
    MSG("初期容量を設定してレコードの追加。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0", "c1");

    EQUAL(tab.recordCount(), 0);
    EQUAL(ind.recordCount(), 0);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);

    Map<String,String> rec0 = ind.appendNew(10);
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 0);
    EQUAL(ind.columnCount(), 0);

    rec0.put("c0", "v0");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 1);
    EQUAL(ind.columnCount(), 1);

    rec0.put("c1", "v1");
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    rec0.clear();
    EQUAL(tab.recordCount(), 1);
    EQUAL(ind.recordCount(), 1);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    Map<String,String> rec1 = ind.appendNew(10);
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    rec1.put("c0", "v0");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 2);
    EQUAL(ind.columnCount(), 2);

    rec1.put("c2", "v2");
    EQUAL(tab.recordCount(), 2);
    EQUAL(ind.recordCount(), 2);
    EQUAL(tab.columnCount(), 3);
    EQUAL(ind.columnCount(), 3);
  }

  public void exists()
  {
    MSG("レコードが存在するかどうかを確認。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0");

    Map<String,String> rec0 = ind.appendNew(3);
    rec0.put("c0", "v00");
    rec0.put("c1", "v01");
    rec0.put("c2", "v02");
    Map<String,String> rec1 = ind.appendNew(3);
    rec1.put("c0", "v10");
    rec1.put("c1", "v11");
    rec1.put("c2", "v12");
    Map<String,String> rec2 = tab.appendNew(3);
    rec2.put("c0", "v20");
    rec2.put("c1", "v21");
    rec2.put("c2", "v22");
    Map<String,String> rec3 = tab.appendNew(3);
    rec3.put("c0", "v30");
    rec3.put("c1", "v31");
    rec3.put("c2", "v32");
    Map<String,String> rec4 = ind.appendNew(3);
    rec4.put("c0", "v00");
    rec4.put("c1", "v11");
    rec4.put("c2", "v22");
    Map<String,String> rec5 = tab.appendNew(3);
    rec5.put("c0", "v01");
    rec5.put("c1", "v31");
    rec5.put("c2", "v22");

    TRUE(ind.exists("c2", "v02"));
    TRUE(ind.exists("c2", "v12"));
    TRUE(ind.exists("c2", "v22"));
    TRUE(ind.exists("c2", "v32"));
    FALSE(ind.exists("c2", "v42"));
    FALSE(ind.exists("c2", "v52"));
    FALSE(ind.exists(null, null));

    Map<String,String> rec6 = tab.appendNew(3);
    TRUE(ind.exists("c2", null));
    FALSE(ind.exists(null, null));

    Map<String,String> cnd = new HashMap<String,String>();
    cnd.put("c0", "v00");
    cnd.put("c2", "v02");
    EQUAL(ind.getIndexedCollection((Record<String,String>)rec0).size(), 1);
    EQUAL(ind.collectRecords(cnd).size(), 1);
    TRUE(ind.exists("c0", "v00"));
    TRUE(tab.exists("c0", "v00"));

    TRUE(ind.exists("c0", "v10"));
    TRUE(ind.exists("c0", "v20"));
    TRUE(ind.exists("c0", "v30"));
    FALSE(ind.exists("c0", "v40"));
    FALSE(ind.exists("c0", "v50"));
    TRUE(ind.exists("c0", null));

    TRUE(ind.exists("c1", "v01"));
    TRUE(ind.exists("c1", "v11"));
    TRUE(ind.exists("c1", "v21"));
    TRUE(ind.exists("c1", "v31"));
    FALSE(ind.exists("c1", "v41"));
    FALSE(ind.exists("c1", "v51"));
    TRUE(ind.exists("c1", null));
  }

  public void selectFirst()
  {
    MSG("条件に最初に該当するレコードを取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0");

    Map<String,String> rec0 = ind.appendNew(3);
    rec0.put("c0", "v00");
    rec0.put("c1", "v01");
    rec0.put("c2", "v02");
    Map<String,String> rec1 = ind.appendNew(3);
    rec1.put("c0", "v10");
    rec1.put("c1", "v11");
    rec1.put("c2", "v12");
    Map<String,String> rec2 = tab.appendNew(3);
    rec2.put("c0", "v20");
    rec2.put("c1", "v21");
    rec2.put("c2", "v22");
    Map<String,String> rec3 = tab.appendNew(3);
    rec3.put("c0", "v30");
    rec3.put("c1", "v31");
    rec3.put("c2", "v32");
    Map<String,String> rec4 = ind.appendNew(3);
    rec4.put("c0", "v00");
    rec4.put("c1", "v11");
    rec4.put("c2", "v22");
    Map<String,String> rec5 = tab.appendNew(3);
    rec5.put("c0", "v01");
    rec5.put("c1", "v31");
    rec5.put("c2", "v22");
    Map<String,String> rec6 = tab.appendNew(3);

    TRUE(ind.selectFirst(null, null) == null);

    TRUE(ind.selectFirst("c2", "v02") == rec0);
    TRUE(ind.selectFirst("c2", "v12") == rec1);
    TRUE(ind.selectFirst("c2", "v22") == rec2
      || ind.selectFirst("c2", "v22") == rec4
      || ind.selectFirst("c2", "v22") == rec5);
    TRUE(ind.selectFirst("c2", "v32") == rec3);
    TRUE(ind.selectFirst("c2", "v42") == null);
    TRUE(ind.selectFirst("c2", "v52") == null);
    TRUE(ind.selectFirst("c2", null ) == rec6);

    TRUE(ind.selectFirst("c0", "v00") == rec0
      || ind.selectFirst("c0", "v00") == rec4);
    TRUE(ind.selectFirst("c0", "v10") == rec1);
    TRUE(ind.selectFirst("c0", "v20") == rec2);
    TRUE(ind.selectFirst("c0", "v30") == rec3);
    TRUE(ind.selectFirst("c0", "v40") == null);
    TRUE(ind.selectFirst("c0", "v50") == null);
    TRUE(ind.selectFirst("c0", null ) == rec6);

    TRUE(ind.selectFirst("c1", "v01") == rec0);
    TRUE(ind.selectFirst("c1", "v11") == rec1
      || ind.selectFirst("c1", "v11") == rec4);
    TRUE(ind.selectFirst("c1", "v21") == rec2);
    TRUE(ind.selectFirst("c1", "v31") == rec3
      |  ind.selectFirst("c1", "v31") == rec5);
    TRUE(ind.selectFirst("c1", "v41") == null);
    TRUE(ind.selectFirst("c1", "v51") == null);
    TRUE(ind.selectFirst("c1", null ) == rec6);
  }

  public void select()
  {
    MSG("条件に該当するレコードのリストを取得。");

    TableEx<String,String> tab = new TableEx<String,String>();
    Index<String,String> ind = tab.getIndex("c2", "c0");

    Map<String,String> rec0 = ind.appendNew(3);
    rec0.put("c0", "v00");
    rec0.put("c1", "v01");
    rec0.put("c2", "v02");
    Map<String,String> rec1 = ind.appendNew(3);
    rec1.put("c0", "v10");
    rec1.put("c1", "v11");
    rec1.put("c2", "v12");
    Map<String,String> rec2 = tab.appendNew(3);
    rec2.put("c0", "v20");
    rec2.put("c1", "v21");
    rec2.put("c2", "v22");
    Map<String,String> rec3 = tab.appendNew(3);
    rec3.put("c0", "v30");
    rec3.put("c1", "v31");
    rec3.put("c2", "v32");
    Map<String,String> rec4 = ind.appendNew(3);
    rec4.put("c0", "v00");
    rec4.put("c1", "v11");
    rec4.put("c2", "v22");
    Map<String,String> rec5 = tab.appendNew(3);
    rec5.put("c0", "v01");
    rec5.put("c1", "v31");
    rec5.put("c2", "v22");
    Map<String,String> rec6 = tab.appendNew(3);

    List<Map<String,String>> recL ;

    recL = ind.select(null, null);
    EQUAL(recL.size(), 0);

    recL = ind.select("c2", "v02");
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec0);

    recL = ind.select("c2", "v12");
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec1);

    recL = ind.select("c2", "v22");
    EQUAL(recL.size(), 3);
    TRUE(recL.contains(rec2));
    TRUE(recL.contains(rec4));
    TRUE(recL.contains(rec5));

    recL = ind.select("c2", "v32");
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec3);

    recL = ind.select("c2", "v42");
    EQUAL(recL.size(), 0);

    recL = ind.select("c2", "v52");
    EQUAL(recL.size(), 0);

    recL = ind.select("c2", null);
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec6);

    recL = ind.select("c0", "v00");
    EQUAL(recL.size(), 2);
    TRUE(recL.contains(rec0));
    TRUE(recL.contains(rec4));

    recL = ind.select("c0", "v10");
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec1);

    recL = ind.select("c0", "v20");
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec2);

    recL = ind.select("c0", "v30");
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec3);

    recL = ind.select("c0", "v40");
    EQUAL(recL.size(), 0);

    recL = ind.select("c0", "v50");
    EQUAL(recL.size(), 0);

    recL = ind.select("c0", null);
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec6);

    recL = ind.select("c1", "v01");
    EQUAL(recL.size(), 1);

    recL = ind.select("c1", "v11");
    EQUAL(recL.size(), 2);
    TRUE(recL.contains(rec1));
    TRUE(recL.contains(rec4));

    recL = ind.select("c1", "v21");
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec2);

    recL = ind.select("c1", "v31");
    EQUAL(recL.size(), 2);
    TRUE(recL.contains(rec3));
    TRUE(recL.contains(rec5));

    recL = ind.select("c1", "v41");
    EQUAL(recL.size(), 0);

    recL = ind.select("c1", "v51");
    EQUAL(recL.size(), 0);

    recL = ind.select("c1", null );
    EQUAL(recL.size(), 1);
    TRUE(recL.get(0) == rec6);
  }
}

