/*********************************************************************
* All Rights reserved,Copyright (c) K-Opticom
**********************************************************************
*＜プログラム内容＞
*   システム名      ：eo顧客基幹システム
*   モジュール名    ：JCCChohyoMaskUtil
*   ソースファイル名：JCCChohyoMaskUtil.java
*   作成者          ：富士通
*   日付            ：2011年07月13日
*＜機能概要＞
*   帳票マスキング部品です。
*＜修正履歴＞
*   バージョン  修正日       修正者      修正内容
*   v1.00.00    2011/07/13    FST        新規作成
*
**********************************************************************/

package eo.common.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;

import com.fujitsu.futurity.common.JCMPropertyCache;

public class JCCChohyoMaskUtil extends JCMPropertyCache {

	// 区切り文字
	private static final String SEP_C = ",";
	// 囲み文字
	private static final String S_ENCOLSE_D = "\"";
	private static final char C_ENCOLSE_D = '"';
	// 値としてのダブルクウォーテーション検知用
	private static Boolean DOUBLEQUART_FLG = false;
	// 改行コード
	// 転送モード
	private static final HashMap<String, String> lineSepMap = new HashMap<String, String>();
	static{
		lineSepMap.put("CRLF","\r\n");
		lineSepMap.put("LF","\n");
		lineSepMap.put("",System.getProperty("line.separator"));
		lineSepMap.put(null,System.getProperty("line.separator"));
	};
	
	/**
	 * 指定された帳票ファイルのマスキング処理を実施します。
	 * @param path      マスキング対象ファイルパス
	 * @param encord    帳票ファイルの文字コード
	 * @param propMap   帳票マスキング定義情報
	 * @return          マスキング実施後のファイルパス 
	 */
	public String maskingInputFile(String path, String encord, String lineSep, HashMap<Integer, ArrayList<String>> propMap)
	{
		/*************************
		/ パラメータチェック処理
		**************************/
		
		// ファイルパスがnullまたは空白の場合
		if(null == path || path.isEmpty())
		{
			throw new JCCFrameworkException("ファイルパスがnullもしくは空白です。");
		}
		
		// 帳票ファイル文字コードがnullまたは空白の場合
		if(null == encord || encord.isEmpty())
		{
			throw new JCCFrameworkException("帳票ファイル文字コードがnullもしくは空白です。");
		}
		
		// 帳票ファイル改行コードがCRLF、LF、空白以外の場合
		if(!lineSepMap.containsKey(lineSep))
		{
			throw new JCCFrameworkException("帳票ファイル改行コードの値が不正です。CRLF、LF、空白のいずれかを設定してください。");
		}
		
		// 帳票マスキング定義ファイル情報がnullまたは空白の場合
		if(null == propMap)
		{
			throw new JCCFrameworkException("帳票マスキング定義ファイル情報がnullです。");
		}
		
		// ファイル存在チェック
		File file = new File(path);
		// ファイル未登録の場合
		if(!file.exists())
		{
			throw new JCCFrameworkException(
					"マスキング対象ファイルが指定されたフォルダに存在しません。（ファイルパス=" + path + "）");
		}
		// ファイルサイズ0BYTEの場合
		if(0 == file.length())
		{
			throw new JCCFrameworkException(
					"マスキング対象ファイルのサイズが0byteです。（ファイルパス=" + path + "）");
		}
		
		// ファイル読み込みバッファ
		BufferedReader br = null;
		// ファイル書き込みバッファ
		BufferedWriter bw = null;
		
		try{
			/*************************
			/ 帳票ファイル読み込み
			**************************/
			
			// マスクされたファイルのパスを組立
			String maskedPath = file.getParent() + System.getProperty("file.separator")+ "Masked_" + file.getName();
			
			// ファイル読み込みバッファ
			br = new BufferedReader(new InputStreamReader(new FileInputStream(path), encord));
			// ファイル書き込みバッファ
			bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(maskedPath), encord));
			
			// 1行読み込み用
			String line = "";
			// 書き込み用データ
			ArrayList<String> alData = new ArrayList<String>();
			// カンマ分割後
			String[] elementWk = null;
			ArrayList<String> element = null;
			// マスク後文字列組立て用
			StringBuffer sb = new StringBuffer();
			// 置換え対象文字列
			String rplcTrgtStr = "";
			// 囲み文字
			String encloseStr = "";
			// 表示する文字
			String indicate = "";
			
