/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.beans.dataset;

import java.util.*;
import java.io.*;
import java.lang.reflect.Field;

import org.apache.commons.jexl2.*;

import jp.ossc.nimbus.beans.PropertyAccess;
import jp.ossc.nimbus.service.codemaster.PartUpdate;
import jp.ossc.nimbus.service.codemaster.PartUpdateRecords;
import jp.ossc.nimbus.service.codemaster.CodeMasterUpdateKey;


/**
 * レコードリスト。<p>
 * データセットの繰り返し構造データを表現したBeanで、{@link Record レコード}のリストである。<br>
 * 繰り返し構造の要素であるレコードは、スキーマ定義によって、どのようなレコード（プロパティ名、型など）が繰り返すのかを動的に決定できる。<br>
 * 以下にサンプルコードを示す。<br>
 * <pre>
 *     import jp.ossc.nimbus.beans.dataset.*;
 *     
 *     // レコードリストを生成
 *     RecordList recordList = new RecordList();
 *     
 *     // レコードリストのスキーマを以下のように定義する
 *     //   プロパティ名  型
 *     //        A        int
 *     //        B        java.lang.String
 *     //        C        java.lang.String
 *     recordList.setSchema(
 *         ":A,int\n"
 *             + ":B,java.lang.String\n"
 *             + ":C,java.lang.String"
 *     );
 *     
 *     // レコード1を生成して、値を設定する
 *     Record record1 = recordList.createRecord();
 *     record1.setProperty("A", 1);
 *     record1.setProperty("B", "hoge1");
 *     record1.setProperty("C", "fuga1");
 *     recordList.addRecord(record1);
 *     // レコード2を生成して、値を設定する
 *     Record record2 = recordList.createRecord();
 *     record2.setProperty("A", 2);
 *     record2.setProperty("B", "hoge2");
 *     record2.setProperty("C", "fuga2");
 *     recordList.addRecord(record2);
 * </pre>
 * 
 * @author M.Takata
 */
public class RecordList implements Externalizable, List<Record>, RandomAccess, Cloneable, PartUpdate{
    
    private static final long serialVersionUID = 6399184480196775369L;
    
    /**
     * 主キーによるインデックス名を表す予約名。<p>
     */
    public static final String PRIMARY_KEY_INDEX_NAME = "$PRIMARY_KEY";
    
    /**
     * 条件式中のプロパティ関数名を表す文字。<p>
     */
    protected static final String PROP_FUNCTION_NAME = "prop";
    
    /**
     * レコード名。<p>
     */
    protected String name;
    
    /**
     * スキーマ文字列。<p>
     */
    protected String schema;
    
    /**
     * レコードスキーマ。<p>
     */
    protected RecordSchema recordSchema;
    
    /**
     * レコードのリスト。<p>
     */
    protected List<Record> records = Collections.synchronizedList(new ArrayList<Record>());
    
    /**
     * 条件検索用のインデックスマップ。<p>
     */
    protected Map<String, Expression> conditionIndexMap;
    
    /**
     * 条件検索用の検索結果マップ。<p>
     */
    protected Map<String, List<Record>> conditionIndexSearchMap;
    
    /**
     * キー検索用のインデックスマップ。<p>
     */
    protected Map<String, KeyIndex> keyIndexMap;
    
    /**
     * キー検索用の検索結果マップ。<p>
     */
    protected Map<String, Map<Object, List<Record>>> keyIndexSearchMap;
    
    /**
     * 更新カウント。<p>
     */
    protected int modCount = 0;
    
    protected int[] partUpdateOrderBy;
    
    protected boolean[] partUpdateIsAsc;
    
    /**
     * 未定義のレコードリストを生成する。<p>
     */
    public RecordList(){
    }
    
    /**
     * 未定義のレコードリストを生成する。<p>
     *
     * @param name レコード名
     */
    public RecordList(String name){
        this.name = name;
    }
    
    /**
     * 空のレコードリストを生成する。<p>
     *
     * @param name レコード名
     * @param schema スキーマ文字列
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public RecordList(String name, String schema)
     throws PropertySchemaDefineException{
        this.name = name;
        setSchema(schema);
    }
    
    /**
     * 空のレコードリストを生成する。<p>
     *
     * @param name レコード名
     * @param schema スキーマ
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public RecordList(String name, RecordSchema schema)
     throws PropertySchemaDefineException{
        this.name = name;
        setRecordSchema(schema);
    }
    
    /**
     * レコード名を取得する。<p>
     *
     * @return レコード名
     */
    public String getName(){
        return name;
    }
    
    /**
     * レコード名を設定する。<p>
     *
     * @param name レコード名
     */
    public void setName(String name){
        this.name = name;
    }
    
    /**
     * レコードのスキーマ文字列を設定する。<p>
     *
     * @param schema レコードのスキーマ文字列
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public void setSchema(String schema) throws PropertySchemaDefineException{
        setRecordSchema(RecordSchema.getInstance(schema));
    }
    
    /**
     * レコードのスキーマ文字列を取得する。<p>
     *
     * @return レコードのスキーマ文字列
     */
    public String getSchema(){
        return schema;
    }
    
    /**
     * レコードスキーマを設定する。<p>
     *
     * @param schema レコードスキーマ
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public void setRecordSchema(RecordSchema schema) throws PropertySchemaDefineException{
        if(size() != 0){
            throw new PropertySchemaDefineException("Record already exists.");
        }
        recordSchema = schema;
        this.schema = schema == null ? null : schema.getSchema();
        List<String> primaryKeyNames = null;
        final PropertySchema[] primaryKeys
            = recordSchema.getPrimaryKeyPropertySchemata();
        if(primaryKeys != null){
            for(PropertySchema prop : primaryKeys){
                if(primaryKeyNames == null){
                    primaryKeyNames = new ArrayList<String>();
                }
                primaryKeyNames.add(prop.getName());
            }
        }
        if(primaryKeyNames == null){
            removeKeyIndex(PRIMARY_KEY_INDEX_NAME);
        }else{
            setKeyIndex(
                PRIMARY_KEY_INDEX_NAME,
                primaryKeyNames.toArray(new String[primaryKeyNames.size()])
            );
        }
    }
    
    /**
     * レコードスキーマを置換する。<p>
     *
     * @param schema レコードのスキーマ文字列
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public void replaceSchema(String schema) throws PropertySchemaDefineException{
        replaceRecordSchema(RecordSchema.getInstance(schema));
    }
    
    /**
     * レコードスキーマを置換する。<p>
     *
     * @param schema レコードスキーマ
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public void replaceRecordSchema(RecordSchema schema) throws PropertySchemaDefineException{
        
        if(recordSchema != null && schema != null && size() != 0){
            for(Record record : records){
                record.replaceRecordSchema(schema);
            }
        }
        setRecordSchema(schema);
    }
    
    /**
     * レコードスキーマ文字列を追加する。<p>
     *
     * @param schema レコードのスキーマ文字列
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public void appendSchema(
        String schema
    ) throws PropertySchemaDefineException{
        if(recordSchema == null){
            setSchema(schema);
        }else{
            replaceRecordSchema(
                recordSchema.appendSchema(schema)
            );
        }
    }
    
    /**
     * レコードスキーマを取得する。<p>
     *
     * @return レコードスキーマ
     */
    public RecordSchema getRecordSchema(){
        return recordSchema;
    }
    
