/*********************************************************************
*  All Rights reserved,Copyright (c) K-Opticom					 *
**********************************************************************
*＜プログラム内容＞
*	システム名			：eo顧客基幹システム
*	モジュール名		：JKKBatMultiMatchingUtil
*	ソースファイル名	：JKKBatMultiMatchingUtil.java
*	作成者				：富士通　
*	作成日				：2012年07月27日
*＜機能概要＞
*	複数件マッチング処理部品です。
*＜修正履歴＞
*	バージョン	修正日		修正者		修正内容
*	v1.00.00	2012/07/27	富士通		新規作成
*********************************************************************/
package eo.business.common;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import eo.framework.item.JBSbatServiceInterfaceMap;

/**
* (クラスの機能概要) <p>
* 複数件マッチング処理部品です。
* マッチングキーにい対するレコードがマスタ/トラン共に複数件存在するような
* マッチング処理の制御を行います。
* 
* @業務サービス制御クラスはマッチングの処理形態で、当クラスのオブジェクトを非staticのフィールドとして保持してください。
* A主処理の冒頭でisReady()メソッドを呼出してください。
*   ・返却値がfalseの場合、トランの読込が必要なためそのままreturnしてください。
*   ・返却値がtrueの場合は、続けてgetMatchingList()メソッドを呼出すことで、マスタとマッチするトランのレコードをListで取得できます。
* Bマッチング処理に関連するフラグ操作は当部品内で吸収します。
* C業務サービス制御クラスはtranMapを直接操作せず、当クラスを経由して操作してください。

*<BR>
* @author 富士通
*/
public class JKKBatMultiMatchingUtil {
	
	/** 入力ファイル１（マスタ）のマッチングキー項目名（複数指定可） */
	private final List<String> mstKeyNames;
	
	/** 入力ファイル２（トラン）のマッチングキー項目名（複数指定可） */
	private final List<String> tranKeyNames;
	
	/** 入力ファイル１（マスタ）のマッチングキー取得部品 */
	private final JKKBatMatchingKeyGetter mstKeyGetter;
	
	/** 入力ファイル２（トラン）のマッチングキー取得部品 */
	private final JKKBatMatchingKeyGetter tranKeyGetter;
	
	/** マッチングフラグ操作インターフェース */
	private final JKKBatMatchingFlgIF flgIF;
	
	/** 入力ファイル２（トラン）のキャッシュ。
	 *  マッチングキーが同一のレコードをキャッシュして保持する。
	 */
	private List<JBSbatServiceInterfaceMap> tranCashList = new ArrayList<JBSbatServiceInterfaceMap>();
	
	/** 入力ファイル２（トラン）のキャッシュに対応するマッチングキー。
	 *  初期値は空リスト（myComparatorの最小値）、最大値はnullとする。
	 */
	private List<String> tranCashKey = new ArrayList<String>();
	
	/** キー一致フラグ */
	private boolean isSameKey = false;
	
	/** マッチングキーの比較を行うComparator。
	 *  bsortでソートキーを複数指定した場合の結果と一致することを意図しています。
	 *  現状、文字列比較しか対応していません。数値項目を比較する際は注意してください。
	 *  
	 *    ・空リストを最小値、nullを最大値とします。
	 *    ・リストサイズが異なる場合、リストサイズの比較結果を返却します。
	 *    ・リストサイズが同じ場合、リストの先頭から順にString.comparerTo()を呼出し、
	 *      最初に返却された0以外の結果をそのまま返却します。
	 *    ・リストの全要素で String.comparerTo()の結果が0の場合、0を返却します。
	 *      リスト内にnullが含まれた場合は、空文字列と同様に取り扱います。
	 */
	private static final Comparator<List<String>> myComparator = new Comparator<List<String>>(){
		@Override
		public int compare(List<String> list1, List<String> list2) {
			if(list1 == null && list2 == null) {
				return 0;
			}else if(list1 == null) {
				return 1;
			}else if(list2 == null) {
				return -1;
			}
			
			int size1 = list1.size();
			int size2 = list2.size();
			if(size1 != size2) {
				return size1 - size2;
			}
			
			for(int i=0; i<list1.size(); i++){
				String val1 = list1.get(i);
				String val2 = list2.get(i);
				
				if(val1 == null){
					val1 = "";
				}
				if(val2 == null){
					val2 = "";
				}
				
				int compare = val1.compareTo(val2);
				if(compare != 0){
					return compare;
				}
			}
			
			return 0;
		}
	};
	