			// 帳票ファイルの最終行までループ
			while((line = br.readLine()) != null){
				// 初期化
				sb.delete(0,sb.length());
				encloseStr = "";
				// カンマで分割
				elementWk = line.split(SEP_C);
				// 値として扱うカンマにより分割
				// されたデータを再組み立て
				element = checkCanma(elementWk);
				// 要素数が0の場合
				// ※改行のみ、",,,"等
				if(0 == element.size()){
					// 無編集で書き出し
					alData.add(line);
				}
				else{
					// カンマ分割後の要素数分繰り返し
					for(int i=0; i<element.size(); i++){
						// 初期化
						rplcTrgtStr = "";
						indicate = "";
						// カラム番号がマスキング
						// 定義に登録されている場合
						if(propMap.containsKey(i)){
							// 表示桁数を取得
							int length = Integer.parseInt(((ArrayList<String>)propMap.get(i)).get(1));
							// 囲み文字が設定されていた場合
							if(this.encloseCheck(element.get(i))){
								// 囲み文字以外に値が存在する場合
								if(2 < element.get(i).length()){
									// 囲み文字を除去
									rplcTrgtStr = element.get(i).substring(1, element.get(i).length()-1);
									// 二重ダブルクウォーテーションを単一へ置換
									rplcTrgtStr = delDoubleQuart(rplcTrgtStr);
								}
								encloseStr = "\"";
							}
							else{
								// 無編集で格納
								rplcTrgtStr = element.get(i);
							}
							if(length <= rplcTrgtStr.length()){
								// 元データから表示桁数分の文字列を退避
								indicate = rplcTrgtStr.substring(
										rplcTrgtStr.length()-length, rplcTrgtStr.length());
							}
							// 単一ダブルクウォーテーションを二重へ置換
							indicate = addDoubleQuart(indicate);
							// カンマ、置換え文字、表示桁数分の元データを追加
							sb.append(SEP_C + encloseStr + ((ArrayList<String>)propMap.get(i)).get(0) + indicate + encloseStr);
						}
						// 未登録の場合
						else{
							// 無編集で追加
							sb.append(SEP_C + element.get(i));
						}
					}
					// 行先頭のカンマを削除
					alData.add(sb.toString().replaceFirst(SEP_C, ""));
				}
			}
			
			// 行数分書き込み
			for(int i=0; i<alData.size(); i++){
				// 1行書き出し
				bw.write(alData.get(i));
				// 最終行以外は改行コード設定
				if(i != alData.size()-1){
					bw.write(lineSepMap.get(lineSep));
				}
			}
			