    /**
     * 新しいレコードを生成する。<p>
     *
     * @return 新しいレコード
     */
    public Record createRecord(){
        return new Record(recordSchema);
    }
    
    /**
     * 指定されたインデックスのレコードを取得する。<p>
     *
     * @param index インデックス
     * @return レコード
     */
    public Record getRecord(int index){
        return (Record)get(index);
    }
    
    /**
     * レコードを追加する。<p>
     *
     * @param r レコード
     */
    public void addRecord(Record r){
        add(r);
    }
    
    /**
     * 指定されたインデックスにレコードを挿入する。<p>
     *
     * @param index インデックス
     * @param r レコード
     */
    public void addRecord(int index, Record r){
        add(index, r);
    }
    
    /**
     * 指定されたインデックスのレコードを置き換える。<p>
     * 但し、このメソッドで置き換えられたレコードは、インデックス型検索の対象にならない。<br>
     *
     * @param index インデックス
     * @param r レコード
     * @return 置き換えられた古いレコード
     */
    public Record setRecord(int index, Record r){
        return (Record)set(index, r);
    }
    
    /**
     * 指定されたレコードを削除する。<p>
     *
     * @param r レコード
     */
    public void removeRecord(Record r){
        remove(r);
    }
    
    /**
     * 指定されたインデックスのレコードを削除する。<p>
     *
     * @param index インデックス
     * @return 削除されたレコード
     */
    public Record removeRecord(int index){
        return (Record)remove(index);
    }
    
    /**
     * インデックス名を指定して、キーによるインデックスを設定する。<p>
     * 検索には、レコードを蓄積する際に検索するインデックス型検索と、蓄積されたレコードにフルアクセスして検索するリアル型検索がある。<br>
     * このセッターは、インデックス型検索のための条件設定を行うもので、レコードを蓄積する前に設定しておかなければならない。<br>
     * インデックス型検索の利点は、蓄積時に同時に検索が行われるため、実際の検索時には、あらかじめ検索された結果を取り出すだけであるため、高速な検索が可能になる事である。<br>
     * 逆に欠点は、条件を設定される前に追加されたレコードや、プロパティの値を変更したレコードは、インデックスが設定された検索結果には、反映されない。また、条件をあらかじめ設定する必要があるため、条件が動的に変わる場合は、対応できない。そのような場合は、リアル型検索({@link #realSearch(String, Map)})を使用する。<br>
     * このセッターに対応するインデックス型検索は、{@link #searchByKeyIndex(String, Record)}で行う。<br>
     *
     * @param name インデックス名
     * @param propertyNames プロパティ名配列
     * @exception DataSetException インデックスの設定に失敗した場合
     * @see #searchByKeyIndex(String, Record)
     */
    public void setKeyIndex(String name, String... propertyNames) throws DataSetException{
        
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        final int[] propertyIndexes = new int[propertyNames.length];
        for(int i = 0; i < propertyNames.length; i++){
            propertyIndexes[i] = recordSchema.getPropertyIndex(propertyNames[i]);
            if(propertyIndexes[i] == -1){
                throw new DataSetException("No such property " + propertyNames[i]);
            }
        }
        setKeyIndex(name, propertyIndexes);
    }
    
    /**
     * インデックス名を指定して、キーによるインデックスを設定する。<p>
     * 検索には、レコードを蓄積する際に検索するインデックス型検索と、蓄積されたレコードにフルアクセスして検索するリアル型検索がある。<br>
     * このセッターは、インデックス型検索のための条件設定を行うもので、レコードを蓄積する前に設定しておかなければならない。<br>
     * インデックス型検索の利点は、蓄積時に同時に検索が行われるため、実際の検索時には、あらかじめ検索された結果を取り出すだけであるため、高速な検索が可能になる事である。<br>
     * 逆に欠点は、条件を設定される前に追加されたレコードや、プロパティの値を変更したレコードは、蓄積された検索結果には、反映されない。また、条件をあらかじめ設定する必要があるため、条件が動的に変わる場合は、対応できない。そのような場合は、リアル型検索({@link #realSearch(String, Map)})を使用する。<br>
     * このセッターに対応するインデックス型検索は、{@link #searchByKeyIndex(String, Record)}で行う。<br>
     *
     * @param name インデックス名
     * @param propertyIndexes プロパティインデックス配列
     * @exception DataSetException インデックスの設定に失敗した場合
     * @see #searchByKeyIndex(String, Record)
     */
    public void setKeyIndex(String name, int... propertyIndexes) throws DataSetException{
        
        if(keyIndexMap == null){
            keyIndexMap = Collections.synchronizedMap(new HashMap<String, KeyIndex>());
        }
        
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        
        final int fieldSize = recordSchema.getPropertySize();
        for(int index : propertyIndexes){
            if(index >= fieldSize){
                throw new DataSetException("No such property " + index);
            }
        }
        
        final KeyIndex condition = new KeyIndex(propertyIndexes);
        keyIndexMap.put(name, condition);
        
        if(keyIndexSearchMap == null){
            keyIndexSearchMap = Collections.synchronizedMap(new HashMap<String, Map<Object, List<Record>>>());
        }else{
            keyIndexSearchMap.remove(name);
        }
        if(size() != 0){
            analyzeSearchKeyIndex(name);
        }
    }
    
    /**
     * キーによるインデックスのインデックス名配列を取得する。<p>
     *
     * @return インデックス名配列
     */
    public String[] getKeyIndexNames(){
        return keyIndexMap == null || keyIndexMap.size() == 0 ? new String[0]
            : keyIndexMap.keySet().toArray(new String[keyIndexMap.size()]);
    }
    
    /**
     * インデックス名を指定して、インデックスのキーとなるプロパティ名配列を取得する。<p>
     *
     * @param name インデックス名
     * @return プロパティ名配列
     */
    public String[] getKeyIndexPropertyNames(String name){
        final int[] indexes = getKeyIndexPropertyIndexes(name);
        if(indexes == null || recordSchema == null){
            return null;
        }
        
        final String[] propNames = new String[indexes.length];
        for(int i = 0; i < indexes.length; i++){
            propNames[i] = recordSchema.getPropertyName(indexes[i]);
        }
        return propNames;
    }
    
    /**
     * インデックス名を指定して、インデックスのキーとなるプロパティインデックス配列を取得する。<p>
     *
     * @param name インデックス名
     * @return プロパティインデックス配列
     */
    public int[] getKeyIndexPropertyIndexes(String name){
        if(keyIndexMap == null || !keyIndexMap.containsKey(name)){
            return null;
        }
        final KeyIndex condition = keyIndexMap.get(name);
        return condition.propertyIndexes;
    }
    
    /**
     * インデックス名を指定して、キーによるインデックスを削除する。<p>
     *
     * @param name インデックス名
     */
    public void removeKeyIndex(String name){
        if(keyIndexMap != null){
            keyIndexMap.remove(name);
        }
        if(keyIndexSearchMap != null){
            keyIndexSearchMap.remove(name);
        }
    }
    
    /**
     * 全てのキーによるインデックスを削除する。<p>
     */
    public void clearKeyIndex(){
        if(keyIndexMap != null){
            keyIndexMap.clear();
        }
        if(keyIndexSearchMap != null){
            keyIndexSearchMap.clear();
        }
    }
    
