/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.taglib;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBTableModel;
// import org.opengion.hayabusa.db.DBColumn;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.FileUtil;

import org.opengion.fukurou.util.StringUtil ;
import static org.opengion.fukurou.util.StringUtil.nval ;

import java.io.File ;
import java.util.Locale ;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

/**
 * ファイル検索リストを元に、action に基づいた処理を行うタグです。
 * command="ENTRY" 時のみ処理を行います。
 *
 * fileQuery などで検索したファイル一覧のDBTableModel を元に、ファイルの
 * コピー(COPY)、移動(MOVE,MODIFY)、削除(DELETE)などの処理を行います。
 * 処理を行うオリジナルファイルは、PARENT,NAME というカラムでなければなりません。
 * このカラム名は、fileQuery の検索時には、必ず作成されるカラムです。
 * また、各アクションに対応するターゲットファイルは、TO_PARENT,TO_NAME という
 * カラムで指定するか、targetDir 属性を利用してフォルダを指定します。
 * TO_PARENT(先フォルダ)と、TO_NAME(先ファイル名)は、処理に応じて、必要なカラムが
 * あれば、自動的に処理します。
 * つまり、TO_PARENT のみの場合は、ファイル名はオリジナルのまま、フォルダのみ変更します。
 * 逆に、TO_NAME の場合は、フォルダはそのままで、ファイル名のみ指定します。
 * 両方同時に指定することも可能です。
 * targetDir 属性で指定する場合は、TO_PARENT のみに同じ値を設定した場合と同じになります。
 * この属性を指定すると、TO_PARENT は無視されます。(TO_NAME は有効です。)
 * COPY、MOVE(,MODIFY) の場合は、指定のフォルダに一括処理可能です。
 * COPY、MOVE(,MODIFY) などの処理で、ターゲットフォルダが存在しないときに、作成するか、エラーにするかは
 * createDir属性 で指定できます。デフォルトは、(true:作成する) です。
 * これは、COPY先やMOVE(,MODIFY)先が存在している前提のシステムで、不要な箇所に間違ってフォルダを
 * 自動作成されると困る場合に、(false:作成しない) とすれば、間違いに気づく確率が上がります。
 *
 * ※ このタグは、Transaction タグの対象ではありません。
 *
 * @og.formSample
 * ●body：なし
 * ●形式：
 *      ・&lt;og:fileUpdate
 *          action      = "COPY|MOVE|MODIFY|DELETE" アクション属性(必須)
 *          command     = "[ENTRY]"                 ENTRY 時のみ実行します。(初期値:ENTRY)
 *          targetDir   = "[指定フォルダ]"          ターゲットとなるフォルダ
 *          createDir   = "[true/false]"            ターゲットとなるフォルダがなければ作成する(true)かどうか(初期値:true)
 *          tableId     = [HybsSystem.TBL_MDL_KEY]  DBTableModel を取り出すキー
 *          outMessage  = "[true/false]"            検索結果のメッセージを表示する(true)かどうかを指定(初期値:true)
 *          displayMsg  = "MSG0040";                処理結果を表示します。（初期値:｢　件登録しました。｣)
 *          selectedAll = "[false/true]"            データを全件選択済みとして処理する(true)かどうか指定(初期値:false)
 *          keepTimeStamp = "[false/true]"          COPY,親違いMOVE(,MODIFY)の時にオリジナルのタイムスタンプを使用するかどうか(初期値:false)
 *      /&gt;
 *
 *    [action属性(必須)]
 *      COPY   オリジナルファイルを、ターゲットにコピーします。
 *      MOVE   オリジナルファイルを、ターゲットに移動(COPY+DELETE)/名称変更(RENAME)します。
 *      MODIFY (MOVE と同じ。エンジンの command を利用するための簡易action）
 *      DELETE オリジナルファイルを、削除します。(フォルダ、ファイルに関わらず)
 *
 * ●使用例
 *       ・&lt;og:fileUpdate command="{&#064;command}" action="COPY" /&gt;
 *             TO_PARENT または、 TO_NAME(両方指定も可)による行単位 COPY 処理
 *             fileQuery の useUpdateClm="true" を設定し、検索結果に、TO_PARENT、 TO_NAMEカラムを追加します。
 *             TO_PARENT または、 TO_NAME は、columnSet などで値をセットしておきます。
 *
 *       ・&lt;og:fileUpdate command="{&#064;command}" action="MODIFY" targetDir="AAA_DIR"  /&gt;
 *             fileQuery の検索結果を、AAA_DIR フォルダに移動します。
 *             ファイル名は、そのままオリジナルの値が使用されます。
 *
 * @og.rev 5.3.4.0 (2011/04/01) 新規追加
 * @og.group ファイル出力
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class FileUpdateTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "5.3.4.0 (2011/04/01)" ;

	private static final long serialVersionUID = 5340 ;		// 5.3.4.0 (2011/04/01)

	/** command 引数に渡す事の出来る コマンド  登録{@value} */
	public static final String CMD_ENTRY  = "ENTRY" ;
	/** command 引数に渡す事の出来る コマンド リスト  */
	private static  final String COMMAND_LIST = CMD_ENTRY;

	/** エラーメッセージID {@value} */
	private static final String errMsgId	 = HybsSystem.ERR_MSG_KEY;

	/** action 引数に渡す事の出来る アクションコマンド  COPY {@value} */
	public static final String ACT_COPY		= "COPY" ;
	/** action 引数に渡す事の出来る アクションコマンド  MOVE {@value} */
	public static final String ACT_MOVE		= "MOVE" ;
	/** action 引数に渡す事の出来る アクションコマンド  MODIFY {@value} */
	public static final String ACT_MODIFY		= "MODIFY" ;
	/** action 引数に渡す事の出来る アクションコマンド  DELETE {@value} */
	public static final String ACT_DELETE	= "DELETE" ;

	private static final String[] ACTION_LIST = new String[] { ACT_COPY , ACT_MOVE , ACT_MODIFY , ACT_DELETE };

	private String	action		= null;
	private String	targetDir	= null;		// ターゲットとなるフォルダ
	private boolean	createDir	= true;		// ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)

	private String	tableId		= HybsSystem.TBL_MDL_KEY;
	private String	command		= CMD_ENTRY;
	private boolean	outMessage	= true;
	private String	displayMsg	= "MSG0040";	// 　件登録しました。
	private boolean selectedAll = false;
	private boolean keepTimeStamp = false;		// オリジナルのタイムスタンプを利用する場合、true

	private transient DBTableModel	table		= null;
	private transient ErrorMessage	errMessage	= null;
	private int		executeCount	= -1;			// 処理件数
	private int		errCode	 		= ErrorMessage.OK;
	private long	dyStart 		= 0;

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @return  int 後続処理の指示
	 */
	@Override
	public int doEndTag() {
		dyStart = System.currentTimeMillis();

		debugPrint();
		// 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
		if( !useTag() ) { return(EVAL_PAGE); }

		table = (DBTableModel)getObject( tableId );

		String label  = "";				// 4.0.0 (2005/11/30) 検索しなかった場合。
		if( table != null && table.getRowCount() > 0 && check( command, COMMAND_LIST ) ) {
			startQueryTransaction( tableId );

			execute();	// 実際の処理を実行します。

			StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_SMALL );

			setRequestAttribute( "DB.COUNT"   , String.valueOf( executeCount ) );
			setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) );

			String err = TaglibUtil.makeHTMLErrorTable( errMessage,getResource() );
			if( err != null && err.length() > 0 ) {
				buf.append( err );
				setSessionAttribute( errMsgId,errMessage );
			}
			label = buf.toString();

			if( table != null && ! commitTableObject( tableId, table ) ) {
				jspPrint( "FileUpdateTag Query処理が割り込まれました。DBTableModel は登録しません。" );
				return (SKIP_PAGE);
			}
		}

		jspPrint( label );

		// 実行件数の表示
		// 4.0.0 (2005/11/30) 出力順の変更。一番最初に出力します。
		if( displayMsg != null && displayMsg.length() > 0 ) {
			String status = executeCount + getResource().getLabel( displayMsg ) ;
			jspPrint( status + HybsSystem.BR );
		}

		// 3.5.4.7 (2004/02/06)
		long dyTime = System.currentTimeMillis()-dyStart;
		jspPrint( "<div id=\"queryTime\" value=\"" + (dyTime) + "\"></div>" );	// 3.5.6.3 (2004/07/12)

		return( EVAL_PAGE );
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		tableId		= HybsSystem.TBL_MDL_KEY;
		command		= CMD_ENTRY;
		action		= null;
		targetDir	= null;		// ターゲットとなるフォルダ
		createDir	= true;		// ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)
		outMessage	= true;
		displayMsg	= "MSG0040";	// 　件登録しました。
		selectedAll = false;
		keepTimeStamp = false;		// オリジナルのタイムスタンプを利用する場合、true
		table		= null;
		errMessage	= null;
		executeCount= -1;		// 処理件数
		errCode	 	= ErrorMessage.OK;
		dyStart 	= 0;		// 処理時間
	}

	/**
	 * 処理を実行します。
	 *
	 */
	private void execute() {
		int[] rowNo = getParameterRows();
		if( rowNo.length > 0 ) {

			FromToFiles fromToFiles = new FromToFiles( table , targetDir , createDir );

			if( ACT_COPY.equalsIgnoreCase( action ) ) {
				actionCOPY( rowNo,fromToFiles );
			}
			// ACT_MODIFY は、エンジンの command で使うため、便利
			else if( ACT_MOVE.equalsIgnoreCase( action ) || ACT_MODIFY.equalsIgnoreCase( action ) ) {
				actionMOVE( rowNo,fromToFiles );
			}
			else if( ACT_DELETE.equalsIgnoreCase( action ) ) {
				actionDELETE( rowNo,fromToFiles );
			}
		}
	}

	/**
	 * COPY アクションを実行します。
	 *
	 * @param  rowNo    int[]  処理を実施する行番号
	 * @param  fromToFiles FromToFiles  FromFile,ToFile をまとめた補助クラス
	 * @throws	HybsSystemException	処理中に何らかのエラーが発生した場合
	 */
	private void actionCOPY( final int[] rowNo , final FromToFiles fromToFiles ) {
		File fromFile = null ;
		File toFile   = null ;

		executeCount = 0 ;	// 開始前に初期化しておく。
		int rowCount = rowNo.length ;
		for( int i=0; i<rowCount; i++ ) {
			File[] files = fromToFiles.makeFromToFile( rowNo[i] );	// FromFile,ToFile
			fromFile = files[0];
			toFile   = files[1];

			if( !FileUtil.copy( fromFile,toFile,keepTimeStamp ) ) {
				String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + HybsSystem.CR
									+ "From=[" + fromFile + "],To=[" + toFile + "]" + HybsSystem.CR;
				throw new HybsSystemException( errMsg );
			}
			executeCount++ ;
		}
	}

	/**
	 * MOVE アクションを実行します。
	 *
	 * @og.rev 5.5.2.4 (2012/05/16) メソッドの戻り値の設定
	 *
	 * @param  rowNo    int[]  処理を実施する行番号
	 * @param  fromToFiles FromToFiles  FromFile,ToFile をまとめた補助クラス
	 * @throws	HybsSystemException	処理中に何らかのエラーが発生した場合
	 */
	private void actionMOVE( final int[] rowNo , final FromToFiles fromToFiles ) {
		File fromFile = null ;
		File toFile   = null ;

		executeCount = 0 ;	// 開始前に初期化しておく。
		int rowCount = rowNo.length ;
		for( int i=0; i<rowCount; i++ ) {
			File[] files = fromToFiles.makeFromToFile( rowNo[i] );	// FromFile,ToFile
			fromFile = files[0];
			toFile   = files[1];

			if( fromToFiles.lastParentEquals() ) {	// FromDirとToDirが同じなので、RENAMEできる。
				if( !fromFile.renameTo( toFile ) ) {
					String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + HybsSystem.CR
										+ "同一親フォルダのため、RENAME処理を行っています。" + HybsSystem.CR
										+ "From=[" + fromFile + "],To=[" + toFile + "]" + HybsSystem.CR;
					throw new HybsSystemException( errMsg );
				}
			}
			else {			// FromDirとToDirが異なるので、COPY ＋ DELETE する。
				if( !FileUtil.copy( fromFile,toFile,keepTimeStamp ) ) {
					String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + HybsSystem.CR
										+ "移動前のCOPY処理を行っていました。" + HybsSystem.CR
										+ "From=[" + fromFile + "],To=[" + toFile + "]" + HybsSystem.CR;
					throw new HybsSystemException( errMsg );
				}

				if( !fromFile.delete() ) {
//					toFile.delete();	// 移動の際の COPY は正常なので、まずは、そのファイルを削除しておく。
					String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + HybsSystem.CR
										+ "移動後のオリジナルファイルの削除処理を行っていました。" + HybsSystem.CR
										+ "From=[" + fromFile + "],To=[" + toFile + "]" + HybsSystem.CR;
					// 5.5.2.4 (2012/05/16) メソッドの戻り値の設定
					if(! toFile.delete() ) {
						errMsg = errMsg + "toFile も削除に失敗しました。" + HybsSystem.CR;
					}

					throw new HybsSystemException( errMsg );
				}
			}
			executeCount++ ;
		}
	}

	/**
	 * DELETE アクションを実行します。
	 *
	 * @param  rowNo    int[]  処理を実施する行番号
	 * @param  fromToFiles FromToFiles  FromFile,ToFile をまとめた補助クラス
	 * @throws	HybsSystemException	処理中に何らかのエラーが発生した場合
	 */
	private void actionDELETE( final int[] rowNo , final FromToFiles fromToFiles ) {
		File fromFile = null;

		executeCount = 0 ;	// 開始前に初期化しておく。
		int rowCount = rowNo.length ;
		for( int i=0; i<rowCount; i++ ) {
			fromFile = fromToFiles.makeFromOnly( rowNo[i] );	// FromFile

			if( !fromFile.delete() ) {
				String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + HybsSystem.CR
									+ "From=[" + fromFile + "]" + HybsSystem.CR;
				throw new HybsSystemException( errMsg );
			}
			executeCount++ ;
		}
	}

	/**
	 * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を
	 * 処理の対象とします。
	 *
	 * @return int[]
	 */
	protected int[] getParameterRows() {
		final int[] rowNo ;
		if( selectedAll ) {
			int rowCnt = table.getRowCount();
			rowNo = new int[ rowCnt ];
			for( int i=0; i<rowCnt; i++ ) {
				rowNo[i] = i;
			}
		} else {
			rowNo = super.getParameterRows();
		}
		return rowNo ;
	}

	/**
	 * 【TAG】アクション(SAVE,LOAD,DELETE)をセットします。
	 *
	 * @og.tag
	 * アクションは,HTMLから（get/post)指定されますので,ACT_xxx で設定される
	 * フィールド定数値のいづれかを、指定できます。
	 * 無指定の場合は、なにもしません。
	 *
	 * <table>
	 * <th><td>action	</td><td>名称</td><td>機能</td></th>
	 * <tr><td>SAVE		</td><td>登録</td><td>指定の keys のキーに vals の値をセットします。</td></tr>
	 * <tr><td>LOAD		</td><td>取得</td><td>指定の keys のクッキーを(リクエスト中に)取得します。</td></tr>
	 * <tr><td>DELETE	</td><td>削除</td><td>指定の keys のクッキーを削除します。</td></tr>
	 * </table>
	 *
	 * @param	act アクション（public static final 宣言されている文字列)
	 * @see		<a href="{@docRoot}/constant-values.html#org.opengion.hayabusa.taglib.CookieTag.ACT_DELETE">アクション定数</a>
	 */
	public void setAction( final String act ) {
		action = nval( getRequestParameter( act ),action );

		if( action != null && !check( action, ACTION_LIST ) ) {
			String errMsg = "指定のアクションは実行できません。アクションエラー" + HybsSystem.CR
							+ "action=[" + action + "] "						+ HybsSystem.CR
							+ StringUtil.array2csv( ACTION_LIST ) ;
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】ターゲットとなるフォルダを指定します。
	 *
	 * @og.tag
	 * targetDir 属性を利用する場合は、引数のファイル、またはフォルダが指定されたことに
	 * なります。COPY、MOVE(,MODIFY) の場合は、targetDir 属性にフォルダを指定することで一括処理可能です。
	 * 指定先のフォルダが存在しない場合は、createDir属性の値により処理が異なります。
	 * createDir="true"(初期値)で、ターゲットフォルダが存在しない場合は、自動作成します。
	 *
	 * @param  dir ターゲットとなるフォルダ
	 * @see		#setCreateDir( String )
	 */
	public void setTargetDir( final String dir ) {
		targetDir = nval( getRequestParameter( dir ),targetDir );
	}

	/**
	 * 【TAG】ターゲットとなるフォルダがなければ、作成するかどうかを指定します(初期値:true)。
	 *
	 * @og.tag
	 * COPY,MOVE(,MODIFY) などの処理で、ターゲットフォルダが存在しないときに、作成するか、エラーにするかを
	 * createDir属性 で指定できます。
	 * これは、COPY先やMOVE(,MODIFY)先が存在している前提のシステムで、不要な箇所に間違ってフォルダを
	 * 自動作成されると困る場合に、false:作成しない とすれば、間違いに気づく確率が上がります。
	 * 初期値は true:作成する です。
	 *
	 * @param  flag ターゲットとなるフォルダを自動作成する(true)か、しない(false) 初期値は、true:作成する
	 */
	public void setCreateDir( final String flag ) {
		createDir = nval( getRequestParameter( flag ),createDir );
	}

	/**
	 * 【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します。
	 *
	 * @og.tag
	 * 検索結果より、DBTableModel オブジェクトを作成します。これを、以下のview 等のタグに
	 * 渡す場合に、通常は、session を利用します。その場合の登録キーです。
	 * query タグを同時に実行して、結果を求める場合、同一メモリに配置される為、
	 * この tableId 属性を利用して、メモリ空間を分けます。
	 * 初期値は、HybsSystem.TBL_MDL_KEY です。
	 *
	 * @param	id sessionに登録する時の ID
	 */
	public void setTableId( final String id ) {
		tableId = nval( getRequestParameter( id ),tableId );
	}

	/**
	 * 【TAG】コマンド(ENTRY)をセットします。
	 *
	 * @og.tag
	 * このタグは、command="ENTRY" でのみ実行されます。
	 * コマンドは,HTMLから（get/post)指定されますので,CMD_xxx で設定される
	 * フィールド定数値のいづれかを、指定できます。
	 * 初期値は、ENTRY なので、何も指定しなければ、実行されます。
	 *
	 * @param	cmd コマンド（public static final 宣言されている文字列)
	 * @see		<a href="{@docRoot}/constant-values.html#org.opengion.hayabusa.taglib.FileUpdateTag.CMD_ENTRY">コマンド定数</a>
	 */
	public void setCommand( final String cmd ) {
		String cmd2 = getRequestParameter( cmd );
		if( cmd2 != null && cmd2.length() >= 0 ) { command = cmd2.toUpperCase(Locale.JAPAN); }
	}

	/**
	 * 【TAG】検索結果のメッセージを表示する/しない(true/false)を指定します(初期値:true)。
	 *
	 * @og.tag
	 * 初期値は、表示する：true です。
	 *
	 * @param	flag 表示する ("true")／含めない (それ以外)
	 */
	public void setOutMessage( final String flag ) {
		outMessage = nval( getRequestParameter( flag ),outMessage );
	}

	/**
	 * 【TAG】処理結果を画面上に表示するメッセージリソースIDを指定します(初期値:MSG0040[　件登録しました])。
	 *
	 * @og.tag
	 * ここでは、検索結果の件数や登録された件数をまず出力し、
	 * その次に、ここで指定したメッセージをリソースから取得して表示します。
	 * 表示させたくない場合は, displayMsg = "" をセットしてください。
	 * displayMsg の初期値は、MSG0040[　件登録しました]です。
	 *
	 * @param	id ディスプレイに表示させるメッセージ ID
	 */
	public void setDisplayMsg( final String id ) {
		String ids = getRequestParameter( id );
		if( ids != null ) { displayMsg = ids; }
	}

	/**
	 * 【TAG】データを全件選択済みとして処理するかどうか(true/false)を指定します(初期値:false)。
	 *
	 * @og.tag
	 * 全てのデータを選択済みデータとして扱って処理します。
	 * 全件処理する場合に、（true/false)を指定します。
	 * 初期値は false です。
	 *
	 * @param  all データを全件選択済み(true) / 通常（false)
	 */
	public void setSelectedAll( final String all ) {
		selectedAll = nval( getRequestParameter( all ),selectedAll );
	}

	/**
	 * 【TAG】オリジナルのタイムスタンプを利用するかどうかを指定します(初期値:false)。
	 *
	 * @og.tag
	 * COPYや親違いMOVE(,MODIFY)の時に、オリジナルのタイムスタンプをそのままコピー先のファイルにも
	 * 適用するかどうかを指定します。
	 * タイムスタンプを初期化されたくない場合に、true に設定します。
	 * 初期値は 利用しない:false です。
	 *
	 * @param  flag タイムスタンプを利用するかどう(初期値:利用しない)。
	 */
	public void setKeepTimeStamp( final String flag ) {
		keepTimeStamp = nval( getRequestParameter( flag ),keepTimeStamp );
	}

	/**
	 * DBTableModel から、FromFile,ToFile を作成するための処理をまとめた補助クラスです。
	 *
	 * ここでは、オリジナルファイルやターゲットファイルを作成するための処理のみを集めています。
	 * メソッドにすると、ローカル変数を多く管理するか、多数の引数渡しを繰り返すことになるため、
	 * このローカルクラスに処理と、値を格納します。
	 *
	 */
	private static final class FromToFiles {
//		private static final String[] CLMS_LIST   = new String[] { "PARENT","NAME","TO_PARENT","TO_NAME" };

		private final DBTableModel	table ;

		private final int PARENT	;
		private final int NAME		;
		private final int TO_PARENT	;
		private final int TO_NAME	;

		private final File    toDir	;
		private final boolean createDir;	// ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)

		private boolean equalParent = false;	// 最後に実行された処理で、親フォルダが同一の場合は、true

		/**
		 *  引数指定のコンストラクター
		 *
		 * 必要なパラメータを渡して、オブジェクトを構築します。
		 *
		 * @param	table     DBTableModel	一覧が格納されているDBTableModel
		 * @param	targetDir String		ターゲットとなるフォルダ
		 * @param	createDir boolean		ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)
		 */
		public FromToFiles( final DBTableModel table , final String targetDir , final boolean createDir ) {
			this.table		= table;
			this.createDir	= createDir ;
			toDir = mkDirs( targetDir,createDir );	// targetDir が指定されていない場合は、null

			// "PARENT","NAME","TO_PARENT","TO_NAME" のカラム名のDBTableModelのカラム番号。存在しない場合は、-1
			PARENT		= table.getColumnNo( "PARENT"	, false );
			NAME		= table.getColumnNo( "NAME"		, false );
			TO_PARENT	= table.getColumnNo( "TO_PARENT", false );
			TO_NAME		= table.getColumnNo( "TO_NAME"	, false );
		}

		/**
		 * 行番号より、対応するオリジナルファイル(FromFile)を返します。
		 *
		 * ここでは、TO_PARENT や TO_NAME は、判定する必要がないため、makeFromToFile( int ) の
		 * 一部のみで処理が終了できます。
		 * １．FromDir は、PARENT 列の値から作成する。
		 * ２．FromFileは、FromDir ＋ NAME列の値から作成する。
		 *
		 * 配列返しの関係で、メソッド(および内部処理)を分けています。
		 *
		 * @param	rowNo int カラムNo
		 * @return	File オリジナルファイル(FromFile)
		 * @see		#makeFromToFile( int )
		 */
		public File makeFromOnly( final int rowNo ) {
			String[] value = table.getValues( rowNo );
			File fromDir  = mkDirs( value[PARENT],createDir );

			return new File( fromDir, value[NAME] ) ;
		}

		/**
		 * 行番号より、対応するオリジナルファイル(FromFile)とターゲットファイル(ToFile)を配列に格納して返します。
		 *
		 * ここでは、TO_PARENT や TO_NAME は、存在するかどうか不明なので、以下の手順で作成します。
		 * １．FromDir は、PARENT 列の値から作成する。
		 * ２．FromFileは、FromDir ＋ NAME列の値から作成する。
		 * ３．toDir は、
		 *       Ａ．targetDir が有れば、それを使う。
		 *       Ｂ．なければ、TO_PARENT 列の値から作成する。
		 *       Ｃ．TO_PARENT 列がないか、値が未設定の場合は、FromDir をそのまま使う。
		 * ４．toFile は、
		 *       Ａ．toDir ＋ TO_NAME 列の値から作成する。
		 *       Ｂ．TO_NAME 列がないか、値が未設定の場合は、toDir ＋ NAME列の値から作成する。
		 * 返り値は、new File[] { formFile , toFile }; とする。
		 *
		 * @param	rowNo int カラムNo
		 * @return	File[] ファイル配列(0:オリジナルファイル 1:ターゲットファイル)
		 */
		public File[] makeFromToFile( final int rowNo ) {

			String[] value = table.getValues( rowNo );
			File fromDir  = mkDirs( value[PARENT],createDir );
			File formFile = new File( fromDir, value[NAME] );
			File tempToDir = toDir;

			equalParent = false;	// 最後に実行された処理で、親フォルダが同一かどうかのフラグをリセットする。
			if( tempToDir == null ) {
				if( TO_PARENT >= 0 && nval(value[TO_PARENT],null) != null ) {
					tempToDir = mkDirs( value[TO_PARENT],createDir );
				}
				else  {
					tempToDir = fromDir;
					equalParent = true;		// 最後に実行された処理で、親フォルダが同一の場合は、true
				}
			}

			File toFile = null;
			if( TO_NAME >= 0 && nval(value[TO_NAME],null) != null  ) {
				toFile = new File( tempToDir, value[TO_NAME] );
			}
			else {
				toFile = new File( tempToDir, value[NAME] );
			}

			return new File[] { formFile , toFile };
		}

		/**
		 * 最後に実行された処理で、親フォルダが同一かどうかを返します(同一の場合は、true)。
		 *
		 * makeFromToFile( int ) が処理されたときの、FromDir と toDir が同一であれば、true を、
		 * 異なる場合は、false を返します。
		 * ここでの結果は、厳密な同一判定ではなく、処理的に、同一かどうかを判定しています。
		 * つまり、toDir に FromDir をセットする(３．Ｃのケース)場合に、true を内部変数にセットします。
		 * この判定値は、ファイルの移動処理で、異なる親フォルダの場合は、COPY ＆ DELETE しなければ
		 * なりませんが、同一親フォルダの場合は、RENAME で済む という処理負荷の軽減が目的です。
		 * よって、結果的に、PARENT と TO_PARENT が同じとか、PARENT と targetDir が同じでも
		 * ここでのフラグは、false が返されます。
		 *
		 * @return	最後に実行された処理で、親フォルダが同一の場合は、true
		 * @see		#makeFromToFile( int )
		 */
		public boolean lastParentEquals() {
			return equalParent ;
		}

		/**
		 *  カラム名配列(String[])より、対応するカラムNo配列(int[])を作成します。
		 *
		 * ここでは、TO_PARENT や TO_NAME は、存在するかどうか不明なので、
		 * EXCEPTION にせず、配列番号に、-1 を返すようにしています。
		 *
		 * @param	table     DBTableModel	一覧が格納されているDBTableModel
		 * @param	nameArray String[] 		カラム名配列
		 * @return	カラムNo配列(カラム名が存在しない場合は、-1)
		 */
	//	private int[] getTableColumnNo( final DBTableModel table ,final String[] nameArray ) {
	//		int[] clmNo = new int[ nameArray.length ];
	//		for( int i=0; i<clmNo.length; i++ ) {
	//			clmNo[i] = table.getColumnNo( nameArray[i] , false );	// カラム名が存在しない場合は、-1 を返す。
	//		}
	//		return clmNo;
	//	}

		/**
		 * フォルダを作成します。
		 *
		 * フォルダが存在しない場合は、途中階層をすべて作成します。
		 *
		 * @param	fname String フォルダ名
		 * @param	createDir boolean		ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)
		 * @return File フォルダを表すファイルオブジェクト。引数が null の場合は、null を返します。
		 * @throws	HybsSystemException		ファイルか、存在しない場合に、createDir=false か、mkdirs() が false の場合
		 */
		private File mkDirs( final String fname , final boolean createDir ) {
			File target = null;
			if( fname != null ) {
				target = new File( fname );
				if( target.exists() ) {			// 存在する
					if( target.isFile() ) {
						String errMsg = "ターゲットに、ファイル名は指定できません。" + HybsSystem.CR
											+ "ターゲット=[" + fname + "]"  + HybsSystem.CR;
						throw new HybsSystemException( errMsg );
					}
				}
				else {							// 存在しない
					// 存在しないのに、作成しない
					if( !createDir ) {
						String errMsg = "ターゲットが存在しません。 " + HybsSystem.CR
											+ "ターゲット=[" + fname + "]"  + HybsSystem.CR;
						throw new HybsSystemException( errMsg );
					}
					// 作成できない
					if( !target.mkdirs() ) {
						String errMsg = "ターゲットを自動作成使用としましたが、作成できませんでした。" + HybsSystem.CR
											+ "ターゲット=[" + fname + "]"  + HybsSystem.CR;
						throw new HybsSystemException( errMsg );
					}
				}
			}
			return target;
		}
	}

	/**
	 * シリアライズ用のカスタムシリアライズ書き込みメソッド
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectOutputStream
	 */
	private void writeObject( final ObjectOutputStream strm ) throws IOException {
		strm.defaultWriteObject();
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectInputStream
	 * @see #release2()
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
		strm.defaultReadObject();
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION		)
				.println( "action"			,action			)
				.println( "command"			,command		)
				.println( "targetDir" 		,targetDir		)
				.println( "createDir" 		,createDir		)
				.println( "tableId"			,tableId		)
				.println( "outMessage"		,outMessage 	)
				.println( "displayMsg"		,displayMsg 	)
				.println( "selectedAll"		,selectedAll	)
				.println( "keepTimeStamp"	,keepTimeStamp	)
				.fixForm().toString() ;
	}
}