			if(null != br){
				br.close();
			}
			if(null != bw){
				bw.close();
			}
			// マスク後ファイルパス返却
			return maskedPath;
		}
		catch(Exception e){
			try{
				if(null != br){
					br.close();
				}
				if(null != bw){
					bw.close();
				}
			}catch(IOException ie){
			}
			throw new JCCFrameworkException(
					"マスキング処理でエラーが発生しました。（ファイルパス=" + path + "）", e);
		}
	}
	
	/**
	 * 引数の文字列が指定の囲み文字で括られているかを確認します。
	 * @param    target 確認対象の文字列
	 * @return   true:囲み有り／false:囲み無し
	 */
	private boolean encloseCheck(String target) 
	{
		// 空白の場合は無条件でfalse
		if(!"".equals(target)){
			// 先頭が囲み文字である場合
			if(C_ENCOLSE_D == target.charAt(0)){
				// 最終が囲み文字である場合
				if(C_ENCOLSE_D == target.charAt(target.length()-1)){
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * 二重ダブルクウォーテーションを単一へ置換します。
	 * @param    target 確認対象の文字列
	 * @return   置換後の文字列
	 */
	private String delDoubleQuart(String target) 
	{
		// 二重ダブルクウォーテーションが含まれていた場合
		if(-1 != target.lastIndexOf("\"\"")){
			// 二重を単一へ変換
			target = target.replace("\"\"", "\"");
			// 検知用フラグON
			DOUBLEQUART_FLG = true;
		}
		return target;
	}
	
	/**
	 * 単一ダブルクウォーテーションを二重へ置換します。
	 * @param    target 確認対象の文字列
	 * @return   置換後の文字列
	 */
	private String addDoubleQuart(String target) 
	{
		// 検知用フラグがON
		// ※delDoubleQuartでONへ変更
		if(DOUBLEQUART_FLG){
			// 単一を二重へ変換
			target = target.replace("\"", "\"\"");
			// 検知用フラグOFF
			DOUBLEQUART_FLG = false;
		}
		return target;
	}
	
	/**
	 * 分割後データ再結合処理
	 * CSVの仕様では、""に括られている場合はカンマ(",")を値として扱うが、
	 * 呼び出し元でSplit(",")により値を分割しているため、値として扱うカンマ
	 * まで分割されてしまう。当メソッドでは、分割された値の再結合を行う。
	 * 処理の中で、区切り文字としてのカンマ、値としてのカンマを判断し、
	 * 値としてのカンマであった場合のみ復活させ値に含める。
	 * @param    target カンマによる分割後の文字列リスト
	 * @return   置換後の文字列
	 */
	private ArrayList<String> checkCanma(String[] target) 
	{
		// 値としてのカンマによる分割判断用
		boolean nextFlg = false;
		// 再結合後のデータ保持用
		ArrayList<String> alElement = new ArrayList<String>();
		// 分割データ結合用バッファ（単一値）
		StringBuffer sb = new StringBuffer();
//		// 分割データ結合用バッファ（リスト）
//		StringBuffer list = new StringBuffer();
		// 分割された値の件数分ループ
		for(int i=0; i<target.length; i++){
			// 先頭データ
			if(!nextFlg){
				// 空白の場合（「,」）
				if("".equals(target[i])){
					alElement.add(target[i]);
				}
				// ダブルクウォーテーション単一の場合
				// データの開始を意味する（「",ABC,"、","」など）
				else if(S_ENCOLSE_D.equals(target[i])){
					// 組み立て用に一時退避し次データ検索
					sb.delete(0,sb.length());
					sb.append(target[i]);
					nextFlg = true;
				}
				// 先頭がダブルクウォーテーションで、
				// 末尾がダブルクウォーテーション以外
				// （「"ABC,DEF"」などが分割された場合）
				else if( S_ENCOLSE_D.equals(target[i].substring(0,1)) && 
						 !S_ENCOLSE_D.equals(target[i].substring(target[i].length()-1,target[i].length()))){
					// 組み立て用に一時退避し次データ検索
					sb.delete(0,sb.length());
					sb.append(target[i]);
					nextFlg = true;
				}
				// ダブルクウォーテーションで囲まれている場合、
				// または先頭・中間ともに囲まれていない場合
				else{
					// リストへ追加
					// 単一データなのでフラグはOFFのまま
					alElement.add(target[i]);
				}
			}
			// 中間データ
			else{
				// 空白の場合（「"ABC,,」など）
				if("".equals(target[i])){
					alElement.add(target[i]);
					nextFlg = false;
				}
				// ダブルクウォーテーション単一の場合
				// データの終了を意味する（「",ABC,"、","」など）
				else if(S_ENCOLSE_D.equals(target[i])){
					sb.append(SEP_C + target[i]);
					alElement.add(sb.toString());
					nextFlg = false;
				}
				// 先頭がダブルクウォーテーションの場合
				// ダブルクウォーテーションで正しく閉じられていない場合
				// 前のデータはリストへ追加し、別データとして扱う
				// データは継続しているためフラグはONのまま
				// （「"ABC,"001,002"」などが分割された場合）
				else if(S_ENCOLSE_D.equals(target[i].substring(0,1))){
					// 2番目もダブルクウォーテーションの場合はデータ継続
					if(2 < target[i].length() &&
						S_ENCOLSE_D.equals(target[i].substring(1,2))){
						sb.append(SEP_C + target[i]);	// カンマ追加
					}
					// 単一の場合は新規データ
					else{
						alElement.add(sb.toString());
						sb.delete(0,sb.length());
						// 末尾がダブルクウォーテーションの場合
						if(S_ENCOLSE_D.equals(target[i].substring(target[i].length()-1,target[i].length()))){
							// 末尾が二重ダブルクウォーテーションの場合はデータ継続
							if( 2 < target[i].length() &&
								S_ENCOLSE_D.equals(target[i].substring(target[i].length()-2,target[i].length()-1))){
								sb.append(target[i]);
							}
							else{
								alElement.add(target[i]);
								nextFlg = false;
							}
						}
						else{
							sb.append(target[i]);	// カンマ追加
						}
					}
				}
				// 末尾がダブルクウォーテーションの場合
				// （「"ABC,DEF"」などが分割された場合）
				else if(S_ENCOLSE_D.equals(target[i].substring(target[i].length()-1,target[i].length()))){
					// 末尾が二重ダブルクウォーテーションの場合はデータ継続
					if( 2 < target[i].length() &&
						S_ENCOLSE_D.equals(target[i].substring(target[i].length()-2,target[i].length()-1))){
						sb.append(SEP_C + target[i]);	// カンマ追加
					}
					// 単一の場合はデータ終了
					else{
						sb.append(SEP_C + target[i]);
						alElement.add(sb.toString());
						nextFlg = false;
					}
				}
				// 先頭・末尾共にダブルクウォーテーション以外
				// （「"ABC,DEF,GHI"」などが分割された場合の中間）
				else{
					sb.append(SEP_C + target[i]);	// カンマ追加
				}
			}
		}
		// データ返却
		return alElement;
	}
}