	/**
	 * コンストラクタ。
	 * @param _mstKeyName 入力ファイル１（マスタ）のマッチングキー項目名
	 * @param _tranKeyName 入力ファイル２（トラン）のマッチングキー項目名
	 * @param _flgIF マッチングフラグ操作インターフェース
	 */
	public JKKBatMultiMatchingUtil(String _mstKeyName, String _tranKeyName, JKKBatMatchingFlgIF _flgIF){
		this(
			Arrays.asList(new String[]{_mstKeyName}), 
			Arrays.asList(new String[]{_tranKeyName}), 
			new JKKBatDefaultMatchingKeyGetter(), 
			new JKKBatDefaultMatchingKeyGetter(), 
			_flgIF
		);
	}
	
	/**
	 * コンストラクタ。
	 * @param _mstKeyNames 入力ファイル１（マスタ）のマッチングキー項目名（複数指定可）
	 * @param _tranKeyNames 入力ファイル２（トラン）のマッチングキー項目名（複数指定可）
	 * @param _mstKeyGetter 入力ファイル１（マスタ）のマッチングキー取得部品
	 * @param _tranKeyGetter入力ファイル２（トラン）のマッチングキー取得部品
	 * @param _flgIF マッチングフラグ操作インターフェース
	 */
	public JKKBatMultiMatchingUtil(
			List<String> _mstKeyNames, 
			List<String> _tranKeyNames, 
			JKKBatMatchingKeyGetter _mstKeyGetter, 
			JKKBatMatchingKeyGetter _tranKeyGetter, 
			JKKBatMatchingFlgIF _flgIF
	){
		mstKeyNames = _mstKeyNames;
		tranKeyNames = _tranKeyNames;
		mstKeyGetter = _mstKeyGetter;
		tranKeyGetter = _tranKeyGetter;
		flgIF = _flgIF;
	}
	
