/*
 * 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.fukurou.process;

import org.opengion.fukurou.util.Argument;
import org.opengion.fukurou.util.HybsFileFilter;
import org.opengion.fukurou.util.LogWriter;

import java.io.File;
import java.util.Map ;
import java.util.LinkedHashMap ;
import java.util.Stack;

/**
 * Process_FileSearch は、指定のフォルダ以下のファイルを一覧する、FirstProcess
 * インターフェースと、ChainProcess インターフェースの実装クラスです。
 *
 * 指定の条件に合致するファイルを検索し、LineModel のサブクラスである、
 * FileLineModel オブジェクトを作成して、下流に渡します。
 * FileLineModel オブジェクトには、ファイル属性(Level,File,Length,Modify)
 * が設定されます。
 *
 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
 * 繋げてください。
 *
 * @og.formSample
 *  Process_FileSearch -start=d:/ -suffix=jsp
 *
 *     -start=開始フォルダ         ：検索を開始するフォルダ
 *   [ -prefix=接頭辞            ] ：File････,View････,など、指定の接頭辞で始まるファイルを検索
 *   [ -unprefix=不接頭辞        ] ：File････,View････,など、指定の接頭辞で始まらないファイルを検索
 *   [ -suffix=接尾辞            ] ：.txt,.java,.jsp.... など、指定の接尾辞で終わるファイルを検索
 *   [ -unsuffix=不接尾辞        ] ：.txt,.java,.jsp.... など、指定の接尾辞で終わらないファイルを検索
 *   [ -instr=部分文字列         ] ：ファイル名と一致する部分文字列を指定
 *   [ -uninstr=不部分文字列     ] ：ファイル名と一致しな部分文字列を指定
 *   [ -equals=一致              ] ：ファイル名と一致する文字列(大文字小文字は区別しない)を指定
 *   [ -notequals=不一致         ] ：ファイル名と一致しない文字列(大文字小文字は区別しない)を指定
 *   [ -match=正規表現           ] ：ファイル名と一致する正規表現を指定
 *   [ -unmatch=正規表現         ] ：ファイル名と一致しない正規表現を指定
 *   [ -modify=YYYYMMDD          ] ：指定日付け以降に変更されたファイルを検索
 *             YYYYMMDD   : YYYYMMDD 形式での指定日の 00:00:00 を基準時刻
 *             TODAY      : 実行日の 00:00:00 を基準時刻
 *             YESTERDAY  : 実行日前日の 00:00:00 を基準時刻
 *             LAST_WEEK  : 実行日の先週(7日前) 00:00:00 を基準時刻
 *             MONTH      : 実行月の 1日 00:00:00 を基準時刻
 *             LAST_MONTH : 実行前月の 同日 00:00:00 を基準時刻
 *             LAST_YEAR  : 実行前年の 同月同日 00:00:00 を基準時刻
 *   [ -useDIR=[false/true]      ] ：判定をファイルだけでなく、ディレクトリでも行うかどうかを指定(初期値:false)
 *   [ -larger=サイズ(Byte)      ] ：ファイルの大きさが指定のバイト数と同じか大きいファイルを検索
 *   [ -smaller=サイズ(Byte)     ] ：ファイルの大きさが指定のバイト数より小さいファイルを検索
 *   [ -isHidden=[true/false]    ] ：true:HIDDENファイルのみ検索/false:NORMALファイルのみ検索(初期値:null)
 *   [ -maxLevel=最大階層数      ] ：ディレクトリの階層を下がる最大数(初期値:256)
 *   [ -useLineCnt=行数計算      ] ：ファイルの行数をカウントするかどうかを指定(初期値:false)
 *   [ -useMD5=MD5計算値         ] ：ファイルのMD5計算を行うかどうかを指定(初期値:false)
 *   [ -useOmitCmnt=[false/true] ] ：コメント部分を削除した行数と文字数計算を行うかどうかを指定(初期値:false)
 *   [ -encode=エンコード名      ] ：コメント削除時の文字数計算で利用するファイルのエンコード(初期値:JISAutoDetect)
 *   [ -inPath=入力共通パス      ] ：BIKO作成用のファイルパスから削除する部分(文字数のみ)
 *   [ -outPath=出力追加パス     ] ：BIKO作成用のファイルパスに追加する部分
 *   [ -display=[false/true]     ] ：trueは、検索状況を表示します(初期値:false)
 *   [ -debug=[false/true]       ] ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class Process_FileSearch extends AbstractProcess
								implements FirstProcess , ChainProcess {

	private Stack<FileListStack>	dirs		= null;
	private File			file		= null;
	private HybsFileFilter	filter		= null;
	private FileLineModel	newData		= null;
	private int				level		= 1;

	private String			startDir	= null;
	private int				maxLevel	= 256;
	private int				inCount		= 0;
	private int				outCount	= 0;
	private int				inPathLen	= 0;		// 4.2.3.0 (2008/05/26) BIKO欄用
	private String			outPath		= null;		// 4.3.1.1 (2008/08/23) BIKO欄用
	private boolean			display		= false;	// 表示しない
	private boolean			debug		= false;	// 5.7.3.0 (2014/02/07) デバッグ情報

	private static final Map<String,String> mustProparty   ;		// ［プロパティ］必須チェック用 Map
	private static final Map<String,String> usableProparty ;		// ［プロパティ］整合性チェック Map

	static {
		mustProparty = new LinkedHashMap<String,String>();
		mustProparty.put( "start",	"検索を開始するフォルダ(必須)" );

		usableProparty = new LinkedHashMap<String,String>();
		usableProparty.put( "prefix",	"File････,View････,など、指定の接頭辞で始まるファイルを検索" );
		usableProparty.put( "unprefix",	"File････,View････,など、指定の接頭辞で始まらないファイルを検索" );
		usableProparty.put( "suffix",	".txt,.java,.jsp.... など、指定の接尾辞で終わるファイルを検索" );
		usableProparty.put( "unsuffix",	".txt,.java,.jsp.... など、指定の接尾辞で終わらないファイルを検索" );
		usableProparty.put( "instr",	"ファイル名と一致する部分文字列を指定" );
		usableProparty.put( "uninstr",	"ファイル名と一致しない部分文字列を指定" );
		usableProparty.put( "equals",	"ファイル名と一致する文字列(大文字小文字は区別しない)を指定" );
		usableProparty.put( "notequals",	"ファイル名と一致しない文字列(大文字小文字は区別しない)を指定" );
		usableProparty.put( "match",	"ファイル名と一致する正規表現を指定" );
		usableProparty.put( "unmatch",	"ファイル名と一致しない正規表現を指定" );
		usableProparty.put( "modify",	"指定日付け以降に変更されたファイルを検索" +
					CR + "YYYYMMDD   : YYYYMMDD 形式での指定日の 00:00:00 を基準時刻" +
					CR + "TODAY      : 実行日の 00:00:00 を基準時刻" +
					CR + "YESTERDAY  : 実行日前日の 00:00:00 を基準時刻" +
					CR + "LAST_WEEK  : 実行日の先週(7日前) 00:00:00 を基準時刻" +
					CR + "MONTH      : 実行月の 1日 00:00:00 を基準時刻" +
					CR + "LAST_MONTH : 実行前月の 同日 00:00:00 を基準時刻" +
					CR + "LAST_YEAR  : 実行前年の 同月同日 00:00:00 を基準時刻"
		);
		usableProparty.put( "useDIR"	,"判定をファイルだけでなく、ディレクトリでも行うかどうかを指定(初期値:false)" );
		usableProparty.put( "larger"	,"ファイルの大きさが指定のバイト数と同じか大きいファイルを検索" );
		usableProparty.put( "smaller"	,"ファイルの大きさが指定のバイト数より小さいファイルを検索" );
		usableProparty.put( "isHidden"	,"true:HIDDENファイルのみ検索/false:NORMALファイルのみ検索(初期値:null)" );
		usableProparty.put( "maxLevel"	,"ディレクトリの階層を下がる最大数(初期値:256)" );
		usableProparty.put( "useLineCnt","ファイルの行数をカウントするかどうかを指定(初期値:false)" );
		usableProparty.put( "useMD5"    ,"ファイルのMD5計算を行うかどうかを指定(初期値:false)" );		// 5.7.2.1 (2014/01/17)
		usableProparty.put( "useOmitCmnt" ,"コメント部分を削除した行数と文字数計算を行うかどうかを指定(初期値:false)" );		// 5.7.4.0 (2014/03/07)
		usableProparty.put( "encode"	,"コメント削除時の文字数計算で利用するファイルのエンコード(初期値:JISAutoDetect)" );	// 5.7.4.0 (2014/03/07)
		usableProparty.put( "inPath"	,"BIKO作成用のファイルパスから削除する部分(文字数のみ)" );
		usableProparty.put( "outPath"	,"BIKO作成用のファイルパスに追加する部分" );
		usableProparty.put( "display"	,"trueは、検索状況を表示します(初期値:false)" );
		usableProparty.put( "debug"		,"デバッグ情報を標準出力に表示する(true)かしない(false)か" +
										CR + "(初期値:false:表示しない)" );		// 5.7.3.0 (2014/02/07) デバッグ情報
	}

	/**
	 * デフォルトコンストラクター。
	 * このクラスは、動的作成されます。デフォルトコンストラクターで、
	 * super クラスに対して、必要な初期化を行っておきます。
	 *
	 */
	public Process_FileSearch() {
		super( "org.opengion.fukurou.process.Process_FileSearch",mustProparty,usableProparty );
	}

	/**
	 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
	 * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
	 *
	 * @og.rev 4.2.2.0 (2008/05/10) 行数カウントの使用有無
	 * @og.rev 4.3.1.1 (2008/08/23) BIKO 欄にoutPath 属性を追加します。
	 * @og.rev 5.1.2.0 (2010/01/01) useDIR 属性を追加します。
	 * @og.rev 5.7.2.1 (2014/01/17) useMD5 属性を追加します。
	 * @og.rev 5.7.4.0 (2014/03/07) useOmitCmnt,encode 属性を追加します。
	 * @og.rev 5.7.4.3 (2014/03/28) larger,smaller属性を文字列に変更
	 * @og.rev 5.7.5.0 (2014/04/04) isHidden属性を追加します。
	 *
	 * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
	 */
	public void init( final ParamProcess paramProcess ) {
		Argument arg = getArgument();

		startDir		= arg.getProparty("start" );

		String inPath	= arg.getProparty("inPath");
		if( inPath != null ) { inPathLen = inPath.length(); }

		String prefix	= arg.getProparty("prefix");
		String unprefix	= arg.getProparty("unprefix");		// 5.1.2.0 (2010/01/01) 追加
		String suffix	= arg.getProparty("suffix");
		String unsuffix	= arg.getProparty("unsuffix");		// 5.1.2.0 (2010/01/01) 追加
		String instr	= arg.getProparty("instr");
		String uninstr	= arg.getProparty("uninstr");		// 5.1.2.0 (2010/01/01) 追加
		String equals	= arg.getProparty("equals");
		String notequals= arg.getProparty("notequals");		// 5.1.2.0 (2010/01/01) 追加
		String match	= arg.getProparty("match");
		String unmatch	= arg.getProparty("unmatch");
		String modify	= arg.getProparty("modify");
		String larger	= arg.getProparty("larger");
		String smaller	= arg.getProparty("smaller");
		String isHidden	= arg.getProparty("isHidden");		// 5.7.5.0 (2014/04/04) 追加
		maxLevel		= arg.getProparty("maxLevel",maxLevel);
		outPath			= arg.getProparty("outPath");
		// 4.2.2.0 (2008/05/10) 行数カウントの使用有無
		boolean useLineCnt	= arg.getProparty( "useLineCnt",false );
		// 5.7.2.1 (2014/01/17) ファイルのMD5の計算有無
		boolean useMD5		= arg.getProparty( "useMD5",false );
		// 5.7.4.0 (2014/03/07) コメント部分を削除した行数と文字数計算を行うかどうか
		boolean useOmitCmnt	= arg.getProparty( "useOmitCmnt",false );
		// 5.7.4.0 (2014/03/07) コメント削除時の文字数計算で利用するファイルのエンコード(初期値:JISAutoDetect)
		String encode	= arg.getProparty( "encode" , "JISAutoDetect" );

		// 5.1.2.0 (2010/01/01) 判定をディレクトリでも行うかどうか
		boolean useDIR = arg.getProparty( "useDIR",false );

		display	= arg.getProparty("display",display);
		debug	= arg.getProparty("debug",debug);				// 5.7.3.0 (2014/02/07) デバッグ情報

		// 5.1.2.0 (2010/01/01) 反転処理の引数追加
		filter = new HybsFileFilter( useDIR );					// 5.1.2.0 (2010/01/01) 判定をディレクトリでも行うかどうか
		filter.startsWith( prefix   , false);
		filter.startsWith( unprefix , true );
		filter.endsWith( suffix   , false);
		filter.endsWith( unsuffix , true );
		filter.instr( instr   , false);
		filter.instr( uninstr , true );
		filter.fileEquals( equals   , false);
		filter.fileEquals( notequals , true );
		filter.matches( match   , false);
		filter.matches( unmatch , true );
		filter.lastModified( modify );
		// 5.7.4.3 (2014/03/28) larger,smaller属性を文字列に変更
		filter.isLarger( larger );
		filter.isSmaller( smaller );
		filter.isHidden( isHidden );			// 5.7.5.0 (2014/04/04) 追加

		File tempFile = new File( startDir );
		if( display ) { println( "start=[" + tempFile + "]" ); }		// 5.7.3.0 (2014/02/07)
		if( tempFile.isDirectory() ) {
			dirs = new Stack<FileListStack>();
			File[] fileList = tempFile.listFiles( filter );
			dirs.push( new FileListStack( fileList, level ) );
		}
		else {
			dirs = new Stack<FileListStack>();
			File[] fileList = new File[] { tempFile };
			dirs.push( new FileListStack( fileList, level ) );
		}

		newData = new FileLineModel( useLineCnt,useMD5,useOmitCmnt );	// 5.7.4.0 (2014/03/07)
		newData.setEncode( encode );
	}

	/**
	 * このデータの処理において、次の処理が出来るかどうかを問い合わせます。
	 * この呼び出し１回毎に、次のデータを取得する準備を行います。
	 *
	 * @og.rev 5.3.8.0 (2011/08/01) 処理中の状態を表示するための println を追加
	 *
	 * @return	処理できる:true / 処理できない:false
	 */
	public boolean next() {
		while( !dirs.empty() ) {
			FileListStack fStack = dirs.pop();

			level = fStack.getLevel();
			if( level > maxLevel ) { continue; }

			File[] fileList = fStack.getFileList();
			if( fileList == null ) { continue; }

			int address = fStack.getAddress();
			for( ; address < fileList.length; address++ ) {
				inCount++ ;
				if( fileList[address].isDirectory() ) {
					if( debug ) { println( "file Add=" + fileList[address].getAbsolutePath() ); }			// 5.7.3.0 (2014/02/07) デバッグ情報
					File[] newList = fileList[address].listFiles( filter );
					dirs.push( new FileListStack( newList,level+1) );
				}
				else {
					file = fileList[address];
					if( debug ) { println( "file=" + file ); }			// 5.7.3.0 (2014/02/07) デバッグ情報
					fStack.setAddress( address+1 );
					dirs.push( fStack );
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 最初に、 行データである LineModel を作成します
	 * FirstProcess は、次々と処理をチェインしていく最初の行データを
	 * 作成して、後続の ChainProcess クラスに処理データを渡します。
	 *
	 * @og.rev 4.2.3.0 (2008/05/26) BIKO 欄に展開ファイル名を記述します。
	 * @og.rev 4.3.1.1 (2008/08/23) BIKO 欄にoutPath 属性を追加します。
	 *
	 * @param	rowNo	処理中の行番号
	 *
	 * @return	処理変換後のLineModel
	 */
	public LineModel makeLineModel( final int rowNo ) {
		outCount++ ;
		newData.setFileVals( level,file );

		// 4.3.1.1 (2008/08/23)
		String biko = null;
		// 4.2.3.0 (2008/05/26) BIKO 欄追加
		if( inPathLen > 0 ) {
			biko = file.getAbsolutePath().substring( inPathLen );
		}

		if( outPath != null ) {
			if( biko == null ) {
				biko = outPath + file.getName() ;
			}
			else {
				biko = outPath + biko ;
			}
		}
		if( biko != null ) {
			newData.setBiko( biko );
		}

		newData.setRowNo( rowNo );

		return newData;
	}

	/**
	 * 引数の LineModel を処理するメソッドです。
	 * 変換処理後の LineModel を返します。
	 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
	 * null データを返します。つまり、null データは、後続処理を行わない
	 * フラグの代わりにも使用しています。
	 * なお、変換処理後の LineModel と、オリジナルの LineModel が、
	 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
	 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
	 * 各処理ごとに自分でコピー(クローン)して下さい。
	 *
	 * @param	data	オリジナルのLineModel
	 *
	 * @return	処理変換後のLineModel
	 */
	public LineModel action( final LineModel data ) {
		LineModel rtn = null;

		final FileLineModel fileData ;
		if( data instanceof FileLineModel ) {
			fileData = (FileLineModel)data ;
		}
		else {
			String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ;
			throw new RuntimeException( errMsg );
		}

		if( debug ) { println( "Before:" + data.dataLine() ); }		// 5.1.2.0 (2010/01/01) display の条件変更

		File inFile = fileData.getFile() ;
		File[] fileList = inFile.listFiles( filter );

		if( fileList != null && fileList.length > 0 ) {
			rtn = data;
		}

		if( display && rtn != null ) { println( rtn.dataLine() ); }		// 5.1.2.0 (2010/01/01) display の条件変更
		return rtn ;
	}

	/**
	 * プロセスの終了を行います。最後に一度だけ、呼び出されます。
	 * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
	 *
	 * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
	 */
	public void end( final boolean isOK ) {
		dirs		= null;
		file		= null;
		filter		= null;
		newData		= null;
	}

	/**
	 * プロセスの処理結果のレポート表現を返します。
	 * 処理プログラム名、入力件数、出力件数などの情報です。
	 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
	 * 形式で出してください。
	 *
	 * @return   処理結果のレポート
	 */
	public String report() {
		String report = "[" + getClass().getName() + "]" + CR
				+ TAB + "Start Folder : " + startDir  + CR
				+ TAB + "Search Count : " + inCount   + CR
				+ TAB + "Output Count : " + outCount ;

		return report ;
	}

	/**
	 * このクラスの使用方法を返します。
	 *
	 * @return	このクラスの使用方法
	 */
	public String usage() {
		StringBuilder buf = new StringBuilder();

		buf.append( "Process_FileSearch は、指定のフォルダ以下のファイルを一覧する、FirstProcess"	).append( CR );
		buf.append( "インターフェースと、ChainProcess インターフェースの実装クラスです。"			).append( CR );
		buf.append( CR );
		buf.append( "指定の条件に合致するファイルを検索し、ファイル属性(Level,File,Length,Modify)"	).append( CR );
		buf.append( "を元に、LineModelを作成し、下流に渡します。"									).append( CR );
		buf.append( CR );
		buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"	).append( CR );
		buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"		).append( CR );
		buf.append( "繋げてください。"																).append( CR );
		buf.append( CR );
		buf.append( "  -start=開始フォルダ    ：検索を開始するフォルダ"									).append( CR );
		buf.append( "[ -prefix=接頭辞        ]：File････,View････,などの接頭辞で始まるファイル"			).append( CR );
		buf.append( "[ -unprefix=不接頭辞    ]：File････,View････,などの接頭辞で始まらないファイル"		).append( CR );
		buf.append( "[ -suffix=接尾辞        ]：.txt,.java,.jsp.... などの接尾辞で終わるファイル"		).append( CR );
		buf.append( "[ -unsuffix=不接尾辞    ]：.txt,.java,.jsp.... などの接尾辞で終わらないファイル"	).append( CR );
		buf.append( "[ -instr=部分文字列     ]：ファイル名と一致する部分文字列"			                ).append( CR );
		buf.append( "[ -uninstr=不部分文字列 ]：ファイル名と一致しな部分文字列"							).append( CR );
		buf.append( "[ -equals=一致          ]：ファイル名と一致する文字列(大文字小文字は区別しない)"	).append( CR );
		buf.append( "[ -notequals=不一致     ]：ファイル名と一致しない文字列(大文字小文字は区別しない)"	).append( CR );
		buf.append( "[ -match=正規表現       ]：ファイル名と一致する正規表現"							).append( CR );
		buf.append( "[ -unmatch=正規表現     ]：ファイル名と一致しない正規表現"							).append( CR );
		buf.append( "[ -modify=YYYYMMDD      ]：指定日付け以降に変更されたファイル"						).append( CR );
		buf.append( "          YYYYMMDD   : YYYYMMDD 形式での指定日の 00:00:00 を基準時刻"				).append( CR );
		buf.append( "          TODAY      : 実行日の 00:00:00 を基準時刻"								).append( CR );
		buf.append( "          YESTERDAY  : 実行日前日の 00:00:00 を基準時刻"							).append( CR );
		buf.append( "          LAST_WEEK  : 実行日の先週(7日前) 00:00:00 を基準時刻"					).append( CR );
		buf.append( "          MONTH      : 実行月の 1日 00:00:00 を基準時刻"							).append( CR );
		buf.append( "          LAST_MONTH : 実行前月の 同日 00:00:00 を基準時刻"						).append( CR );
		buf.append( "          LAST_YEAR  : 実行前年の 同月同日 00:00:00 を基準時刻"					).append( CR );
		buf.append( "[ -useDIR=[false/true]  ]：判定をディレクトリ名も含めて行うかどうか(初期値:false)"	).append( CR );
		buf.append( "[ -larger=サイズ(Byte)  ]：大きさが指定のバイト数と同じか大きいファイル"			).append( CR );
		buf.append( "[ -smaller=サイズ(Byte) ]：大きさが指定のバイト数より小さいファイル"				).append( CR );
		buf.append( "[ -isHidden=[false/true]]：true:HIDDENのみ検索/false:NORMALのみ検索(初期値:null)"	).append( CR );
		buf.append( "[ -maxLevel=最大階層数  ]：ディレクトリの階層を下がる最大数(初期値:256)"			).append( CR );
		buf.append( "[ -useLineCnt=行数計算  ]：ファイルの行数をカウントするかどうか(初期値:false)"		).append( CR );
		buf.append( "[ -useMD5=MD5計算値     ]：ファイルのMD5計算を行うかどうかを指定(初期値:false)" 	).append( CR );
		buf.append( "[ -inPath=入力共通パス  ]：BIKO作成用のファイルパスから削除する文字列"				).append( CR );
		buf.append( "[ -outPath=出力追加パス ]：BIKO作成用のファイルパスに追加する文字列"				).append( CR );
		buf.append( "[ -display=[false/true] ]：trueは、検索状況を表示(初期値:false)"					).append( CR );
		buf.append( "[ -debug=[false/true]   ]：trueは、デバッグ状況を表示(初期値:false)"				).append( CR );
		buf.append( CR ).append( CR );
		buf.append( getArgument().usage() ).append( CR );

		return buf.toString();
	}

	/**
	 * このクラスは、main メソッドから実行できません。
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		LogWriter.log( new Process_FileSearch().usage() );
	}

	/**
	 * このクラスはファイルをスタックを使用して展開する場合の
	 * 個々の状態を保持する為のクラスです。
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static final class FileListStack {
		private int address ;
		private final File[] files;
		private final int level;

		/**
		 * コンストラクター
		 * 初期値を設定します。
		 * ファイルの配列については、コピーせずそのまま内部配列にセットしています。
		 *
		 * @param files File[] ファイルの配列(ファイルリスト)
		 * @param	level	レベル(指定のstartフォルダからの階層数)
		 */
		FileListStack( final File[] files,final int level ) {
			this.files   = files;
			this.address = 0;
			this.level   = level;
		}

		/**
		 * ファイルリストのアドレスを設定します。
		 * スタックから取り出した後、配列を前回の続きからサーチする場合に使用します。
		 *
		 * @param	address	ファイルリストのアドレス
		 */
		void setAddress( final int address ) {
			this.address = address;
		}

		/**
		 * ファイルリストのアドレスを取り出します。
		 *
		 * @return	ファイルリストのアドレス
		 */
		int getAddress() {
			return address;
		}

		/**
		 * ファイルリストを取り出します。
		 * ファイルの配列については、コピーせずそのまま内部配列を返しています。
		 *
		 * @return File[] ファイルリスト
		 */
		File[] getFileList() {
			return files;
		}

		/**
		 * 階層レベルを取り出します。
		 *
		 * @return	レベル
		 */
		int getLevel() {
			return level;
		}
	}
}
