package org.imdst.util;

import java.util.*;
import java.io.*;
import java.net.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ConcurrentHashMap;

import org.batch.util.ILogger;
import org.batch.util.LoggerFactory;
import org.batch.lang.BatchException;
import org.imdst.util.StatusUtil;

import com.sun.mail.util.BASE64DecoderStream;

/**
 * DataNodeが使用するKey-Valueを管理するモジュール.<br>
 * データのファイルストア、登録ログの出力、同期化を行う.<br>
 *
 * @author T.Okuyama
 * @license GPL(Lv3)
 */
public class KeyMapManager extends Thread {

    /**
     * Logger.<br>
     */
    private static ILogger logger = LoggerFactory.createLogger(KeyMapManager.class);

    private KeyManagerValueMap keyMapObj = null;
    private int mapSize = 0;

    private static final String tagStartStr = ImdstDefine.imdstTagStartStr;
    private static final String tagEndStr = ImdstDefine.imdstTagEndStr;
    private static final String tagKeySep = ImdstDefine.imdstTagKeyAppendSep;

    private int putValueMaxSize = new Double(ImdstDefine.saveDataMaxSize * 1.38).intValue();


    // Key系の書き込み、取得
    private Object poolKeyLock = new Object();
    private Object setKeyLock = new Object();
    private Object lockKeyLock = new Object();
    // set,remove系のシンクロオブジェクト
    private static final int parallelSize = 5000;
    private Integer[] parallelSyncObjs = new Integer[KeyMapManager.parallelSize];


    // Tag系の書き込み、取得
    private Object setTagLock = new Object();
    private Object getTagLock = new Object();

    private String keyFilePath = null;
    private String keyFileTmpPath = null;
    private String workKeyFilePath = null;

    FileOutputStream fos = null;
    OutputStreamWriter osw = null;
    BufferedWriter bw = null;

    // 本クラスへのアクセスブロック状態
    private boolean blocking = false;
    // 本クラスの初期化状態
    private boolean initFlg  = false;

    // Mapファイルを書き込む必要有無
    private boolean writeMapFileFlg = false;

    // 起動時にトランザクションログから復旧
    // Mapファイル本体を更新する時間間隔(ミリ秒)(時間間隔の合計 = updateInterval × intervalCount)
    private static int updateInterval = 10000;
    private static int intervalCount = 10;

    // workMap(トランザクションログ)ファイルのデータセパレータ文字列
    private static String workFileSeq = ImdstDefine.keyWorkFileSep;

    // workMap(トランザクションログ)ファイルの文字コード
    private static String workMapFileEnc = ImdstDefine.keyWorkFileEncoding;

    // workMap(トランザクションログ)ファイルのデータセパレータ文字列
    private static String workFileEndPoint = ImdstDefine.keyWorkFileEndPointStr;

    // workMap(トランザクションログ)ファイルをメモリーモードにするかの指定
    private boolean workFileMemory = false;

    // データのメモリーモードかファイルモードかの指定
    private boolean dataMemory = true;

    // Diskモード(ファイルモード)で稼動している場合のデータファイル名
    private String diskModeRestoreFile = null;

    // データへの最終アクセス時間
    private long lastAccess = 0L;

    // データファイルのバキューム実行指定
    private boolean vacuumExec = true;

    // Key値の数とファイルの行数の差がこの数値を超えるとvacuumを行う
    // 行数と1行のデータサイズをかけると不要なデータサイズとなる
    // vacuumStartLimit × (ImdstDefine.saveDataMaxSize * 1.38) = 不要サイズ
    private int vacuumStartLimit = 20000;


    // Vacuum実行時に事前に以下のミリ秒の間アクセスがないと実行許可となる
    private int vacuumExecAfterAccessTime = 30000;

    // データを管理するか、Transaction情報を管理するかを決定
    private boolean dataManege = true;

    // Lockの開始時間の連結文字列
    private String lockKeyTimeSep = "_";

    // ノード復旧中のデータを一時的に蓄積する設定
    private boolean diffDataPoolingFlg = false;
    private CopyOnWriteArrayList diffDataPoolingList = null;
    private Object diffSync = new Object();

    // ノード間でのデータ移動時に削除として蓄積するMap
    private ConcurrentHashMap moveAdjustmentDataMap = null;
    private Object moveAdjustmentSync = new Object();

    // 初期化メソッド
    // Transactionを管理する場合に呼び出す
    public KeyMapManager(String keyMapFilePath, String workKeyMapFilePath, boolean workFileMemory, int keySize, boolean dataMemory, boolean dataManage) throws BatchException {
        this(keyMapFilePath, workKeyMapFilePath, workFileMemory, keySize, dataMemory);
        this.dataManege = dataManage;
    }