    /**
     * インデックス名を指定して、条件式によるインデックスを設定する。<p>
     * 検索には、レコードを蓄積する際に検索するインデックス型検索と、蓄積されたレコードにフルアクセスして検索するリアル型検索がある。<br>
     * このセッターは、インデックス型検索のための条件設定を行うもので、レコードを蓄積する前に設定しておかなければならない。<br>
     * インデックス型検索の利点は、蓄積時に同時に検索が行われるため、実際の検索時には、あらかじめ検索された結果を取り出すだけであるため、高速な検索が可能になる事である。<br>
     * 逆に欠点は、条件を設定される前に追加されたレコードや、プロパティの値を変更したレコードは、蓄積された検索結果には、反映されない。また、条件をあらかじめ設定する必要があるため、条件が動的に変わる場合は、対応できない。そのような場合は、リアル型検索({@link #realSearch(String, Map)})を使用する。<br>
     * このセッターに対応するインデックス型検索は、{@link #searchByConditionIndex(String)}で行う。<br>
     * <p>
     * 条件式は、<a href="http://jakarta.apache.org/commons/jexl/">Jakarta Commons Jexl</a>の式言語を使用する。<br>
     * インデックス型検索では、レコードの列の値を、列名を指定する事で、式中で参照する事ができる。<br>
     * <pre>
     *  例：A == '1' and B &gt;= 3
     * </pre>
     *
     * @param name インデックス名
     * @param condition 条件式
     * @exception DataSetException 条件式が不正な場合
     * @see #searchByConditionIndex(String)
     */
    public void setConditionIndex(String name, String condition)
     throws DataSetException{
        
        if(conditionIndexMap == null){
            conditionIndexMap = Collections.synchronizedMap(new HashMap<String, Expression>());
        }
        
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        
        try{
            JexlEngine jexl = new JexlEngine();
            jexl.setSilent(true);
            Map<String, Object> funcs = new HashMap<String, Object>();
            PropertyAccess propAccess = new PropertyAccess();
            funcs.put(PROP_FUNCTION_NAME, propAccess);
            jexl.setFunctions(funcs);
            conditionIndexMap.put(name, jexl.createExpression(condition));
        }catch(Exception e){
            throw new DataSetException(e);
        }
        
        if(conditionIndexSearchMap == null){
            conditionIndexSearchMap = Collections.synchronizedMap(new HashMap<String, List<Record>>());
        }else{
            conditionIndexSearchMap.remove(name);
        }
        if(size() != 0){
            analyzeSearchConditionIndex(name);
        }
    }
    
    /**
     * 条件式によるインデックスのインデックス名配列を取得する。<p>
     *
     * @return インデックス名配列
     */
    public String[] getConditionIndexNames(){
        return conditionIndexMap == null || conditionIndexMap.size() == 0 ? new String[0]
            : conditionIndexMap.keySet().toArray(new String[conditionIndexMap.size()]);
    }
    
    /**
     * インデックス名を指定して、インデックスの条件式を取得する。<p>
     *
     * @param name インデックス名
     * @return 条件式
     */
    public String getConditionIndex(String name){
        if(conditionIndexMap == null){
            return null;
        }
        Expression exp = conditionIndexMap.get(name);
        return exp == null ? null : exp.getExpression();
    }
    
    /**
     * インデックス名を指定して、条件式によるインデックスを削除する。<p>
     *
     * @param name インデックス名
     */
    public void removeConditionIndex(String name){
        if(conditionIndexMap != null){
            conditionIndexMap.remove(name);
        }
        if(conditionIndexSearchMap != null){
            conditionIndexSearchMap.remove(name);
        }
    }
    
    /**
     * 全ての条件式によるインデックスを削除する。<p>
     */
    public void clearConditionIndex(){
        if(conditionIndexMap != null){
            conditionIndexMap.clear();
        }
        if(conditionIndexSearchMap != null){
            conditionIndexSearchMap.clear();
        }
    }
    
    /**
     * プライマリキーで検索する。<p>
     * プライマリキーキー検索を行うためには、スキーマ定義において、ユニークキーフラグを設定する必要がある。<br>
     *
     * @param key 検索キーレコード
     * @return 検索結果。条件に合致したレコード
     */
    public Record searchByPrimaryKey(Record key){
        final List<Record> list = searchByKeyIndex(PRIMARY_KEY_INDEX_NAME, key);
        return list == null || list.size() == 0 ? null : list.get(0);
    }
    
    /**
     * 指定されたインデックス名のキーインデックス型検索の検索結果を取得する。<p>
     * 蓄積型キー検索を行うためには、{@link #setKeyIndex(String, String[])}や、{@link #setKeyIndex(String, int[])}で検索条件を設定しておく必要がある。<br>
     *
     * @param name インデックス名
     * @param key 検索キーレコード
     * @return 検索結果。条件に合致したレコードの配列
     * @see #setKeyIndex(String, String[])
     * @see #setKeyIndex(String, int[])
     */
    public List<Record> searchByKeyIndex(String name, Record key){
        final List<Record> result = new ArrayList<Record>();
        if(keyIndexSearchMap == null
             || keyIndexSearchMap.size() == 0){
            return result;
        }
        final Map<Object, List<Record>> map = keyIndexSearchMap.get(name);
        if(map == null){
            return result;
        }
        final KeyIndex condition = keyIndexMap.get(name);
        if(condition == null){
            return result;
        }
        Object recKey = condition.createKey(key);
        if(recKey == null){
            return result;
        }
        List<Record> values = map.get(recKey);
        if(values == null){
            return result;
        }
        result.addAll(values);
        return result;
    }
    
    /**
     * 指定されたインデックス名の条件式インデックス型検索の検索結果を取得する。<p>
     * インデックス型検索を行うためには、{@link #setConditionIndex(String, String)}で検索条件を設定しておく必要がある。<br>
     *
     * @param name インデックス名
     * @return 検索結果。条件に合致したレコードのリスト
     * @see #setConditionIndex(String, String)
     */
    public List<Record> searchByConditionIndex(String name){
        final List<Record> result = new ArrayList<Record>();
        if(conditionIndexSearchMap == null
             || conditionIndexSearchMap.size() == 0){
            return result;
        }
        final List<Record> values = conditionIndexSearchMap.get(name);
        if(values == null){
            return result;
        }
        result.addAll(values);
        return result;
    }
    
    /**
     * リアル型キー検索を行う。<p>
     * レコードリストから、指定したプロパティ名の値が、検索キーレコードの値と一致したレコードを検索する。<br>
     *
     * @param key 検索キーレコード
     * @param propertyNames 検索キーとなるプロパティ名
     * @return 検索結果。条件に合致したレコードの配列
     */
    public List<Record> searchByKey(Record key, String... propertyNames){
        final List<Record> result = new ArrayList<Record>();
        if(size() == 0){
            return result;
        }
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        final int[] propertyIndexes = new int[propertyNames.length];
        for(int i = 0; i < propertyNames.length; i++){
            propertyIndexes[i] = recordSchema.getPropertyIndex(propertyNames[i]);
            if(propertyIndexes[i] == -1){
                throw new DataSetException("No such property " + propertyNames[i]);
            }
        }
        return searchByKey(key, propertyIndexes);
    }
    