	/**
	 * <dd>メソッド名	：読込完了判定
	 * <dd>メソッド説明	：1.トランを1レコード読込み、トランの内容をキャッシュします。
	 *                    2.マスタに対応するトランの読込・キャッシュがすべて完了しているかを判定し、
	 *                      フラグの設定および返却値の判定を行います。
	 *                      
	 *                    業務サービス制御クラスは主処理の最初に当メソッドを呼出し、
	 *                    返却値がtrueの場合のみ業務処理を行ってください。
	 *                    返却値がfalseの場合は処理を終了し、フレームワークにてトランの次レコードを読み込んで下さい。
	 *                    
	 * @param mastMap 入力ファイル１（マスタ）の読込レコードマップ
	 * @param tranMap 入力ファイル２（トラン）の読込レコードマップ
	 * @return 入力ファイル２（トラン）の次レコード読込が必要な場合、true。
	 *          必要な読込がすべて完了しており、次レコード読込が不要な場合、false。
	 * @throws Exception フレームワークにおける処理異常時
	 */
	public boolean isReady(JBSbatServiceInterfaceMap mastMap, JBSbatServiceInterfaceMap tranMap) throws Exception{
		
		/** 入力電文の判定 */
		// マスタがnullの場合
		if(mastMap == null) {
			// 返却値はfalse。呼出元は処理を終了し、トランの次レコードを読み込む。
			setTranProcFlg(true);
			isSameKey = false;
			return false;
			
		// トランキャッシュのキーがnullの場合
		} else if(tranCashKey == null) {
			// トランの読込みは終了している。
			// 返却値はtrue。呼出元での業務処理終了後、マスタの次レコードを読み込む。
			setMastProcFlg(true);
			isSameKey = false;
			return true;
			
		// 上記以外の場合
		} else {
			
			/** トランのキーブレイク判定 */
			// トランがnullでない（レコードが存在する）場合
			if(tranMap != null){
				// トランからマッチングキー取得
				List<String> tranKey = tranKeyGetter.getMatchingKey(tranMap, tranKeyNames);
				// キーがキャッシュと一致している（キーブレイクしていない）場合
				if( tranKey.equals(tranCashKey) ){
					// トランのレコードをキャッシュに退避
					tranCashList.add(tranMap);
					
					// キーブレイクしていないため、トランの次レコード読込みが必要。
					// 返却値はfalse。呼出元は処理を終了し、トランの次レコードを読み込む。
					this.setTranProcFlg(true);
					isSameKey = false;
					return false;
				}
			}
			
			
			/** トランのキーブレイクを確認したため、マスタとのキー比較 */
			// マスタからマッチングキー取得
			List<String> mstKey = mstKeyGetter.getMatchingKey(mastMap, mstKeyNames);
			// マスタのキーとトランキャッシュのキーが一致した場合
			if( myComparator.compare(mstKey, tranCashKey) == 0 ){
				// 返却値はtrue。呼出元での業務処理終了後、マスタの次レコードを読み込む。
				// setchProcFlg(true)にするとマスタではなくトランが次レコードへ進んでしまうため、
				// setMastProcFlg(true)とする。
				setMastProcFlg(true);
				isSameKey = true;
				return true;
				
			// マスタのキー ＜ トランキャッシュのキー の場合
			} else if( myComparator.compare(mstKey, tranCashKey) < 0 ){
				// 返却値はtrue。呼出元での業務処理終了後、マスタの次レコードを読み込む。
				setMastProcFlg(true);
				isSameKey = false;
				return true;
				
			// マスタのキー ＞ トランキャッシュのキー の場合
			} else {
				// 現在のキャッシュは不要となったため、キャッシュをクリア
				tranCashList.clear();
				
				// トランがnull以外の場合
				if(tranMap!=null){
					// トランの読込位置のレコードをキャッシュに1件目として退避
					tranCashList.add(tranMap);
					// トランキャッシュのキーを更新
					tranCashKey = tranKeyGetter.getMatchingKey(tranMap, tranKeyNames);
					
					// キーブレイク判定のため、トランの次レコード読込みが必要。
					// 返却値はfalse。呼出元は処理を終了し、トランの次レコードを読み込む。
					this.setTranProcFlg(true);
					isSameKey = false;
					return false;
					
				// トランがnullの場合
				}else{
					// トランキャッシュのキーにnullセット。
					tranCashKey = null;
					// キーブレイク判定は必要ないので、返却値はtrue。
					// 呼出元での業務処理終了後、マスタの次レコードを読み込む。
					setMastProcFlg(true);
					isSameKey = false;
					return true;
				}
			}
		}
	}

	/**
	 * マスタとマッチングキーが一致するトランのレコードをListで返却する。
	 * @return マスタとマッチングキーが一致するトランレコードのList。該当なしの場合空リスト。
	 */
	public List<JBSbatServiceInterfaceMap> getMatchingList(){
		// マスタとキャッシュのキーが一致している場合
		if(isSameKey){
			// キャッシュを返却
			return tranCashList;
			
		// 上記以外の場合
		} else {
			// 空リストを返却
			return new ArrayList<JBSbatServiceInterfaceMap>();
		}
	}
	
	
	
	
	
	
	/**
	 * マッチングキー取得部品のためのインターフェース。
	 * 業務独自でマッチングキー取得部品を作成する場合、当インターフェースを実装してください。
	 */
	public static interface JKKBatMatchingKeyGetter{
		/**
		 * 入力の1レコードを元にマッチングキーを取得します。
		 * @param inMap 入力レコードマップ
		 * @param keyNames 入力レコードマップから項目を取得する際のキー（複数指定可）
		 * @return マッチングキー
		 * @throws Exception フレームワークにおける処理異常時
		 */
		public List<String> getMatchingKey(JBSbatServiceInterfaceMap inMap, List<String> keyNames) throws Exception; 
	}
	