    // 初期化メソッド
    public KeyMapManager(String keyMapFilePath, String workKeyMapFilePath, boolean workFileMemory, int keySize, boolean dataMemory) throws BatchException {
        logger.debug("init - start");
        if (!initFlg) {
            initFlg = true;
            this.workFileMemory = workFileMemory;
            this.dataMemory = dataMemory;
            this.mapSize = keySize;
            this.keyFilePath = keyMapFilePath;
            this.keyFileTmpPath = keyMapFilePath + ".tmp";
            this.workKeyFilePath = workKeyMapFilePath;

            FileInputStream keyFilefis = null;
            ObjectInputStream keyFileois = null;

            FileInputStream workKeyFilefis = null;
            InputStreamReader isr = null;
            ObjectInputStream workKeyFileois = null;

            FileReader fr = null;
            BufferedReader br = null;
            String line = null;
            String[] workSplitStrs = null;

            // Diskモード時のファイルパス作成
            if (!this.dataMemory) {
                this.diskModeRestoreFile = keyMapFilePath + ".data";
            }

            // set,remove系のシンクロオブジェクト初期化
            for (int i = 0; i < KeyMapManager.parallelSize; i++) {
                this.parallelSyncObjs[i] = new Integer(i);
            }

            synchronized(poolKeyLock) {
                try {
                    File keyFile = new File(this.keyFilePath);
                    File tmpKeyFile = new File(this.keyFileTmpPath);

                    File workKeyFile = new File(this.workKeyFilePath);

                    // Mapファイルを読み込む必要の有無
                    boolean mapFileRead = true;

                    // tmpMapファイルがある場合はMapファイルへの変更中に前回エラーの可能性があるので読み込む
                    if (tmpKeyFile.exists()) {
                        logger.info("tmpKeyMapFile - Read - start");
                        mapFileRead = false;
                        keyFilefis = new FileInputStream(tmpKeyFile);
                        keyFileois = new ObjectInputStream(keyFilefis);
                        try {
                            if (this.keyMapObj != null) this.keyMapObj.close();
                            this.keyMapObj = (KeyManagerValueMap)keyFileois.readObject();
                            if (!dataMemory) {
                                this.keyMapObj.initNoMemoryModeSetting(this.diskModeRestoreFile);
                            }
                            keyFileois.close();
                            keyFilefis.close();

                            // 古いKeyMapファイルを消しこみ
                            File keyMapObjFile = new File(this.keyFilePath);
                            if (keyMapObjFile.exists()) {
                                keyMapObjFile.delete();
                                keyMapObjFile = null;
                            }
                        // 一時KeyMapファイルをKeyMapファイル名に変更
                        tmpKeyFile.renameTo(new File(this.keyFilePath));
                        logger.info("tmpKeyMapFile - Read - end");
                        } catch (Exception we) {
                            logger.error("tmpKeyMapFile - Read - Error", we);
                            // workKeyファイル読み込み失敗
                            mapFileRead = true;
                            keyFileois = null;
                            keyFilefis = null;

                            if (this.keyMapObj != null) this.keyMapObj.close();
                            this.keyMapObj = null;
                            // tmpKeyMapファイルを消しこみ
                            tmpKeyFile.delete();
                            logger.info("tmpKeyMapFile - Delete - End");
                        }
                    }

                    // KeyMapファイルが存在する場合は読み込み
                    if (mapFileRead = true && keyFile.exists()) {
                        logger.info("KeyMapFile - Read - start");

                        keyFilefis = new FileInputStream(keyFile);
                        keyFileois = new ObjectInputStream(keyFilefis);

                        if (this.keyMapObj != null) this.keyMapObj.close();
                        this.keyMapObj = (KeyManagerValueMap)keyFileois.readObject();
                        if (!dataMemory) {
                            this.keyMapObj.initNoMemoryModeSetting(this.diskModeRestoreFile);
                        }

                        keyFileois.close();
                        keyFilefis.close();
                        logger.info("KeyMapFile - Read - end");
                    } else {
                        logger.info("KeyMapFile - No Exists");
                        // 存在しない場合はMapを作成

                        if (this.keyMapObj != null) this.keyMapObj.close();
                        this.keyMapObj = new KeyManagerValueMap(this.mapSize);
                        if (!dataMemory) {
                            this.keyMapObj.initNoMemoryModeSetting(this.diskModeRestoreFile);
                        }

                    }


                    // WorkKeyMapファイルが存在する場合は読み込み
                    if (workKeyFile.exists()) {
                        logger.info("workKeyMapFile - Read - start");
                        workKeyFilefis = new FileInputStream(workKeyFile);
                        isr = new InputStreamReader(workKeyFilefis , workMapFileEnc);
                        br = new BufferedReader(isr);
                        int counter = 1;

                        while((line=br.readLine())!=null){
                            if ((counter % 5000) == 0) {
                                logger.info("workKeyMapFile - Read - Count =[" + counter + "]");
                            }

                            if (!line.equals("")) {
                                workSplitStrs = line.split(workFileSeq);


                                // データは必ず5つか6つに分解できる
                                if (workSplitStrs.length == 5) {
                                    // 登録データ
                                    if (workSplitStrs[0].equals("+")) {

                                        // トランザクションファイルからデータ登録操作を復元する。その際に登録実行時間もファイルから復元
                                        keyMapObjPutSetTime(workSplitStrs[1], workSplitStrs[2], new Long(workSplitStrs[3]).longValue());
                                    } else if (workSplitStrs[0].equals("-")) {

                                        // トランザクションファイルからデータ削除操作を復元する。その際に削除実行時間もファイルから復元
                                        keyMapObjRemoveSetTime(workSplitStrs[1], new Long(workSplitStrs[3]).longValue());
                                    }
                                } else if (workSplitStrs.length == 6) {
                                    // 登録データ
                                    if (workSplitStrs[0].equals("+")) {

                                        // トランザクションファイルからデータ登録操作を復元する。その際に登録実行時間もファイルから復元
                                        keyMapObjPutSetTime(workSplitStrs[1], workSplitStrs[2] + workFileSeq + workSplitStrs[3], new Long(workSplitStrs[4]).longValue());
                                    } else if (workSplitStrs[0].equals("-")) {

                                        // トランザクションファイルからデータ削除操作を復元する。その際に削除実行時間もファイルから復元
                                        keyMapObjRemoveSetTime(workSplitStrs[1], new Long(workSplitStrs[3]).longValue());
                                    }
                                } else {

                                    // 不正データ
                                    logger.error("workKeyMapFile - Read - Error " + counter + "Line Data = [" + workSplitStrs + "]");
                                }
                            } else {
                                logger.info("workKeyMapFile - Read - Info " + counter + "Line Blank");
                            }
                            counter++;
                        }

                        br.close();
                        isr.close();
                        workKeyFilefis.close();
                        logger.info("workKeyMapFile - Read - end");

                    }

                    this.fos = new FileOutputStream(new File(this.workKeyFilePath), true);
                    this.osw = new OutputStreamWriter(fos , workMapFileEnc);
                    this.bw = new BufferedWriter(osw);
                    this.bw.newLine();
                    this.bw.flush();
                } catch (Exception e) {
                    logger.error("KeyMapManager - init - Error" + e);
                    blocking = true;
                    StatusUtil.setStatusAndMessage(1, "KeyMapManager - init - Error [" + e.getMessage() + "]");
                    throw new BatchException(e);

                }
            }
        }
        logger.debug("init - end");
    }