    /**
     * リアル型キー検索を行う。<p>
     * レコードリストから、指定したプロパティ名の値が、検索キーレコードの値と一致したレコードを検索する。<br>
     *
     * @param key 検索キーレコード
     * @param propertyNames 検索キーとなるプロパティ名
     * @return 検索結果。条件に合致したレコードの配列
     */
    public List<Record> searchByKey(Record key, int... propertyIndexes){
        final List<Record> result = new ArrayList<Record>();
        if(size() == 0){
            return result;
        }
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        for(int i = 0, imax = size(); i < imax; i++){
            Record rd = getRecord(i);
            boolean isMatch = true;
            for(int j = 0; j < propertyIndexes.length; j++){
                Object val1 = key.getProperty(propertyIndexes[j]);
                Object val2 = rd.getProperty(propertyIndexes[j]);
                if(val1 == null && val2 == null){
                    continue;
                }else if(val1 == null && val2 != null
                    || val1 != null && val2 == null
                    || !val1.equals(val2)
                ){
                    isMatch = false;
                    break;
                }
            }
            if(isMatch){
                result.add(rd);
            }
        }
        return result;
    }
    
    /**
     * リアル型条件検索を行う。<p>
     * レコードリストから、条件式に合致するレコードを検索する。<br>
     * また、検索には、レコードを蓄積する際に検索するインデックス型検索と、蓄積されたレコードからリアルに検索するリアル型検索がある。<br>
     * リアル型検索の利点は、条件式中に、動的に変わる変数を指定し、その変数値を引数valueMapで与える事ができる事である。<br>
     * <p>
     * 条件式は、<a href="http://jakarta.apache.org/commons/jexl/">Jakarta Commons Jexl</a>の式言語を使用する。<br>
     * リアル型検索では、レコードの列の値を、列名を指定する事で、式中で参照する事ができるのに加えて、任意の変数名を式中に定義し、その値を引数valueMapで与える事ができる。<br>
     * <pre>
     *  例：A == '1' and B &gt;= 3
     * </pre>
     *
     * @param condition 条件式
     * @param valueMap 条件式中の変数マップ
     * @return 検索結果。条件に合致したレコードのリスト
     * @exception DataSetException 条件式が不正な場合
     */
    public List<Record> searchByCondition(String condition, Map<String, Object> valueMap) throws DataSetException{
        final List<Record> result = new ArrayList<Record>();
        if(size() == 0){
            return result;
        }
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        try{
            JexlEngine jexl = new JexlEngine();
            jexl.setSilent(true);
            Map<String, Object> funcs = new HashMap<String, Object>();
            PropertyAccess propAccess = new PropertyAccess();
            funcs.put(PROP_FUNCTION_NAME, propAccess);
            jexl.setFunctions(funcs);
            final Expression exp
                 = jexl.createExpression(condition);
            final JexlContext context = valueMap == null ? new MapContext()
                : new MapContext(valueMap);
            for(int i = 0, imax = size(); i < imax; i++){
                Record rd = getRecord(i);
                for(int j = 0, jmax = recordSchema.getPropertySize();
                        j < jmax; j++){
                    final PropertySchema prop = recordSchema.getPropertySchema(j);
                    final String propName = prop.getName();
                    context.set(propName, rd.getProperty(propName));
                }
                final Boolean ret = (Boolean)exp.evaluate(context);
                if(ret != null && ret.booleanValue()){
                    result.add(rd);
                }
            }
        }catch(Exception e){
            throw new DataSetException(e);
        }
        return result;
    }
    
    /**
     * 条件インデックス型検索の再解析を行います。<p>
     *
     * @param name インデックス名
     * @exception DataSetException 条件式が不正な場合
     */
    public void analyzeSearchConditionIndex(String name)
     throws DataSetException{
        if(size() == 0
             || conditionIndexMap == null
             || conditionIndexMap.size() == 0
             || !conditionIndexMap.containsKey(name)
        ){
            return;
        }
        final Expression exp = conditionIndexMap.get(name);
        List<Record> values = conditionIndexSearchMap.get(name);
        if(values == null){
            values = new ArrayList<Record>();
            conditionIndexSearchMap.put(name, values);
        }
        values.clear();
        
        try{
            final JexlContext context = new MapContext();
            for(Record record : records){
                for(int i = 0, imax = recordSchema.getPropertySize();
                        i < imax; i++){
                    final PropertySchema prop
                         = recordSchema.getPropertySchema(i);
                    final String propName = prop.getName();
                    context.set(
                        propName,
                        record.getProperty(propName)
                    );
                }
                Object ret = exp.evaluate(context);
                if(!(ret instanceof Boolean)){
                    throw new DataSetException(
                        "Illegal condition : " + exp.getExpression()
                    );
                }
                boolean isMatch = ((Boolean)ret).booleanValue();
                if(isMatch){
                    values.add(record);
                }
            }
        }catch(DataSetException e){
            throw e;
        }catch(Exception e){
            throw new DataSetException(e);
        }
    }
    
    /**
     * キーインデックス型検索の再解析を行います。<p>
     *
     * @param name インデックス名
     * @exception DataSetException 再解析に失敗した場合
     */
    public void analyzeSearchKeyIndex(String name)
     throws DataSetException{
        if(size() == 0
             || keyIndexMap == null
             || keyIndexMap.size() == 0
             || !keyIndexMap.containsKey(name)
        ){
            return;
        }
        final KeyIndex condition = keyIndexMap.get(name);
        Map<Object, List<Record>> map = keyIndexSearchMap.get(name);
        if(map == null){
            map = Collections.synchronizedMap(new HashMap<Object, List<Record>>());
            keyIndexSearchMap.put(name, map);
        }else{
            map.clear();
        }
        boolean isPrimaryKeyIndex = PRIMARY_KEY_INDEX_NAME.equals(name);
        for(Record record : records){
            final Object key = condition.createKey(record);
            if(key == null){
                continue;
            }
            List<Record> values = map.get(key);
            if(values == null){
                values = new ArrayList<Record>();
                map.put(key, values);
            }
            if(isPrimaryKeyIndex && values.size() != 0){
                throw new DataSetException("Dupilicate primary key. " + key);
            }
            values.add(record);
        }
    }
    
    /**
     * 条件式インデックス型検索の蓄積を行います。<p>
     *
     * @param record 蓄積されるレコード
     * @exception DataSetException 条件式が不正な場合
     */
    protected void addSearchConditionIndex(Record record)
     throws DataSetException{
        if(conditionIndexMap == null
             || conditionIndexMap.size() == 0){
            return;
        }
        
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        try{
            for(Map.Entry<String, Expression> entry : conditionIndexMap.entrySet()){
                final String name = entry.getKey();
                final Expression exp = entry.getValue();
                final JexlContext context = new MapContext();
                for(int i = 0, imax = recordSchema.getPropertySize();
                        i < imax; i++){
                    final PropertySchema prop
                         = recordSchema.getPropertySchema(i);
                    final String propName = prop.getName();
                    context.set(
                        propName,
                        record.getProperty(propName)
                    );
                }
                Object ret = exp.evaluate(context);
                if(!(ret instanceof Boolean)){
                    throw new DataSetException(
                        "Illegal condition : " + exp.getExpression()
                    );
                }
                boolean isMatch = ((Boolean)ret).booleanValue();
                if(isMatch){
                    List<Record> values = conditionIndexSearchMap.get(name);
                    if(values == null){
                        values = new ArrayList<Record>();
                        conditionIndexSearchMap.put(name, values);
                    }
                    values.add(record);
                }
            }
        }catch(DataSetException e){
            throw e;
        }catch(Exception e){
            throw new DataSetException(e);
        }
    }
    