	/**
	 * デフォルトのマッチングキー取得部品です。
	 */
	public static class JKKBatDefaultMatchingKeyGetter implements JKKBatMatchingKeyGetter{
		/**
		 * 入力の1レコードを元にマッチングキーを取得します。
		 * keyNamesで指定されたキーでinMapから値を取得し、順序を守りながらList<String>に格納して返却します。
		 * 
		 * @param inMap 入力レコードマップ
		 * @param keyNames 入力レコードマップから項目を取得する際のキー
		 * @return マッチングキー
		 * @throws Exception フレームワークにおける処理異常時
		 * @see eo.business.common.JKKBatMultiMatchingUtil.JKKBatMatchingKeyGetter#getMatchingKey(eo.framework.item.JBSbatServiceInterfaceMap, java.util.List)
		 */
		@Override
		public List<String> getMatchingKey(JBSbatServiceInterfaceMap inMap, List<String> keyNames) throws Exception {
			List<String> key = new ArrayList<String>();
			for(String keyName : keyNames){
				String keyStr = inMap.getString(keyName);
				key.add(keyStr);
			}
			return key;
		}
	}
	
	
	
	/**
	 * @param mastProcFlg 設定する mastProcFlg。
	 */
	public void setMastProcFlg(boolean mastProcFlg)
	{
		flgIF.setServiceMastProcFlg(mastProcFlg);
	}

	/**
	 * @param mastProcFlg 設定する matchProcFlg。
	 */
	public void setMatchProcFlg(boolean matchProcFlg)
	{
		flgIF.setServiceMatchProcFlg(matchProcFlg);
	}

	/**
	 * @param tranProcFlg 設定する tranProcFlg。
	 */
	public void setTranProcFlg(boolean tranProcFlg)
	{
		flgIF.setServiceTranProcFlg(tranProcFlg);
	}

	/**
	 * 業務サービス制御クラスが持つマッチング関連のフラグを操作するためのインターフェースクラス。
	 * 業務サービス制御クラスは、自クラスのフラグ操作メソッドをラップする形で実装し、
	 * JKKBatMultiMatchingUtilのコンストラクタに渡してください。
	 * 
	 * 実装例：
	 * 
	 * 		// 起動パラメータ FREE より、マッチングキーを取得
	 *		String[] freeItems = super.freeItem.split(JKKBatConst.S_PARAM_DELIM);
	 *		// マッチング処理部品を初期化
	 *		matchingUtil = 
	 *			new JKKBatMultiMatchingUtil(
	 *					freeItems[0], 
	 *					freeItems[1],
	 *					new JKKBatMatchingFlgIF(){
	 *						public void setServiceMastProcFlg(boolean _mastProcFlg) {
	 *							setMastProcFlg(_mastProcFlg);
	 *						}
	 *						public void setServiceMatchProcFlg(boolean _matchProcFlg) {
	 *							setMatchProcFlg(_matchProcFlg);
	 *						}
	 *						public void setServiceTranProcFlg(boolean _tranProcFlg) {
	 *							setTranProcFlg(_tranProcFlg);
	 *						}
	 *					}
	 *			);
	 */
	public static interface JKKBatMatchingFlgIF{
		
		public void setServiceMastProcFlg(boolean _mastProcFlg);
		public void setServiceMatchProcFlg(boolean _matchProcFlg);
		public void setServiceTranProcFlg(boolean _tranProcFlg);
		
	}

}