    /**
     * 定期的にKeyMapを再保存する.<br>
     * システム停止要求を監視して停止依頼があった場合は自身を終了する.<br>
     *
     */ 
    public void run (){
        while(true) {

            if (StatusUtil.getStatus() != 0) {
                logger.info ("KeyMapManager - run - System Shutdown [1] Msg=[" + StatusUtil.getStatusMessage() + "]");
                break;
            }

            try {
                // 1サイクル30秒の停止を規定回数行う(途中で停止要求があった場合は無条件で処理実行)
                for (int count = 0; count < intervalCount; count++) {

                    // システム停止要求を監視
                    if (StatusUtil.getStatus() != 0) {
                        logger.info ("KeyMapManager - run - System Shutdown 2");
                        break;
                    }

                    if (!this.dataManege) {
                        this.autoLockRelease(System.currentTimeMillis());
                    }
                    Thread.sleep(updateInterval);
                }

                logger.info("VacuumCheck - Start");
                //  Vacuum実行の確認
                // データがメモリーではなくかつ、vacuum実行指定がtrueの場合
                if (!dataMemory && vacuumExec == true) {
                    logger.info("vacuumCheck - Start - 1");
                    if ((this.keyMapObj.getAllDataCount() - this.keyMapObj.getKeySize()) > this.vacuumStartLimit) {
                        logger.info("VacuumCheck - Start - 2");

                        // 規定時間アクセスがない
                        if ((System.currentTimeMillis() - this.lastAccess) > this.vacuumExecAfterAccessTime) {
                            logger.info("Vacuum - Start");
                            long vacuumStart = System.currentTimeMillis();

                            synchronized(poolKeyLock) {
                                this.keyMapObj.vacuumData();
                            }

                            long vacuumEnd = System.currentTimeMillis();
                            logger.info("Vacuum - End - VacuumTime [" + (vacuumEnd - vacuumStart) +"] Milli Second");
                        }
                    }
                }
                logger.info("VacuumCheck - End");

            } catch (Exception e) {
                e.printStackTrace();
                logger.error("KeyMapManager - run - Error" + e);
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "KeyMapManager - run - Error [" + e.getMessage() + "]");
                e.printStackTrace();
            }   
        }
    }


    /**
     * キーを指定することでノードをセットする.<br>
     *
     * @param key キー値
     * @param keyNode Value値
     * @param transactionCode
     * @param boolean 移行データ指定
     */
    public void setKeyPair(String key, String keyNode, String transactionCode) throws BatchException {

        if (!blocking) {
            try {
                //logger.debug("setKeyPair - synchronized - start");
                // このsynchroの方法は正しくないきがするが。。。
                synchronized(this.parallelSyncObjs[((keyNode.hashCode() << 1) >>> 1) % KeyMapManager.parallelSize]) {

                    if (this.moveAdjustmentDataMap != null) {
                        synchronized (this.moveAdjustmentSync) {
                            if (this.moveAdjustmentDataMap != null && this.moveAdjustmentDataMap.containsKey(key))
                                this.moveAdjustmentDataMap.remove(key);
                        }
                    }


                    String data = null;
                    if (keyNode.indexOf("-1") == -1) {

                        data = keyNode;
                    } else if (!containsKeyPair(key)) {

                        String[] keyNoddes = keyNode.split("!");
                        data = keyNoddes[0] + "!0";
                    } else {

                        String tmp = keyMapObjGet(key);
                        String[] keyNoddes = keyNode.split("!");

                        if (tmp != null) {

                            String[] tmps = tmp.split("!");
                            data = keyNoddes[0] + "!" + (Long.parseLong(tmps[1]) + 1);
                        } else {

                            data = keyNoddes[0] + "!0";
                        }
                    }


                    keyMapObjPut(key, data);
                    //keyMapObjPut(key,keyNode);
                    // データ操作履歴ファイルに追記
                    if (this.workFileMemory == false) 
                        //this.bw.write(new StringBuffer("+").append(workFileSeq).append(key).append(workFileSeq).append(keyNode).append(workFileSeq).append(System.currentTimeMillis()).append(workFileSeq).append(workFileEndPoint).append("\n").toString());
                        this.bw.write(new StringBuffer("+").append(workFileSeq).append(key).append(workFileSeq).append(data).append(workFileSeq).append(System.currentTimeMillis()).append(workFileSeq).append(workFileEndPoint).append("\n").toString());

                    // Diffモードでかつsync後は再度モードを確認後、addする
                    if (this.diffDataPoolingFlg) {
                        synchronized (diffSync) {
                            if (this.diffDataPoolingFlg) {
                                this.diffDataPoolingList.add("+" + workFileSeq + key + workFileSeq +  data);
                            }
                        }
                    }
                }

                // データの書き込みを指示
                this.writeMapFileFlg = true;

                // Flushは重いので同期外
                if (this.workFileMemory == false) this.bw.flush();

                //logger.debug("setKeyPair - synchronized - end");
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("setKeyPair - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "setKeyPair - Error [" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }
    }


    /**
     * キーを指定することでノードをセットする.<br>
     * 既に登録されている場合は、失敗する。
     *
     * @param key キー値
     * @param keyNode Value値
     * @param transactionCode 
     */
    public boolean setKeyPairOnlyOnce(String key, String keyNode, String transactionCode) throws BatchException {
        return setKeyPairOnlyOnce(key, keyNode, transactionCode, false);
    }


    /**
     * キーを指定することでノードをセットする.<br>
     * 既に登録されている場合は、失敗する。
     *
     * @param key キー値
     * @param keyNode Value値
     * @param transactionCode 
     */
    public boolean setKeyPairOnlyOnce(String key, String keyNode, String transactionCode, boolean moveData) throws BatchException {
        boolean ret = false;
        if (!blocking) {
            try {
                // このsynchroの方法は正しくないきがするが。。。
                synchronized(this.parallelSyncObjs[((key.hashCode() << 1) >>> 1) % KeyMapManager.parallelSize]) {

                    //logger.debug("setKeyPairOnlyOnce - synchronized - start");

                    if(this.containsKeyPair(key)) return ret;

                    if (this.moveAdjustmentDataMap != null) {
                        synchronized (this.moveAdjustmentSync) {
                            if (this.moveAdjustmentDataMap != null && this.moveAdjustmentDataMap.containsKey(key) && moveData == false)
                                this.moveAdjustmentDataMap.remove(key);
                        }
                    }


                    String data = null;
                    if (keyNode.indexOf("-1") == -1) {

                        data = keyNode;
                    } else {

                        String[] keyNoddes = keyNode.split("!");
                        data = keyNoddes[0] + "!0";
                    }

                    keyMapObjPut(key, data);
                    ret = true;

                    // データ操作履歴ファイルに追記
                    if (this.workFileMemory == false) 
                        this.bw.write(new StringBuffer("+").append(workFileSeq).append(key).append(workFileSeq).append(data).append(workFileSeq).append(System.currentTimeMillis()).append(workFileSeq).append(workFileEndPoint).append("\n").toString());

                    if (this.diffDataPoolingFlg) {
                        synchronized (diffSync) {
                            if (this.diffDataPoolingFlg) {
                                this.diffDataPoolingList.add("+" + workFileSeq + key + workFileSeq +  data);
                            }
                        }
                    }
                }

                // データの書き込みを指示
                this.writeMapFileFlg = true;

                // Flushは重いので同期外
                if (this.workFileMemory == false) this.bw.flush();
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("setKeyPairOnlyOnce - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "setKeyPairOnlyOnce - Error [" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }
        return ret;
    }


    // キーを指定することでノードを返す
    public String getKeyPair(String key) {
        String ret = null;
        if (!blocking) {
            ret =  (String)keyMapObjGet(key);
        }
        return ret;
    }

    // キーを指定することでノードを削除する
    public String removeKeyPair(String key, String transactionCode) throws BatchException {
        String ret = null;
        if (!blocking) {
            try {
                // このsynchroの方法は正しくないきがするが。。。
                synchronized(this.parallelSyncObjs[((key.hashCode() << 1) >>> 1) % KeyMapManager.parallelSize]) {

                    ret =  (String)keyMapObjGet(key);

                    // 削除を記録
                    if (this.moveAdjustmentDataMap != null) {
                        synchronized (this.moveAdjustmentSync) {
                            if (this.moveAdjustmentDataMap != null)
                                this.moveAdjustmentDataMap.put(key, "");
                        }
                    }


                    if (ret != null) {
                        keyMapObjRemove(key);
                    } else {
                        return null;
                    }


                    // データ操作履歴ファイルに追記
                    if (this.workFileMemory == false)
                        // データ操作履歴ファイル再保存(登録と合わせるために4つに分割できるようにする)
                        this.bw.write(new StringBuffer("-").append(workFileSeq).append(key).append(workFileSeq).append(" ").append(workFileSeq).append(System.currentTimeMillis()).append(workFileSeq).append(workFileEndPoint).append("\n").toString());

                    if (this.diffDataPoolingFlg) {
                        synchronized (diffSync) {
                            if (this.diffDataPoolingFlg) {
                                this.diffDataPoolingList.add("-" + workFileSeq + key);
                            }
                        }
                    }
                }

                // データの書き込みを指示
                this.writeMapFileFlg = true;

                // Flushは重いので同期外
                if (this.workFileMemory == false) this.bw.flush();
            } catch (Exception e) {
                logger.error("removeKeyPair - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "removeKeyPair - Error[" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }
        return ret;
    }


    // Tagとキーを指定することでTagとキーをセットする
    public void setTagPair(String tag, String key, String transactionCode) throws BatchException {

        if (!blocking) {

            try {
                synchronized(this.setTagLock) {

                    String keyStrs = null;
                    int counter = 0;
                    boolean appendFlg = true;
                    String tagCnv = null;
                    String lastTagCnv = null;
                    int dataPutCounter = 0;
                    boolean firsrtRegist = true;

                    while (true) {

                        tagCnv = tagStartStr + tag + "_" + new Integer(counter).toString() + tagEndStr;

                        if (this.containsKeyPair(tagCnv)) {
                            firsrtRegist = false;
                            keyStrs = this.getKeyPair(tagCnv);
                            String[] workStrs = keyStrs.split(ImdstDefine.setTimeParamSep);
                            keyStrs = workStrs[0];

                            if (keyStrs.indexOf(((String[])key.split(ImdstDefine.setTimeParamSep))[0]) != -1) {

                                // 既に登録済み
                                appendFlg = false;
                                break;
                            }
                        } else {

                            // Tag値のデータそのものがないもしくは、登録連番の中にはデータがない
                            if (counter > 0) {
                                dataPutCounter = counter - 1;
                            } else {
                                // 該当領域にデータを登録する
                                dataPutCounter = counter;
                            }
                            break;
                        }

                        counter++;
                    }


                    // 登録の必要有無で分岐
                    if (appendFlg) {

                        // 登録
                        if (firsrtRegist) {

                            // 初登録
                            this.setKeyPair(tagCnv, key, transactionCode);
                        } else {

                            // 既に別のKeyが登録済みなので、そのキーにアペンドしても良いかを確認
                            // 登録時間の長さ(16)もプラス
                            if ((keyStrs.getBytes().length + tagKeySep.getBytes().length + key.getBytes().length + 16) >= ImdstDefine.saveDataMaxSize) {

                                // 既にキー値が最大のサイズに到達しているので別のキーを生み出す
                                counter++;

                                tagCnv = tagStartStr + tag + "_" + (dataPutCounter + 1) + tagEndStr;
                                this.setKeyPair(tagCnv, key, transactionCode);
                            } else{

                                // アペンド
                                tagCnv = tagStartStr + tag + "_" + dataPutCounter + tagEndStr;

                                keyStrs = keyStrs + tagKeySep + key;
                                this.setKeyPair(tagCnv, keyStrs, transactionCode);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                logger.error("setTagPair - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "setTagPair - Error [" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }
    }


    // Tagを指定することでKeyリストを返す
    public String getTagPair(String tag) {
        String keyStrs = "";
        String[] setTimeSplitWork = null;

        boolean isMatch = false;
        StringBuffer tmpBuf = new StringBuffer();
        String tmpStr = null;
        String tmpSep = "";
        String lastSetTime = "";

        if (!blocking) {

            int counter = 0;
            // Tagのキー値を連結
            while(true) {

                String tagCnv = tagStartStr + tag + "_" + counter + tagEndStr;

                if (this.containsKeyPair(tagCnv)) {

                    tmpStr = (String)this.getKeyPair(tagCnv);

                    if (tmpStr != null) {

                        isMatch = true;
                        tmpBuf.append(tmpSep);

                        setTimeSplitWork = tmpStr.split(ImdstDefine.setTimeParamSep);

                        if (setTimeSplitWork.length > 1) lastSetTime = setTimeSplitWork[1];

                        tmpBuf.append(setTimeSplitWork[0]);
                        tmpSep = tagKeySep;
                    } else {

                        if (!isMatch) {

                            keyStrs = null;
                        } else {

                            keyStrs = tmpBuf.append(ImdstDefine.setTimeParamSep).append(lastSetTime).toString();
                        }
                        break;
                    }
                } else {

                    if (!isMatch) {
                        keyStrs = null;
                    } else {
                        keyStrs = tmpBuf.append(ImdstDefine.setTimeParamSep).append(lastSetTime).toString();
                    }
                    break;
                }
                counter++;
            }
        }
        return keyStrs;
    }


    // キーを指定することでキーが存在するかを返す
    public boolean containsKeyPair(String key) {
        boolean ret = false;
        if (!blocking) {
            ret =  this.keyMapObj.containsKey(key);
        }
        return ret;
    }

    // キーを指定することでキーが存在するかを返す
    public boolean containsTagPair(String tag) {
        boolean ret = false;
        if (!blocking) {
            String tagCnv = tagStartStr + tag + "_0" + tagEndStr;

            ret =  this.containsKeyPair(tagCnv);
        }
        return ret;
    }

    /**
     * Lockの取得を行う.<br>
     * 
     * @param key Key値
     * @param transactionCode 取得時に使用するTransactionCode
     * @param lockingTime Lock継続時間
     * @return String 成功時はtransactionCode、失敗時はnull
     */
    public String locking (String key, String transactionCode, int lockingTime) throws BatchException {
        if (!blocking) {
            try {
                String saveTransactionStr =  null;
                synchronized(this.lockKeyLock) {

                    if (this.containsKeyPair(key)) return null;
                    if (lockingTime == 0) {
                        saveTransactionStr = transactionCode + this.lockKeyTimeSep + new Long(Long.MAX_VALUE).toString();
                    } else {
                        saveTransactionStr = transactionCode + this.lockKeyTimeSep + new Long(System.currentTimeMillis() + (lockingTime * 1000)).toString();
                    }
                    keyMapObjPut(key, saveTransactionStr);
                }
                this.writeMapFileFlg = true;

                if (workFileMemory == false) {

                    // データ格納場所記述ファイル再保存
                    this.bw.write(new StringBuffer("+").append(workFileSeq).append(key).append(workFileSeq).append(saveTransactionStr).append(workFileSeq).append(System.currentTimeMillis()).append(workFileSeq).append(workFileEndPoint).append("\n").toString());
                    this.bw.flush();
                }

                if (this.diffDataPoolingFlg) {
                    synchronized (diffSync) {
                        if (this.diffDataPoolingFlg) {
                            this.diffDataPoolingList.add("+" + workFileSeq + key + workFileSeq +  saveTransactionStr);
                        }
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
                logger.error("locking - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "locking - Error [" + e.getMessage() + "]");

                throw new BatchException(e);
            }
        }
        return transactionCode;
    }


    /**
     * Lockの開放を行う.<br>
     * 
     * @param key Key値
     * @param transactionCode 取得時に使用するTransactionCode
     * @return String 成功時はtransactionCode、失敗時はnull
     */
    public String removeLock (String key, String transactionCode) throws BatchException {

        String ret = null;
        if (!blocking) {
            try {
                synchronized(this.lockKeyLock) {
                    if (!this.containsKeyPair(key)) return transactionCode;
                    if (!(((String[])((String)this.keyMapObjGet(key)).split(this.lockKeyTimeSep))[0]).equals(transactionCode)) return null;
                    ret = ((String[])((String)this.keyMapObj.remove(key)).split(this.lockKeyTimeSep))[0];
                    this.keyMapObj.setKLastDataChangeTime(System.currentTimeMillis());

                    this.lastAccess = System.currentTimeMillis();
                }

                // データの書き込みを指示
                this.writeMapFileFlg = true;

                if (workFileMemory == false) {
                    // データ格納場所記述ファイル再保存(登録と合わせるために4つに分割できるようにする)
                    this.bw.write(new StringBuffer("-").append(workFileSeq).append(key).append(workFileSeq).append(" ").append(workFileSeq).append(System.currentTimeMillis()).append(workFileSeq).append(workFileEndPoint).append("\n").toString());
                    this.bw.flush();
                }

                if (this.diffDataPoolingFlg) {
                    synchronized (diffSync) {
                        if (this.diffDataPoolingFlg) {
                            this.diffDataPoolingList.add("-" + workFileSeq + key);
                        }
                    }
                }

            } catch (Exception e) {
                logger.error("removeLock - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "removeLock - Error[" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }

        return ret;
    }


    /**
     * Lockの状況を確認する.<br>
     * 
     * @param key Key値
     * @return boolean true:ロックされている false:ロックされていない
     */
    public boolean isLock (String key) {
        return this.containsKeyPair(key);
    }


    /**
     * Lockの状況を確認する.<br>
     * 
     * @param key Key値
     * @return String TransactionCode
     */
    public String getLockedTransactionCode (String key) {
        synchronized(this.lockKeyLock) {
            if (!this.containsKeyPair(key)) return null;
            return ((String[])((String)this.keyMapObjGet(key)).split(this.lockKeyTimeSep))[0];
        }
    }


    /**
     * Lockの自動開放メソッド.<br>
     * 引数の時間だけ経過しているLockは強制的に開放される<br>
     *
     * @param time 現在ミリ秒
     */
    public void autoLockRelease(long time) throws BatchException {
        synchronized(this.setKeyLock) {
            synchronized(this.lockKeyLock) {
                try {
                    Object key = null;

                    Set set = this.keyMapObj.keySet();
                    Iterator iterator = set.iterator();

                    String[] keyList = new String[this.keyMapObj.size()];
                    for (int idx = 0; idx < keyList.length; idx++) {
                        keyList[idx] = (String)iterator.next();
                    }

                    for (int idx = 0; idx < keyList.length; idx++) {
                        String transactionLine = (String)this.keyMapObj.get(keyList[idx]);
                        String[] codeList = transactionLine.split(this.lockKeyTimeSep);
                        if(Long.parseLong(codeList[1]) < time) {

                            this.keyMapObj.remove(keyList[idx]);
                            // データの書き込みを指示
                            this.writeMapFileFlg = true;

                            if (workFileMemory == false) {

                                // データ格納場所記述ファイル再保存(登録と合わせるために4つに分割できるようにする)
                                this.bw.write(new StringBuffer("-").append(workFileSeq).append(keyList[idx]).append(workFileSeq).append(" ").append(workFileSeq).append(System.currentTimeMillis()).append(workFileSeq).append(workFileEndPoint).append("\n").toString());
                                this.bw.flush();
                            }

                            if (this.diffDataPoolingFlg) {
                                synchronized (diffSync) {
                                    if (this.diffDataPoolingFlg) {
                                        this.diffDataPoolingList.add("-" + workFileSeq + keyList[idx]);
                                    }
                                }
                            }
                        }
                    }
                } catch(Exception e) {
                    logger.error("autoLockRelease - Error");
                    blocking = true;
                    StatusUtil.setStatusAndMessage(1, "autoLockRelease - Error[" + e.getMessage() + "]");
                    throw new BatchException(e);
                }

            }
        }
    }


    /**
     * keyMapObjに対するアクセスメソッド.<br>
     * TODO:登録のValueのサイズが最大サイズを超えている場合は無条件で登録しない.<br>
     */
    private void keyMapObjPut(String key, String val) {
        if (val.length() < putValueMaxSize) {
            this.keyMapObj.put(key, val);
            this.keyMapObj.setKLastDataChangeTime(System.currentTimeMillis());
        }
        this.lastAccess = System.currentTimeMillis();
    }

    private void setLastDataChangeTime() {
        this.keyMapObj.setKLastDataChangeTime(System.currentTimeMillis());
    }
    /**
     * keyMapObjに対するアクセスメソッド.<br>
     * 更新時間を登録しない.<br>
     * TODO:登録のValueのサイズが最大サイズを超えている場合は無条件で登録しない.<br>
     */
    private void keyMapObjPutNoChange(String key, String val) {
        if (val.length() < putValueMaxSize) {
            this.keyMapObj.put(key, val);
        }
        this.lastAccess = System.currentTimeMillis();
    }

    /**
     * keyMapObjに対するアクセスメソッド.<br>
     * 任意の更新時間をセットする.<br>
     * TODO:登録のValueのサイズが最大サイズを超えている場合は無条件で登録しない.<br>
     */
    private void keyMapObjPutSetTime(String key, String val, long execTime) {
        if (val.length() < putValueMaxSize) {
            this.keyMapObj.put(key, val);
            this.keyMapObj.setKLastDataChangeTime(execTime);
        }
        this.lastAccess = System.currentTimeMillis();
    }


    /**
     * keyMapObjに対するアクセスメソッド.<br>
     * get<br>
     */
    private String keyMapObjGet(String key) {
        this.lastAccess = System.currentTimeMillis();
        return (String)this.keyMapObj.get(key);

    }

    /**
     * keyMapObjに対するアクセスメソッド.<br>
     * 
     */
    private void keyMapObjRemove(String key) {
        this.keyMapObj.remove(key);
        this.keyMapObj.setKLastDataChangeTime(System.currentTimeMillis());
        this.lastAccess = System.currentTimeMillis();
    }

    /**
     * keyMapObjに対するアクセスメソッド.<br>
     * 更新時間をセットしない
     */
    private void keyMapObjRemoveNoChange(String key) {
        this.keyMapObj.remove(key);
        this.lastAccess = System.currentTimeMillis();
    }


    /**
     * keyMapObjに対するアクセスメソッド.<br>
     *  任意の更新時間をセットする.<br>
     */
    private void keyMapObjRemoveSetTime(String key, long execTime) {
        this.keyMapObj.remove(key);
        this.keyMapObj.setKLastDataChangeTime(execTime);
        this.lastAccess = System.currentTimeMillis();
    }


    public void diffDataMode(boolean flg, PrintWriter pw) {
        synchronized (diffSync) {

            if (flg) {
                this.diffDataPoolingList = new CopyOnWriteArrayList();
            } else {
                this.diffDataPoolingList = null;
            }
            this.diffDataPoolingFlg = flg;
            try {
                pw.println("1");
                pw.flush();
            } catch (Exception e) {
            }
        }
    }

    public void diffDataMode(boolean flg) {
        synchronized (diffSync) {

            if (flg) {
                this.diffDataPoolingList = new CopyOnWriteArrayList();
            } else {
                this.diffDataPoolingList = null;
            }
            this.diffDataPoolingFlg = flg;
        }
    }

    // 強制的に差分モードをOffにする
    public void diffDataModeOff() {

        this.diffDataPoolingList = null;
        this.diffDataPoolingFlg = false;
    }



    // 引数で渡されてストリームに対しkeyMapObjを書き出す
    public void outputKeyMapObj2Stream(PrintWriter pw) throws BatchException {
        if (!blocking) {
            try {
                synchronized(poolKeyLock) {
                    logger.info("outputKeyMapObj2Stream - synchronized - start");
                    String allDataSep = "";
                    StringBuffer allDataBuf = new StringBuffer();

                    // keyMapObjの全内容を1行文字列として書き出し
                    Set entrySet = this.keyMapObj.entrySet();

                    int printLineCount = 0;
                    // 一度に送信するデータ量を算出。空きメモリの50%を使用する
                    int maxLineCount = new Double((JavaSystemApi.getRuntimeFreeMem("") * 0.5) / ImdstDefine.saveDataMaxSize).intValue();
                    //int maxLineCount = 500;
                    if (entrySet.size() > 0) {
                        printLineCount = new Double(entrySet.size() / maxLineCount).intValue();
                        if (entrySet.size() % maxLineCount > 0) {
                            printLineCount = printLineCount + 1;
                        }
                    }

                    // 送信データ行数を送信
                    pw.println(printLineCount);
                    pw.flush();

                    Iterator entryIte = entrySet.iterator(); 

                    int counter = 0;
                    while(entryIte.hasNext()) {
                        Map.Entry obj = (Map.Entry)entryIte.next();
                        String key = null;

                        key = (String)obj.getKey();

                        // 全てのデータを送る
                        allDataBuf.append(allDataSep);
                        allDataBuf.append(key);
                        allDataBuf.append(workFileSeq);
                        allDataBuf.append(this.keyMapObjGet(key));
                        allDataSep = ImdstDefine.imdstConnectAllDataSendDataSep;

                        counter++;
                        if (counter > (maxLineCount - 1)) {
                            pw.println(allDataBuf.toString());
                            allDataBuf = new StringBuffer();
                            counter = 0;
                        }
                    }

                    pw.println(allDataBuf.toString());
                }
                //logger.debug("outputKeyMapObj2Stream - synchronized - end");
            } catch (Exception e) {
                logger.error("outputKeyMapObj2Stream - Error =[" + e.getMessage() + "]");
                //blocking = true;
                //StatusUtil.setStatusAndMessage(1, "outputKeyMapObj2Stream - Error [" + e.getMessage() + "]");
                //throw new BatchException(e);
            }
        }
    }


    // 引数で渡されてストリームに対し復旧中の差分データを書き出す
    // 
    public void outputDiffKeyMapObj2Stream(PrintWriter pw, BufferedReader br) throws BatchException {
        if (!blocking) {
            try {

                synchronized(poolKeyLock) {
                    synchronized(diffSync) {
                        logger.info("outputDiffKeyMapObj2Stream - synchronized - start");
                        String allDataSep = "";
                        StringBuffer allDataBuf = new StringBuffer();

                        // 差分データの全内容を1行文字列として書き出し

                        for (int i = 0; i < this.diffDataPoolingList.size(); i++) {

                            allDataBuf.append(allDataSep);
                            allDataBuf.append(this.diffDataPoolingList.get(i));
                            allDataSep = ImdstDefine.imdstConnectAllDataSendDataSep;
                        }

                        pw.println(allDataBuf.toString());
                        pw.flush();
                        this.diffDataPoolingList = null;
                        allDataBuf = null;
                        // 取り込み完了をまつ
                        String outputRet = br.readLine();

                        if (outputRet == null || !outputRet.equals("1")) {
                            throw new Exception("outputDiffKeyMapObj2Stream - Error Ret=[" + outputRet + "]");
                        }
                        // 終了受信を送信
                        pw.println("1");
                        pw.flush();
                        this.diffDataMode(false);
                    }
                }
                //logger.debug("outputDiffKeyMapObj2Stream - synchronized - end");
            } catch (Exception e) {
                logger.error("outputDiffKeyMapObj2Stream - Error [" + e.getMessage() + "]");
                this.diffDataMode(false);
                //blocking = true;
                //StatusUtil.setStatusAndMessage(1, "outputDiffKeyMapObj2Stream - Error [" + e.getMessage() + "]");
                //throw new BatchException(e);
            }
        }
    }

    // 引数で渡されてストリームからの値でデータを作成する
    public void inputKeyMapObj2Stream(BufferedReader br, PrintWriter pw, int dataLineCount) throws BatchException {
        if (!blocking) {
            try {
                int i = 0;
                String[] oneDatas = null;
                boolean setDataExec = false;
                logger.info("inputKeyMapObj2Stream - synchronized - start");
                synchronized(this.poolKeyLock) {
                    // 事前に不要なファイルを削除

                    // KeyMapファイルを消しこみ
                    File keyMapObjFile = new File(this.keyFilePath);
                    if (keyMapObjFile.exists()) {
                        keyMapObjFile.delete();
                        keyMapObjFile = null;
                    }

                    // TmpKeyMapファイルを消しこみ
                    File keyMapTmpObjFile = new File(this.keyFileTmpPath);
                    if (keyMapTmpObjFile.exists()) {
                        keyMapTmpObjFile.delete();
                        keyMapTmpObjFile = null;
                    }

                    // WorkKeyMapファイルを消しこみ
                    File workKeyMapObjFile = new File(this.workKeyFilePath);
                    if (workKeyMapObjFile.exists()) {
                        if (this.bw != null) this.bw.close();
                        if (this.osw != null) this.osw.close();
                        if (this.fos != null) this.fos.close();
                        workKeyMapObjFile.delete();
                    }

                    // Disk時はデータファイルを削除
                    if (this.dataMemory) {
                        if(this.keyMapObj != null) this.keyMapObj.close();
                    } else {
                        this.keyMapObj.deleteMapDataFile();
                    }


                    this.keyMapObj = new KeyManagerValueMap(this.mapSize);
                    if (!dataMemory) {
                        this.keyMapObj.initNoMemoryModeSetting(this.diskModeRestoreFile);
                    }

                    // WorkKeyMapファイル用のストリームを作成
                    this.fos = new FileOutputStream(new File(this.workKeyFilePath));
                    this.osw = new OutputStreamWriter(fos , workMapFileEnc);
                    this.bw = new BufferedWriter(osw);

                    // 取り込み開始
                    for (int idx = 0; idx < dataLineCount; idx++) {

                        // 最終更新日付変えずに全てのデータを登録する
                        // ストリームからKeyMapの1ラインを読み込み、パース後1件づつ登録
                        String allDataStr = br.readLine();

                        String[] allDataLines = allDataStr.split(ImdstDefine.imdstConnectAllDataSendDataSep);

                        for (i = 0; i < allDataLines.length; i++) {
                            if (!allDataLines[i].trim().equals("")) {
                                oneDatas = allDataLines[i].split(workFileSeq);
                                if (oneDatas.length == 2) {
                                    setDataExec = true;
                                    this.keyMapObjPutNoChange(oneDatas[0], oneDatas[1]);
                                } else if (oneDatas.length == 3) {
                                    setDataExec = true;
                                    this.keyMapObjPutNoChange(oneDatas[0], oneDatas[1] + workFileSeq + oneDatas[2]);
                                }
                            }
                        }
                    }

                    // 全てのデータを取り込んだタイミングで最終更新時間を変更
                    // 1件でも取り込んでいる場合のみ
                    if (setDataExec == true) this.setLastDataChangeTime();
                    pw.println("1");
                    pw.flush();
                }
                logger.info("inputKeyMapObj2Stream - synchronized - end");
            } catch (Exception e) {
                try {
                    pw.println("-1");
                    pw.flush();
                } catch (Exception e2) {
                }
                logger.error("writeKeyMapObj2Stream - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "writeKeyMapObj2Stream - Error [" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }
    }


    // 引数で渡されてストリームからの値でデータを作成する
    // 差分データの登録なので、データファイルの消しこみなどはせずに、追加で登録、削除していく
    public void inputDiffKeyMapObj2Stream(BufferedReader br, PrintWriter pw) throws BatchException {
        if (!blocking) {
            try {
                int i = 0;
                String[] oneDatas = null;
                logger.info("inputDiffKeyMapObj2Stream - synchronized - start");
                synchronized(this.poolKeyLock) {


                    // 最終更新日付変えずに全てのデータを登録する
                    // ストリームからKeyMapの1ラインを読み込み、パース後1件づつ登録
                    String allDataStr = br.readLine();

                    String[] allDataLines = allDataStr.split(ImdstDefine.imdstConnectAllDataSendDataSep);

                    for (i = 0; i < allDataLines.length; i++) {
                        if (!allDataLines[i].trim().equals("")) {
                            oneDatas = allDataLines[i].split(workFileSeq);

                            // 最後のデータのみ更新日を変更
                            if (allDataLines.length == (i + 1)) {

                                if (oneDatas[0].equals("+")) {

                                    if (oneDatas.length == 3) {
                                        this.keyMapObjPut(oneDatas[1], oneDatas[2]);
                                    } else if (oneDatas.length == 4) {
                                        this.keyMapObjPut(oneDatas[1], oneDatas[2] + workFileSeq + oneDatas[3]);
                                    }
                                } else if (oneDatas[0].equals("-")) {

                                    this.keyMapObjRemove(oneDatas[1]);
                                }
                            } else {
                                if (oneDatas[0].equals("+")) {

                                    if (oneDatas.length == 3) {
                                        this.keyMapObjPutNoChange(oneDatas[1], oneDatas[2]);
                                    } else if (oneDatas.length == 4) {
                                        this.keyMapObjPutNoChange(oneDatas[1], oneDatas[2] + workFileSeq + oneDatas[3]);
                                    }
                                } else if (oneDatas[0].equals("-")) {
                                    this.keyMapObjRemoveNoChange(oneDatas[1]);
                                }
                            }
                        }
                    }


                    // 全てのデータをトランザクションログモードがONの場合のみ書き出し
                    // ファイルストリームは既にinputKeyMapObj2Streamメソッド内で作成されている想定
                    if (this.workFileMemory == false) {

                        // keyMapObjの全内容を1行文字列として書き出し
                        Set entrySet = this.keyMapObj.entrySet();
                        Iterator entryIte = entrySet.iterator(); 

                        long writeCurrentTime = this.lastAccess;
                        String writeKey = null;

                        while(entryIte.hasNext()) {

                            Map.Entry obj = (Map.Entry)entryIte.next();
                            writeKey = (String)obj.getKey();

                            this.bw.write(new StringBuffer("+").
                                          append(workFileSeq).
                                          append(writeKey).
                                          append(workFileSeq).
                                          append(this.keyMapObjGet(writeKey)).
                                          append(workFileSeq).
                                          append(writeCurrentTime).
                                          append(workFileSeq).
                                          append(workFileEndPoint).
                                          append("\n").
                                          toString());
                        }
                        this.bw.flush();
                    }
                    // 取り込み終了を送信
                    pw.println("1");
                    pw.flush();
                }
                logger.info("inputKeyMapObj2Stream - synchronized - end");
            } catch (Exception e) {
                logger.error("writeKeyMapObj2Stream - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "writeKeyMapObj2Stream - Error [" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }
    }


    // 引数で渡されてストリームに対しKey値を書き出す
    // 書き出すKey値は引数のrulesStrを使用して割り出した値がmatchNoとマッチしないデータ
    // 終了時は-1が返る
    public void outputNoMatchKeyMapKey2Stream(PrintWriter pw, int matchNo, String rulesStr) throws BatchException {
        if (!blocking) {
            try {
                String[] rules = null;
                int[] rulesInt = null;
                rules = rulesStr.split(",");
                rulesInt = new int[rules.length];

                for (int i = 0; i < rules.length; i++) {
                    rulesInt[i] = Integer.parseInt(rules[i]);
                }


                String allDataSep = "";
                StringBuffer allDataBuf = new StringBuffer();

                // keyMapObjの全内容を1行文字列として書き出し
                Set entrySet = this.keyMapObj.entrySet();

                int printLineCount = 0;
                // 一度に送信するデータ量を算出。空きメモリの10%を使用する
                int maxLineCount = new Double((JavaSystemApi.getRuntimeFreeMem("") * 0.1) / (ImdstDefine.saveKeyMaxSize * 1.38 + ImdstDefine.saveDataMaxSize * 1.38)).intValue();
                //int maxLineCount = 500;
                if (entrySet.size() > 0) {
                    printLineCount = new Double(entrySet.size() / maxLineCount).intValue();
                    if (entrySet.size() % maxLineCount > 0) {
                        printLineCount = printLineCount + 1;
                    }
                }


                Iterator entryIte = entrySet.iterator(); 

                int counter = 0;
                while(entryIte.hasNext()) {
                    Map.Entry obj = (Map.Entry)entryIte.next();
                    String key = null;
                    String sendTagKey = null;
                    boolean sendFlg = true;
                    boolean tagFlg = false;

                    key = (String)obj.getKey();
                    if (key.indexOf(ImdstDefine.imdstTagStartStr) == 0) {
                        // タグの場合は分解して
                        tagFlg = true;
                        int startIdx = 15;
                        int endIdx = key.lastIndexOf(ImdstDefine.imdstTagEndStr);
         
                        String checkKey = key.substring(startIdx, endIdx);
                        sendTagKey = key.substring(startIdx, endIdx);

                        int lastIdx = checkKey.lastIndexOf("=");

                        // マッチするか確認
                        // タグの対象データ判定はタグ値に連結されているインデックス文字列や、左右のプレフィックス文字列をはずして判定する
                        for (int idx = 0; idx < rulesInt.length; idx++) {

                            if (DataDispatcher.isRuleMatchKey(checkKey.substring(0, lastIdx+1), rulesInt[idx], matchNo)) {
                                sendFlg = false;
                                break;
                            }
                        }

                    } else {

                        // マッチするか確認
                        for (int idx = 0; idx < rulesInt.length; idx++) {

                            if (DataDispatcher.isRuleMatchKey(key, rulesInt[idx], matchNo)) {
                                sendFlg = false;
                                break;
                            }
                        }
                    }

                    // 送信すべきデータのみ送る
                    if (sendFlg) {
                        String data = this.keyMapObjGet(key);
                        if (data != null) {
                            if (tagFlg) {

                                // タグ
                                // タグの場合はValue部分をレコードとしてばらして送る
                                String[] tagDatas = data.split(ImdstDefine.imdstTagKeyAppendSep);
                                for (int idx = 0; idx < tagDatas.length; idx++) {

                                    // タグの対象データのキーを送る場合はデータ転送後消しこむ際にインデックス番号が必要なので、
                                    // 左右のプレフィックス文字列は外すが、インデックス番号はつけたまま送る
                                    allDataBuf.append(allDataSep);
                                    allDataBuf.append("2");
                                    allDataBuf.append(workFileSeq);
                                    allDataBuf.append(sendTagKey);
                                    allDataBuf.append(workFileSeq);
                                    allDataBuf.append(tagDatas[idx]);
                                    allDataSep = ImdstDefine.imdstConnectAllDataSendDataSep;
                                    counter++;
                                }
                            } else {

                                // 通常データ
                                allDataBuf.append(allDataSep);
                                allDataBuf.append("1");
                                allDataBuf.append(workFileSeq);
                                allDataBuf.append(key);
                                allDataBuf.append(workFileSeq);
                                allDataBuf.append(data);
                                allDataSep = ImdstDefine.imdstConnectAllDataSendDataSep;
                            }
                        }
                    }

                    counter++;

                    if (counter > (maxLineCount - 1)) {
                        pw.println(allDataBuf.toString());
                        allDataBuf = new StringBuffer();
                        counter = 0;
                        allDataSep = "";
                    }
                }
                pw.println(allDataBuf.toString());
                pw.println("-1");
                pw.flush();
            } catch (Exception e) {
                logger.error("outputNoMatchKeyMapKey2Stream - Error =[" + e.getMessage() + "]");
            }
        }
    }


    // 引数で渡されてストリームに対しKey値を書き出す
    // 書き出すKey値は引数のrulesStrを使用して割り出した値がmatchNoとマッチしないデータ
    // 終了時は-1が返る
    public void outputConsistentHashMoveData2Stream(PrintWriter pw, String targetRangStr) throws BatchException {
        if (!blocking) {
            try {

                String allDataSep = "";
                StringBuffer allDataBuf = new StringBuffer();
                int counter = 0;

                // レンジデータ作成
                int[][] rangs = this.convertRangeData(targetRangStr);

                // keyMapObjの内容を1行文字列として書き出し
                Set entrySet = this.keyMapObj.entrySet();
                int printLineCount = 0;

                // 一度に送信するデータ量を算出。空きメモリの10%を使用する
                int maxLineCount = new Double((JavaSystemApi.getRuntimeFreeMem("") * 0.1) / (ImdstDefine.saveKeyMaxSize * 1.38 + ImdstDefine.saveDataMaxSize * 1.38)).intValue();
                //int maxLineCount = 500;
                if (entrySet.size() > 0) {
                    printLineCount = new Double(entrySet.size() / maxLineCount).intValue();
                    if (entrySet.size() % maxLineCount > 0) {
                        printLineCount = printLineCount + 1;
                    }
                }


                // KeyMapObject内のデータを1件づつ対象になるか確認
                Iterator entryIte = entrySet.iterator(); 

                // キー値を1件づつレンジに含まれているか確認
                while(entryIte.hasNext()) {
                    Map.Entry obj = (Map.Entry)entryIte.next();
                    String key = null;
                    String sendTagKey = null;
                    boolean sendFlg = false;
                    boolean tagFlg = false;

                    // キー値を取り出し
                    key = (String)obj.getKey();

                    if (key.indexOf(ImdstDefine.imdstTagStartStr) == 0) {
                        // タグの場合は分解して
                        tagFlg = true;
                        int startIdx = 15;
                        int endIdx = key.lastIndexOf(ImdstDefine.imdstTagEndStr);
         
                        String checkKey = key.substring(startIdx, endIdx);
                        sendTagKey = key.substring(startIdx, endIdx);

                        int lastIdx = checkKey.lastIndexOf("=");

                        // 対象データ判定
                        // タグの対象データ判定はタグ値に連結されているインデックス文字列や、左右のプレフィックス文字列をはずして判定する
                        sendFlg = DataDispatcher.isRangeData(checkKey.substring(0, lastIdx+1), rangs);
                    } else {
                        // 対象データ判定
                        sendFlg = DataDispatcher.isRangeData(key, rangs);
                    }


                    // 送信すべきデータのみ送る
                    if (sendFlg) {

                        String data = this.keyMapObjGet(key);
                        if (data != null) {

                            if (tagFlg) {

                                // タグ
                                // タグの場合はValue部分をレコードとしてばらして送る
                                String[] tagDatas = data.split(ImdstDefine.imdstTagKeyAppendSep);
                                for (int idx = 0; idx < tagDatas.length; idx++) {

                                    // 送信するTag値とTagのValue(実際のKey値群)
                                    //System.out.println(sendTagKey);
                                    //System.out.println(tagDatas[idx]);

                                    // タグの対象データのキーを送る場合はデータ転送後消しこむ際にインデックス番号が必要なので、
                                    // 左右のプレフィックス文字列は外すが、インデックス番号(TagKey_??)はつけたまま送る
                                    allDataBuf.append(allDataSep);
                                    allDataBuf.append("2");
                                    allDataBuf.append(workFileSeq);
                                    allDataBuf.append(sendTagKey);
                                    allDataBuf.append(workFileSeq);
                                    allDataBuf.append(tagDatas[idx]);
                                    allDataSep = ImdstDefine.imdstConnectAllDataSendDataSep;
                                    counter++;
                                }
                            } else {

                                // 通常データ
                                allDataBuf.append(allDataSep);
                                allDataBuf.append("1");
                                allDataBuf.append(workFileSeq);
                                allDataBuf.append(key);
                                allDataBuf.append(workFileSeq);
                                allDataBuf.append(data);
                                allDataSep = ImdstDefine.imdstConnectAllDataSendDataSep;
                            }
                        }
                    }

                    counter++;

                    if (counter > (maxLineCount - 1)) {

                        pw.println(allDataBuf.toString());
                        pw.flush();
                        allDataBuf = new StringBuffer();
                        counter = 0;
                        allDataSep = "";
                    }
                }

                pw.println(allDataBuf.toString());
                pw.println("-1");
                pw.flush();
            } catch (Exception e) {
                logger.error("outputConsistentHashMoveData2Stream - Error =[" + e.getMessage() + "]");
            }
        }
    }


    // 引数で渡されてストリームからの値をデータ登録する
    // この際、既に登録されているデータは登録しない
    public void inputNoMatchKeyMapKey2Stream(PrintWriter pw, BufferedReader br) throws BatchException {
        this.inputConsistentHashMoveData2Stream(pw, br);
    }


    // 引数で渡されてストリームからの値をデータ登録する
    // この際、既に登録されているデータは登録しない
    public void inputConsistentHashMoveData2Stream(PrintWriter pw, BufferedReader br) throws BatchException {
        if (!blocking) {
            try {
                this.moveAdjustmentDataMap = new ConcurrentHashMap(1024, 1000, 512);

                int i = 0;
                String[] oneDatas = null;


                // 最終更新日付変えずに全てのデータを登録する
                // ストリームからKeyMapの1ラインを読み込み、パース後1件づつ登録
                // 既に同一のキー値で登録データが存在する場合はそちらのデータを優先
                String dataStr = null;

                while(true) {
                    logger.info("inputConsistentHashMoveData2Stream - synchronized - start");
                    synchronized(this.poolKeyLock) {

                        dataStr = br.readLine();
                        if (dataStr == null || dataStr.equals("-1")) break;
                        String[] dataLines = dataStr.split(ImdstDefine.imdstConnectAllDataSendDataSep);

                        for (i = 0; i < dataLines.length; i++) {

                            if (!dataLines[i].trim().equals("")) {

                                oneDatas = dataLines[i].split(workFileSeq);

                                // データの種類に合わせて処理分岐
                                if (oneDatas[0].equals("1")) {

                                    // 通常データ
                                    // 成功、失敗関係なく全て登録処理
                                    this.setKeyPairOnlyOnce(oneDatas[1], oneDatas[2], "0", true);
                                } else if (oneDatas[0].equals("2")) {

                                    // Tagデータ
                                    // 通常通りタグとして保存
                                    // Tagデータはキー値にインデックス付きで送信されるので、インデックスを取り外す
                                    int lastIdx = oneDatas[1].lastIndexOf("=");

                                    oneDatas[1] = oneDatas[1].substring(0, lastIdx+1);

                                    this.setTagPair(oneDatas[1], oneDatas[2], "0");
                                }
                            }
                        }
                        pw.println("next");
                        pw.flush();
                    }
                    logger.info("inputConsistentHashMoveData2Stream - synchronized - end");
                }
                pw.println("end");
                pw.flush();
            } catch(SocketException se) {
                // 切断とみなす
                logger.error(se);
            } catch (Exception e) {
                if (pw != null) {
                    try {
                        pw.println("error");
                        pw.flush();
                    } catch (Exception ee) {
                    }
                }
                logger.error("inputConsistentHashMoveData2Stream - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "inputConsistentHashMoveData2Stream - Error [" + e.getMessage() + "]");
                throw new BatchException(e);
            } finally {
                synchronized (this.moveAdjustmentSync) {

                    // keyMapObjの内容を1行文字列として書き出し
                    Set entrySet = this.moveAdjustmentDataMap.entrySet();

                    // KeyMapObject内のデータを1件づつ対象になるか確認
                    Iterator entryIte = entrySet.iterator(); 

                    // キー値を1件づつレンジに含まれているか確認
                    while(entryIte.hasNext()) {
                        Map.Entry obj = (Map.Entry)entryIte.next();
                        String key = null;

                        // キー値を取り出し
                        key = (String)obj.getKey();
                        // 削除
                        keyMapObjRemove(key);
                        
                    }
                    this.moveAdjustmentDataMap = null;
                }
            }
        }
    }


    // 移動対象のデータが移動完了した後に削除するために呼び出す
    // 終了時は-1が返る
    public void removeConsistentHashMoveData2Stream(PrintWriter pw, String targetRangStr) throws BatchException {
        if (!blocking) {
            try {
                // レンジデータ作成
                int[][] rangs = this.convertRangeData(targetRangStr);

                // keyMapObjの内容を1行文字列として書き出し
                Set entrySet = this.keyMapObj.entrySet();

                // KeyMapObject内のデータを1件づつ対象になるか確認
                Iterator entryIte = entrySet.iterator(); 

                // キー値を1件づつレンジに含まれているか確認
                while(entryIte.hasNext()) {
                    Map.Entry obj = (Map.Entry)entryIte.next();
                    String key = null;

                    // キー値を取り出し
                    key = (String)obj.getKey();

                    if (key.indexOf(ImdstDefine.imdstTagStartStr) == 0) {
                        // タグの場合は分解して
                        int startIdx = 15;
                        int endIdx = key.lastIndexOf(ImdstDefine.imdstTagEndStr);
         
                        String checkKey = key.substring(startIdx, endIdx);

                        int lastIdx = checkKey.lastIndexOf("=");

                        // 対象データ判定
                        // タグの対象データ判定はタグ値に連結されているインデックス文字列や、左右のプレフィックス文字列をはずして判定する
                        if(DataDispatcher.isRangeData(checkKey.substring(0, lastIdx+1), rangs)) this.removeKeyPair(key, "0");
                    } else {
                        // 対象データ判定
                        if(DataDispatcher.isRangeData(key, rangs)) this.removeKeyPair(key, "0");
                    }
                }

                pw.println("-1");
                pw.flush();
            } catch (Exception e) {
                if (pw != null) {
                    try {
                        pw.println("error");
                        pw.flush();
                    } catch (Exception ee) {
                    }
                }
                logger.error("removeConsistentHashMoveData2Stream - Error =[" + e.getMessage() + "]");
            }
        }
    }


    public void removeModMoveData2Stream(PrintWriter pw, BufferedReader br) throws BatchException {
        if (!blocking) {
            try {
                int i = 0;
                String[] oneDatas = null;


                // 最終更新日付変えずに全てのデータを登録する
                // ストリームからKeyMapの1ラインを読み込み、パース後1件づつ登録
                // 既に同一のキー値で登録データが存在する場合はそちらのデータを優先
                String dataStr = null;

                while(true) {
                    logger.info("removeModMoveData2Stream - synchronized - start");

                    dataStr = br.readLine();
                    if (dataStr == null || dataStr.equals("-1")) break;

                    oneDatas = dataStr.split(workFileSeq);

                    if (oneDatas[0].equals("1")) {
                        // 通常データ
                        removeKeyPair(oneDatas[1], "0");
                    } else if (oneDatas[0].equals("2")) {
                        // タグ
                        removeKeyPair(tagStartStr + oneDatas[1] + tagEndStr, "0");
                    }
                    pw.println("next");
                    pw.flush();

                    logger.info("inputConsistentHashMoveData2Stream - synchronized - end");
                }

            } catch(SocketException se) {
                // 切断とみなす
                logger.error(se);
            } catch (Exception e) {
                if (pw != null) {
                    try {
                        pw.println("error");
                        pw.flush();
                    } catch (Exception ee) {
                    }
                }
                logger.error("inputConsistentHashMoveData2Stream - Error");
                blocking = true;
                StatusUtil.setStatusAndMessage(1, "inputConsistentHashMoveData2Stream - Error [" + e.getMessage() + "]");
                throw new BatchException(e);
            }
        }
    }


    // ConsistentHash時のデータ移動用レンジ用配列作成
    private int[][] convertRangeData(String rangsStr) {
        String[] targetRangs = rangsStr.split("_");
        int[][] rangs = new int[targetRangs.length][2];

        // レンジのstartとendをセット単位でintの配列に落とす
        for (int ii = 0; ii < targetRangs.length; ii++) {

            String[] workRangs = targetRangs[ii].split("-");
            rangs[ii][0] = Integer.parseInt(workRangs[0]);
            rangs[ii][1] = Integer.parseInt(workRangs[1]);
        }
        return rangs;
    }


    // データの最終更新時間を返す
    public long getLastDataChangeTime() {
        return this.keyMapObj.getKLastDataChangeTime();
    }

    // 格納データ数を返す
    public int getSaveDataCount() {
        return this.keyMapObj.size();
    }

    // 自身のステータスがエラーでないかを返す
    public boolean checkError() {
        return this.blocking;
    }

    public void dump() {
        System.out.println("-------------------------------------- Dump Start ------------------------------------");
        System.out.println("ALL Data Count = [" + this.getSaveDataCount() + "]");
        System.out.println("======================================================================================");
        // keyMapObjの内容を1行文字列として書き出し
        Set entrySet = this.keyMapObj.entrySet();

        // KeyMapObject内のデータを1件づつ対象になるか確認
        Iterator entryIte = entrySet.iterator(); 

        // キー値を1件づつレンジに含まれているか確認
        while(entryIte.hasNext()) {
            Map.Entry obj = (Map.Entry)entryIte.next();
            String key = null;

            // キー値を取り出し
            key = (String)obj.getKey();
            if (key.indexOf(ImdstDefine.imdstTagStartStr) == 0) {

                String tag = key;
                int startIdx = 15;
                int endIdx = key.lastIndexOf(ImdstDefine.imdstTagEndStr);
 
                key = key.substring(startIdx, endIdx);

                int lastIdx = key.lastIndexOf("=");

                key = key.substring(0, lastIdx+1);

                System.out.println("Tag=[" + new String(BASE64DecoderStream.decode(key.getBytes())) + "], Value=[" + this.keyMapObjGet(tag) + "]");

            } else {
                System.out.println("Key=[" + new String(BASE64DecoderStream.decode(key.getBytes())) + "], Value=[" + this.keyMapObjGet(key) + "]");
            }
        }
        System.out.println("-------------------------------------- Dump End --------------------------------------");
    }
}