    /**
     * 条件式インデックス型検索の蓄積から削除します。<p>
     *
     * @param record 蓄積から削除されるレコード
     * @exception DataSetException 条件式が不正な場合
     */
    protected void removeSearchConditionIndex(Record record)
     throws DataSetException{
        if(conditionIndexSearchMap == null
             || conditionIndexSearchMap.size() == 0){
            return;
        }
        
        if(recordSchema == null){
            throw new DataSetException("Schema not initalize.");
        }
        for(List<Record> list : conditionIndexSearchMap.values()){
            list.remove(record);
        }
    }
    
    /**
     * キーインデックス型検索の蓄積を行います。<p>
     *
     * @param record 蓄積されるレコード
     * @exception DataSetException 条件式が不正な場合
     */
    protected void addSearchKeyIndex(Record record)
     throws DataSetException{
        if(keyIndexMap == null
             || keyIndexMap.size() == 0){
            return;
        }
        for(Map.Entry<String, KeyIndex> entry : keyIndexMap.entrySet()){
            final String name = entry.getKey();
            final boolean isPrimaryKeyIndex = PRIMARY_KEY_INDEX_NAME.equals(name);
            final KeyIndex condition = entry.getValue();
            final Object key = condition.createKey(record);
            if(key == null){
                continue;
            }
            Map<Object, List<Record>> map = keyIndexSearchMap.get(name);
            if(map == null){
                map = Collections.synchronizedMap(new HashMap<Object, List<Record>>());
                keyIndexSearchMap.put(name, map);
            }
            List<Record> values = map.get(key);
            if(values == null){
                values = new ArrayList<Record>();
                map.put(key, values);
            }
            if(isPrimaryKeyIndex && values.size() != 0){
                throw new DataSetException("Dupilicate primary key. " + key);
            }
            values.add(record);
        }
    }
    
    /**
     * キーインデックス型検索の蓄積から削除します。<p>
     *
     * @param record 蓄積から削除されるレコード
     * @exception DataSetException 条件式が不正な場合
     */
    protected void removeSearchKeyIndex(Record record)
     throws DataSetException{
        if(keyIndexMap == null
             || keyIndexMap.size() == 0){
            return;
        }
        for(Map.Entry<String, KeyIndex> entry : keyIndexMap.entrySet()){
            final String name = entry.getKey();
            final KeyIndex condition = entry.getValue();
            final Object key = condition.createKey(record);
            if(key == null){
                continue;
            }
            Map<Object, List<Record>> map = keyIndexSearchMap.get(name);
            if(map == null){
                continue;
            }
            List<Record> values = map.get(key);
            if(values == null){
                continue;
            }
            values.remove(record);
        }
    }
    
    /**
     * 指定されたプロパティ名のプロパティをソートキーにして、昇順でソートする。<p>
     *
     * @param orderBy ソートキーとなるプロパティ名配列
     */
    public void sort(String... orderBy){
        sort(orderBy, null);
    }
    
    /**
     * 指定されたプロパティインデックスのプロパティをソートキーにして、昇順でソートする。<p>
     *
     * @param orderBy ソートキーとなるプロパティインデックス配列
     */
    public void sort(int... orderBy){
        sort(orderBy, null);
    }
    
    /**
     * 指定されたプロパティインデックスのプロパティをソートキーにしてソートする。<p>
     *
     * @param orderBy ソートキーとなるプロパティインデックス配列
     * @param isAsc 昇順ソートする場合はtrue。降順ソートする場合は、false
     */
    public void sort(int[] orderBy, boolean[] isAsc){
        if(records.size() < 2){
            return;
        }
        Collections.sort(records, new RecordComparator(recordSchema, orderBy, isAsc));
    }
    
    /**
     * 指定されたプロパティ名のプロパティをソートキーにしてソートする。<p>
     *
     * @param orderBy ソートキーとなるプロパティ名配列
     * @param isAsc 昇順ソートする場合はtrue。降順ソートする場合は、false
     */
    public void sort(String[] orderBy, boolean[] isAsc){
        if(records.size() < 2){
            return;
        }
        Collections.sort(records, new RecordComparator(orderBy, isAsc));
    }
    
    /**
     * 指定されたプロパティ名のプロパティをソートキーにして、昇順でソートする。<p>
     *
     * @param records ソート対象のレコードのリスト
     * @param orderBy ソートキーとなるプロパティ名配列
     */
    public static void sort(List<Record> records, String... orderBy){
        sort(records, orderBy, null);
    }
    
    /**
     * 指定されたプロパティインデックスのプロパティをソートキーにして、昇順でソートする。<p>
     *
     * @param records ソート対象のレコードのリスト
     * @param orderBy ソートキーとなるプロパティインデックス配列
     */
    public static void sort(List<Record> records, int... orderBy){
        sort(records, orderBy, null);
    }
    
    /**
     * 指定されたプロパティインデックスのプロパティをソートキーにしてソートする。<p>
     *
     * @param records ソート対象のレコードのリスト
     * @param orderBy ソートキーとなるプロパティインデックス配列
     * @param isAsc 昇順ソートする場合はtrue。降順ソートする場合は、false
     */
    public static void sort(List<Record> records, int[] orderBy, boolean[] isAsc){
        if(records.size() < 2){
            return;
        }
        Collections.sort(
            records,
            new RecordComparator(
                records.get(0).getRecordSchema(),
                orderBy,
                isAsc
            )
        );
    }
    
    /**
     * 指定されたプロパティ名のプロパティをソートキーにしてソートする。<p>
     *
     * @param records ソート対象のレコードのリスト
     * @param orderBy ソートキーとなるプロパティ名配列
     * @param isAsc 昇順ソートする場合はtrue。降順ソートする場合は、false
     */
    public static void sort(List<Record> records, String[] orderBy, boolean[] isAsc){
        if(records.size() < 2){
            return;
        }
        Collections.sort(
            records,
            new RecordComparator(
                orderBy,
                isAsc
            )
        );
    }
    
    // java.util.ListのJavaDoc
    @Override
    public int size(){
        return records.size();
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean isEmpty(){
        return records.isEmpty();
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean contains(Object o){
        return records.contains(o);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public Iterator<Record> iterator(){
        return new RecordIterator();
    }
    
    // java.util.ListのJavaDoc
    @Override
    public ListIterator<Record> listIterator(){
        return listIterator(0);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public ListIterator<Record> listIterator(int index){
        return new RecordListIterator(index);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public List<Record> subList(int fromIndex, int toIndex){
        return records.subList(fromIndex, toIndex);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public Object[] toArray(){
        return records.toArray();
    }
    
    // java.util.ListのJavaDoc
    @Override
    public <T> T[] toArray(T[] a){
        return records.<T>toArray(a);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean add(Record rec){
        if(rec == null){
            return false;
        }
        boolean isAdd = records.add(rec);
        addSearchConditionIndex(rec);
        addSearchKeyIndex(rec);
        if(isAdd){
            modCount++;
        }
        return isAdd;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public void add(int index, Record rec){
        if(rec == null){
            return;
        }
        records.add(index, rec);
        addSearchConditionIndex(rec);
        addSearchKeyIndex(rec);
        modCount++;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public Record set(int index, Record rec){
        Record old = null;
        if(index < size()){
            old = records.get(index);
            if(old != null){
                removeSearchConditionIndex(old);
                removeSearchKeyIndex(old);
            }
        }
        old = records.set(index, rec);
        addSearchConditionIndex(rec);
        addSearchKeyIndex(rec);
        return old;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public Record get(int index){
        return records.get(index);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public int indexOf(Object o){
        return records.indexOf(o);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public int lastIndexOf(Object o){
        return records.lastIndexOf(o);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean remove(Object o){
        boolean isRemoved = records.remove(o);
        if(isRemoved){
            removeSearchConditionIndex((Record)o);
            removeSearchKeyIndex((Record)o);
            modCount++;
        }
        return isRemoved;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public Record remove(int index){
        Record old = records.remove(index);
        if(old != null){
            removeSearchConditionIndex(old);
            removeSearchKeyIndex(old);
            modCount++;
        }
        return old;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean containsAll(Collection<?> c){
        return records.containsAll(c);
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean addAll(Collection<? extends Record> c){
        if(c == null || c.size() == 0){
            return false;
        }
        Record[] vals = c.toArray(new Record[c.size()]);
        boolean result = false;
        for(Record val : vals){
            result |= add(val);
        }
        if(result){
            modCount++;
        }
        return result;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean addAll(int index, Collection<? extends Record> c){
        if(c == null || c.size() == 0){
            return false;
        }
        Record[] vals = c.toArray(new Record[c.size()]);
        for(int i = vals.length; --i >= 0;){
            add(index, vals[i]);
        }
        modCount++;
        return true;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean removeAll(Collection<?> c){
        boolean isRemoved = false;
        for(Object o : c){
            isRemoved |= remove(o);
        }
        if(isRemoved){
            modCount++;
        }
        return isRemoved;
    }
    
    // java.util.ListのJavaDoc
    @Override
    public boolean retainAll(Collection<?> c){
        boolean isRemoved = false;
        final Iterator<Record> itr = iterator();
        while(itr.hasNext()){
            Record record = (Record)itr.next();
            if(!c.contains(record)){
                itr.remove();
                isRemoved = true;
            }
        }
        if(isRemoved){
            modCount++;
        }
        return isRemoved;
    }
    
    /**
     * 全てのレコード及び、全てのインデックス型検索結果を削除する。<p>
     */
    @Override
    public void clear(){
        records.clear();
        if(conditionIndexSearchMap != null){
            conditionIndexSearchMap.clear();
        }
        if(keyIndexSearchMap != null){
            keyIndexSearchMap.clear();
        }
        modCount++;
    }
    
    /**
     * サイズをリストの現在のサイズに縮小する。<p>
     * アプリケーションでは、このオペレーションでサイズを最小にすることができる。 <br>
     */
    @SuppressWarnings("unchecked")
    public void trimToSize(){
        Class<?> clazz = null;
        try{
            clazz = Class.forName("java.util.Collections$SynchronizedCollection");
        }catch(ClassNotFoundException e){
            return;
        }
        Object mutex = records;
        try{
            Field field = clazz.getDeclaredField("mutex");
            field.setAccessible(true);
            mutex = field.get(records);
        }catch(IllegalAccessException e){
        }catch(NoSuchFieldException e){
        }catch(SecurityException e){
        }
        ArrayList<Record> list = null;
        try{
            Field field = clazz.getDeclaredField("c");
            field.setAccessible(true);
            list = (ArrayList<Record>)field.get(records);
        }catch(IllegalAccessException e){
            return;
        }catch(NoSuchFieldException e){
            return;
        }catch(SecurityException e){
            return;
        }
        synchronized(mutex){
            list.trimToSize();
        }
    }
    
    /**
     * 全てのレコードを検証する。<p>
     *
     * @return 検証結果。trueの場合、検証成功
     * @exception PropertyGetException プロパティの取得に失敗した場合
     * @exception PropertyValidateException プロパティの検証時に例外が発生した場合
     */
    public boolean validate() throws PropertyGetException, PropertyValidateException{
        for(int i = 0, imax = records.size(); i < imax; i++){
            Record record = records.get(i);
            if(!record.validate()){
                return false;
            }
        }
        return true;
    }
    
    /**
     * レコードリストを複製する。<p>
     *
     * @return 複製したレコードリスト
     */
    public Object clone(){
        return cloneRecordList();
    }
    
    /**
     * 同じスキーマを持ちデータを持たない空のレコードリストを複製する。<p>
     *
     * @return 複製した空のレコードリスト
     */
    public RecordList cloneSchema(){
        RecordList clone = null;
        try{
            clone = (RecordList)super.clone();
            clone.modCount = 0;
            clone.records = Collections.<Record>synchronizedList(new ArrayList<Record>());;
            if(conditionIndexMap != null
                 && conditionIndexMap.size() != 0){
                clone.conditionIndexMap = Collections.synchronizedMap(new HashMap<String, Expression>());
                clone.conditionIndexMap.putAll(conditionIndexMap);
            }
            if(keyIndexMap != null
                 && keyIndexMap.size() != 0){
                clone.keyIndexMap = Collections.synchronizedMap(new HashMap<String, KeyIndex>());
                clone.keyIndexMap.putAll(keyIndexMap);
            }
            if(partUpdateOrderBy != null){
                clone.partUpdateOrderBy = new int[partUpdateOrderBy.length];
                System.arraycopy(
                    partUpdateOrderBy,
                    0,
                    clone.partUpdateOrderBy,
                    0,
                    partUpdateOrderBy.length
                );
            }
            if(partUpdateIsAsc != null){
                clone.partUpdateIsAsc = new boolean[partUpdateIsAsc.length];
                System.arraycopy(
                    partUpdateIsAsc,
                    0,
                    clone.partUpdateIsAsc,
                    0,
                    partUpdateIsAsc.length
                );
            }
        }catch(CloneNotSupportedException e){
            return null;
        }
        return clone;
    }
    
    /**
     * レコードリストを複製する。<p>
     *
     * @return 複製したレコードリスト
     */
    public RecordList cloneRecordList(){
        final RecordList recList = cloneSchema();
        if(size() == 0){
            return recList;
        }
        for(int i = 0; i < records.size(); i++){
            final Record rec = getRecord(i).cloneRecord();
            recList.addRecord(rec);
        }
        return recList;
    }
    
    /**
     *  部分更新時に、指定されたプロパティ名の列をソートキーにしてソートするように設定する。<p>
     *
     * @param orderBy ソートキーとなるプロパティ名配列
     * @param isAsc 昇順ソートする場合はtrue。降順ソートする場合は、false
     */
    public void setPartUpdateSort(String[] orderBy, boolean[] isAsc){
        final int[] propertyIndexes = new int[orderBy.length];
        for(int i = 0; i < orderBy.length; i++){
            propertyIndexes[i] = recordSchema.getPropertyIndex(orderBy[i]);
            if(propertyIndexes[i] == -1){
                throw new DataSetException("No such property " + orderBy[i]);
            }
        }
        setPartUpdateSort(propertyIndexes, isAsc);
    }
    
    /**
     *  部分更新時に、指定されたインデックスのプロパティをソートキーにしてソートするように設定する。<p>
     *
     * @param orderBy ソートキーとなるプロパティインデックス配列
     * @param isAsc 昇順ソートする場合はtrue。降順ソートする場合は、false
     */
    public void setPartUpdateSort(int[] orderBy, boolean[] isAsc){
        if(orderBy == null || orderBy.length == 0){
            throw new DataSetException("Property index array is empty.");
        }
        if(isAsc != null && orderBy.length != isAsc.length){
            throw new DataSetException("Length of property index array and sort flag array is unmatch.");
        }
        
        final int fieldSize = recordSchema.getPropertySize();
        for(int index : orderBy){
            if(index >= fieldSize){
                throw new DataSetException("No such property " + index);
            }
        }
        
        partUpdateOrderBy = orderBy;
        partUpdateIsAsc = isAsc;
    }
    
    /**
     * 更新情報を格納したコードマスタ部分更新レコードを生成する。<p>
     *
     * @param updateType 更新タイプ
     * @param containsValue 更新されたRecordをコードマスタ部分更新レコードに含める場合は、true
     * @return コードマスタ部分更新レコード
     * @exception DataSetException コードマスタ部分更新レコードの生成に失敗した場合
     */
    public PartUpdateRecords createPartUpdateRecords(
        PartUpdateRecords records,
        CodeMasterUpdateKey.UpdateType updateType,
        boolean containsValue
    ) throws DataSetException{
        if(records == null){
            records = new PartUpdateRecords();
        }
        for(Record record : this.records){
            CodeMasterUpdateKey key = record.createCodeMasterUpdateKey();
            key.setUpdateType(updateType);
            if(containsValue){
                records.addRecord(key, record);
            }else{
                records.addRecord(key);
            }
        }
        return records;
    }
    
    /**
     * 指定されたコードマスタ更新キーに該当するレコードを格納した部分更新情報を作成する。<p>
     *
     * @param key コードマスタ更新キー
     * @return 更新レコードを含んだ部分更新情報
     */
    public PartUpdateRecords fillPartUpdateRecords(CodeMasterUpdateKey key){
        PartUpdateRecords records = new PartUpdateRecords();
        records.addRecord(key);
        return fillPartUpdateRecords(records);
    }
    
    /**
     * 指定された部分更新情報に該当するレコードを格納した部分更新情報を作成する。<p>
     *
     * @param records 部分更新情報
     * @return 更新レコードを含んだ部分更新情報
     */
    public PartUpdateRecords fillPartUpdateRecords(PartUpdateRecords records){
        if(records == null || records.size() == 0
             || (!records.containsAdd() && !records.containsUpdate())){
            return records;
        }
        records.setFilledRecord(true);
        Record rec = createRecord();
        final CodeMasterUpdateKey[] keys = records.getKeyArray();
        for(int i = 0; i < keys.length; i++){
            CodeMasterUpdateKey key = (CodeMasterUpdateKey)keys[i];
            final CodeMasterUpdateKey.UpdateType updateType = key.getUpdateType();
            
            records.removeRecord(key);
            
            // 検索用のRecordに検索キーを設定する
            rec.setCodeMasterUpdateKey(key);
            
            // このRecordSetの主キーのみを持ったCodeMasterUpdateKeyに変換する
            key = rec.createCodeMasterUpdateKey(key);
            key.setUpdateType(updateType);
            
            // 削除の場合は、CodeMasterUpdateKeyだけ登録し直す
            if(key.isRemove()){
                records.addRecord(key);
                continue;
            }
            
            // 追加または更新されたRecordを検索する
            final Record searchRec = searchByPrimaryKey(rec);
            records.addRecord(key, searchRec);
        }
        return records;
    }
    
    /**
     * 部分更新情報を取り込んだ、ディープコピーインスタンスを生成する。<p>
     *
     * @param records 部分更新情報
     * @return 部分更新情報を取り込んだ、ディープコピーインスタンス
     */
    public PartUpdate cloneAndUpdate(PartUpdateRecords records){
        final RecordList newRecList = cloneSchema();
        CodeMasterUpdateKey tmpKey = new CodeMasterUpdateKey();
        CodeMasterUpdateKey key = null;
        for(Record oldRecord : this.records){
            tmpKey = oldRecord.createCodeMasterUpdateKey(tmpKey);
            key = records == null ? null : records.getKey(tmpKey);
            Record newRecord = null;
            if(key == null){
                newRecord = oldRecord.cloneRecord();
            }else{
                switch(key.getUpdateType()){
                case ADD:
                case UPDATE:
                    newRecord = (Record)records.removeRecord(key);
                    break;
                case REMOVE:
                default:
                    records.removeRecord(key);
                    continue;
                }
            }
            if(newRecord != null){
                newRecList.addRecord(newRecord);
            }
        }
        if(records != null && records.size() != 0){
            for(Map.Entry<CodeMasterUpdateKey, Object> entry : records.getRecords().entrySet()){
                switch(entry.getKey().getUpdateType()){
                case ADD:
                case UPDATE:
                    final Record record = (Record)entry.getValue();
                    if(record != null){
                        newRecList.addRecord(record);
                    }
                    break;
                default:
                }
            }
        }
        if(partUpdateOrderBy != null && partUpdateOrderBy.length != 0){
            newRecList.sort(partUpdateOrderBy, partUpdateIsAsc);
        }
        
        return newRecList;
    }
    
    public void writeExternal(ObjectOutput out) throws IOException{
        out.writeObject(name);
        out.writeObject(schema);
        if(conditionIndexMap == null || conditionIndexMap.size() == 0){
            out.writeInt(0);
        }else{
            out.writeInt(conditionIndexMap.size());
            final Iterator<Map.Entry<String, Expression>> entries
                 = conditionIndexMap.entrySet().iterator();
            while(entries.hasNext()){
                final Map.Entry<String, Expression> entry = entries.next();
                out.writeObject(entry.getKey());
                out.writeObject(entry.getValue());
            }
        }
        if(keyIndexMap == null || keyIndexMap.size() == 0){
            out.writeInt(0);
        }else{
            out.writeInt(keyIndexMap.size());
            final Iterator<Map.Entry<String, KeyIndex>> entries
                 = keyIndexMap.entrySet().iterator();
            while(entries.hasNext()){
                final Map.Entry<String, KeyIndex> entry = entries.next();
                out.writeObject(entry.getKey());
                out.writeObject(entry.getValue());
            }
        }
        out.writeObject(partUpdateOrderBy);
        out.writeObject(partUpdateIsAsc);
        out.writeInt(records.size());
        for(int i = 0, imax = records.size(); i < imax; i++){
            Record record = records.get(i);
            for(int j = 0, jmax = record.size(); j < jmax; j++){
                out.writeObject(record.getProperty(j));
            }
        }
    }
    
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
        name = (String)in.readObject();
        schema = (String)in.readObject();
        if(schema != null){
            recordSchema = RecordSchema.getInstance(schema);
        }
        int size = in.readInt();
        if(size > 0){
            conditionIndexMap = Collections.synchronizedMap(new HashMap<String, Expression>());
            for(int i = 0; i < size; i++){
                conditionIndexMap.put((String)in.readObject(), (Expression)in.readObject());
            }
        }
        if(conditionIndexMap != null){
            conditionIndexSearchMap = Collections.synchronizedMap(new HashMap<String, List<Record>>());
        }
        size = in.readInt();
        if(size > 0){
            keyIndexMap = Collections.synchronizedMap(new HashMap<String, KeyIndex>());
            for(int i = 0; i < size; i++){
                keyIndexMap.put((String)in.readObject(), (KeyIndex)in.readObject());
            }
        }
        if(keyIndexMap != null){
            keyIndexSearchMap = Collections.synchronizedMap(new HashMap<String, Map<Object, List<Record>>>());
        }
        partUpdateOrderBy = (int[])in.readObject();
        partUpdateIsAsc = (boolean[])in.readObject();
        final int recSize = in.readInt();
        records = Collections.<Record>synchronizedList(new ArrayList<Record>());
        if(recordSchema != null){
            final int propSize = recordSchema.getPropertySize();
            for(int i = 0; i < recSize; i++){
                Record record = createRecord();
                for(int j = 0; j < propSize; j++){
                    record.setProperty(j, in.readObject());
                }
                addRecord(record);
            }
        }
    }
    
    protected class RecordIterator implements Iterator<Record>, Serializable{
        
        private static final long serialVersionUID = 200743372396432511L;
        
        protected int cursor = 0;
        protected int lastRet = -1;
        protected int expectedModCount = modCount;
        
        @Override
        public boolean hasNext(){
            return cursor != size();
        }
        
        @Override
        public Record next(){
            checkForComodification();
            try{
                Record next = get(cursor);
                lastRet = cursor++;
                return next;
            }catch(IndexOutOfBoundsException e){
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
        
        @Override
        public void remove(){
            if(lastRet == -1){
                throw new IllegalStateException();
            }
            checkForComodification();
            
            try{
                RecordList.this.remove(lastRet);
                if(lastRet < cursor){
                    cursor--;
                }
                lastRet = -1;
                expectedModCount = modCount;
            }catch(IndexOutOfBoundsException e){
                throw new ConcurrentModificationException();
            }
        }
        
        final void checkForComodification(){
            if(modCount != expectedModCount){
                throw new ConcurrentModificationException();
            }
        }
    }
    
    protected class RecordListIterator extends RecordIterator
     implements ListIterator<Record>{
        
        private static final long serialVersionUID = 1979810413080499078L;
        
        public RecordListIterator(int index){
            cursor = index;
        }
        
        @Override
        public boolean hasPrevious(){
            return cursor != 0;
        }
        
        @Override
        public Record previous(){
            checkForComodification();
            try{
                int i = cursor - 1;
                Record previous = get(i);
                lastRet = cursor = i;
                return previous;
            }catch(IndexOutOfBoundsException e){
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
        
        @Override
        public int nextIndex(){
            return cursor;
        }
        
        @Override
        public int previousIndex(){
            return cursor - 1;
        }
        
        @Override
        public void set(Record o){
            if(lastRet == -1){
                throw new IllegalStateException();
            }
            checkForComodification();
            
            try{
                RecordList.this.set(lastRet, o);
                expectedModCount = modCount;
            }catch(IndexOutOfBoundsException e){
                throw new ConcurrentModificationException();
            }
        }
        
        @Override
        public void add(Record o){
            checkForComodification();
            
            try{
                RecordList.this.add(cursor++, o);
                lastRet = -1;
                expectedModCount = modCount;
            }catch(IndexOutOfBoundsException e){
                throw new ConcurrentModificationException();
            }
        }
    }
    
    protected static class RecordComparator implements Comparator<Record>{
        
        private String[] propNames;
        private boolean[] isAsc;
        
        public RecordComparator(String[] propNames){
            this(propNames, null);
        }
        
        public RecordComparator(String[] propNames, boolean[] isAsc){
            if(propNames == null || propNames.length == 0){
                throw new DataSetException("Property name array is empty.");
            }
            if(isAsc != null && propNames.length != isAsc.length){
                throw new DataSetException("Length of property name array and sort flag array is unmatch.");
            }
            this.propNames = propNames;
            this.isAsc = isAsc;
        }
        
        public RecordComparator(RecordSchema recordSchema, int[] propIndexes){
            this(recordSchema, propIndexes, null);
        }
        
        public RecordComparator(RecordSchema recordSchema, int[] propIndexes, boolean[] isAsc){
            if(recordSchema == null){
                throw new DataSetException("Schema not initalize.");
            }
            if(propIndexes == null || propIndexes.length == 0){
                throw new DataSetException("Property name array is empty.");
            }
            if(isAsc != null && propIndexes.length != isAsc.length){
                throw new DataSetException("Length of column name array and sort flag array is unmatch.");
            }
            propNames = new String[propIndexes.length];
            for(int i = 0; i < propIndexes.length; i++){
                propNames[i] = recordSchema.getPropertyName(propIndexes[i]);
                if(propNames == null){
                    throw new DataSetException("Property not found : " + propIndexes[i]);
                }
            }
            if(propNames == null || propNames.length == 0){
                throw new DataSetException("Property name array is empty.");
            }
            this.isAsc = isAsc;
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public int compare(Record rd1, Record rd2){
            if(rd1 == null && rd2 == null){
                return 0;
            }
            if(rd1 != null && rd2 == null){
                return 1;
            }
            if(rd1 == null && rd2 != null){
                return -1;
            }
            for(int i = 0; i < propNames.length; i++){
                Object val1 = rd1.getProperty(propNames[i]);
                Object val2 = rd2.getProperty(propNames[i]);
                if(val1 != null && val2 == null){
                    return (isAsc == null || isAsc[i]) ? 1 : -1;
                }
                if(val1 == null && val2 != null){
                    return (isAsc == null || isAsc[i]) ? -1 : 1;
                }
                if(val1 != null && val2 != null){
                    int comp = 0;
                    if(val1 instanceof Comparable){
                        comp = ((Comparable<Object>)val1).compareTo(val2);
                    }else{
                        comp = val1.hashCode() - val2.hashCode();
                    }
                    if(comp != 0){
                        return (isAsc == null || isAsc[i]) ? comp : -1 * comp;
                    }
                }
            }
            return 0;
        }
    }
    
    protected static class KeyIndex implements Serializable{
        
        private static final long serialVersionUID = 2501333319162543622L;
        
        protected int[] propertyIndexes;
        
        public KeyIndex(int[] indexes){
            propertyIndexes = indexes;
        }
        
        public Object createKey(Record record){
            ArrayList<Object> key = null;
            for(int propertyIndex : propertyIndexes){
                Object val = null;
                try{
                    val = record.getProperty(propertyIndex);
                }catch(PropertyGetException e){
                    key = null;
                    break;
                }
                if(key == null){
                    key = new ArrayList<Object>();
                }
                key.add(val);
            }
            if(key != null){
                key.trimToSize();
            }
            return key;
        }
    }
}
