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

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import static org.opengion.fukurou.system.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Collections;										// 6.4.3.1 (2016/02/12) refactoring
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

/**
 * Argument は、バッチ処理の main メソッドの引数を解析するクラスです。
 * Argument は、３つのタイプに分かれます。
 *
 * ［コメント］  ： # で始まる引数で、使用されません。(登録もされません。)
 * ［引数］      ： #,-,= 以外で始まる通常の文字列。登録の順番が指定されます。
 * ［プロパティ］： - で始まり、キーと値を=で区切っているパラメータです。順序は無関係。
 *
 * これらのタイプを混在させても構いません。[引数]は、[コメント] や[プロパティ]を
 * 無視した、入力の順番が重要視されます。取り出す場合も、番号で取り出します。
 * 最初の［引数］が、0 で、以降 引数個数-1 までの番号で取り出します。
 * ［プロパティ］は、順番は無視し、キー部を指定することで取り出せます。
 * ただし、キー部を重複して登録することは出来ません。なお、キー部の頭の文字列のみで
 * 取り出すメソッドがあるため、key1,key2,key3 などと指定して、key で取り出せば、
 * 複数プロパティを同一キーで取り出すことが可能です。
 * ［プロパティ］の指定では、キーと値を=で区切りますが、その前後にスペースを
 * 入れないで下さい。引数の前後に = が付く文字列は指定できません。
 *
 * java Program AAA BBB #CCC -DD=XX -EE=YY -FF=ZZ GGG
 *              ~~~ ~~~ ~~~~ ~~~~~~ ~~~~~~ ~~~~~~ ~~~
 * ［コメント］  ： #CCC
 * ［引数］      ： [0]=AAA , [1]=BBB , [2]=GGG
 * ［プロパティ］： key=DD,val=XX  key=EE,val=YY  key=FF,val=ZZ
 *
 * Argument の整合性チェックは、３つのパターンがあります。
 *
 * ［引数］個数指定 ：引数自身の最小個数、最大個数を登録しておくことで、プロパティのハイフン忘れ等を防止します。
 * ［プロパティ］必須チェック ：必須キーが登録されたかどうかのチェックを行います。
 * ［プロパティ］整合性チェック ： 指定されているキーのみ登録可能です。
 *
 * これらのチェックで、整合性チェックのみ、Argument の登録時に行います。
 * それ以外は、取り出し時まで、判断できません。
 * (取り出しは、登録がすべて終了したのちに行われると仮定しています)
 *
 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
 * ［プロパティ］設定可能なプロパティの値を指定することで、誤記入を防止します。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class Argument  {
	/** Argument引数のタイプ ［コメント］は、無視されます。 {@value}  */
	public static final int CMNT = 0;	// ［コメント］

	/** Argument引数のタイプ ［引数］は、入力順にアクセスできます。 {@value}  */
	public static final int ARGS = 1;	// ［引数］

	/** Argument引数のタイプ ［プロパティ］は、-KEY=VALUE 形式でキーでアクセスできます。 {@value}  */
	public static final int PROP = 2;	// ［プロパティ］

	private boolean argOkFlag	;
	private final List<String> argments = new ArrayList<>();
	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。  */
//	private final Map<String,String> proparty = new LinkedHashMap<>();
//	private final Map<String,String> proparty = Collections.synchronizedMap( new LinkedHashMap<>() );
	private final Map<String,String> propMap = Collections.synchronizedMap( new LinkedHashMap<>() );

	private int argRangeMin ;			// 初期値:0
	private int argRangeMax = 200 ;		// 本当は、Windows の引数の上限値を設定

	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。  */
//	private Map<String,String> mustProparty   ;
	private final Map<String,String> mustProparty   = Collections.synchronizedMap( new LinkedHashMap<>() );
	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。  */
//	private Map<String,String> usableProparty ;
	private final Map<String,String> usableProparty = Collections.synchronizedMap( new LinkedHashMap<>() );

	private final String programID  ;

	/**
	 * この Argument を使用している プログラムID(Javaクラス名)を指定して
	 * インスタンスを作成します。
	 * toString() する際に、表示します。
	 *
	 * @param   pgid プログラムID
	 */
	public Argument( final String pgid ) {
		programID = pgid;
	}

	/**
	 * Argument の配列文字列から、引数やプロパティをセットします。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * これは、main メソッド等で単独起動する場合に、引数そのままを
	 * セットする場合に使用します。
	 *
	 * @param   args 引数配列(可変長引数)
	 * @see  #putArgument( String )
	 */
	public void setArgument( final String... args ) {
		for( int i=0; i<args.length; i++ ) {
			putArgument( args[i] );
		}
	}

	/**
	 * Argument の文字列から、引数かプロパティをセットします。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * Argument を設定する時に、タイプ判断として、getArgumentType( String ) を
	 * 使用します。よって、不正な Argument を設定した場合は、強制終了されます。
	 *
	 * @param   arg 引数
	 * @see  #putArgument( String,String )
	 */
	public void putArgument( final String arg ) {
		final int type = getArgumentType( arg );

		switch( type ) {
			case CMNT : break;
			case ARGS : argments.add( arg ); break;
			case PROP :
				final int sep = arg.indexOf( '=' );	// sep は、 0 以上保証済み
				final String key = arg.substring(1,sep);
				final String val = arg.substring(sep+1);
				checkProparty( key );		// 3.8.0.1 (2005/06/17)
				propMap.put( key,val );
				break;
			default: break;
		}
	}

	/**
	 * Argument の文字列から、プロパティをセットします。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * このメソッドは、引数 や コメントの判断を行いません。プロパティ のみ
	 * 設定されるものとして、処理します。
	 * プロパティの key=val が初めから分割されている場合の簡易メソッドです。
	 *
	 * @param   key プロパティのキー
	 * @param   val プロパティの値
	 * @see  #putArgument( String )
	 */
	public void putArgument( final String key,final String val ) {
		checkProparty( key );		// 3.8.0.1 (2005/06/17)
		propMap.put( key,val );
	}

	/**
	 * ［引数］個数指定を設定します。
	 * 最大値、最小値を登録しておくことで、個数が、規定から外れていないか
	 * どうかを確認します。
	 * エラー判定は、実際に、［引数］を取り出すときに行われます。
	 * このチェックの登録は、putArgument( String ) の前でも後でもよく、
	 * getArgument の実行前であれば、いつでも構いません。
	 * 設定しない場合の初期値は、0～200 です。
	 *
	 * @param   min ［引数］の最小個数(初期値:0)
	 * @param   max ［引数］の最大個数(初期値:200)
	 */
	public void setArgRange( final int min, final int max ) {
		argRangeMin = min ;
		argRangeMax = max ;
	}

	/**
	 * ［プロパティ］必須チェック  Map 登録
	 * 必須キーが登録されたかどうかのチェックを行います。
	 * マスト判定は、実際に、［プロパティ］を取り出すときに行われます。
	 * すべてのプロパティーがセットし終わったかどうかの判断が出来ないためです。
	 * それ以外のチェックは、putArgument( String ) 時に行われるので、それまでに
	 * mustProparty のMapを登録しておく必要があります。
	 * ただし、引数文字列の記述チェック(使用してもよい値の配列チェック)は、
	 * #getProparty( String , String , String[] ) で行われるので、取得時になります。
	 *
	 * 設定しない場合の初期値は、制限なしです。
	 * 指定のMapのValue値には、エラー時のコメントを記述しておきます。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
	 *
	 * @param   mustProp 必須キーのMap
	 * @see #getProparty( String , String , String[] )
	 */
	public void setMustProparty( final Map<String,String> mustProp ) {
//		mustProparty = new LinkedHashMap<>( mustProp ) ;
		mustProparty.putAll( mustProp ) ;
	}

	/**
	 * ［プロパティ］整合性チェック Map 登録
	 * 指定されているキーのみ登録可能です。
	 * エラー判定は、実際に、［プロパティ］を取り出すときに行われます。
	 * このチェックの登録は、putArgument( String ) 時に行われるので、それまでに
	 * usableProparty のMapを登録しておく必要があります。
	 * ただし、引数文字列の記述チェック(使用してもよい値の配列チェック)は、
	 * #getProparty( String , String , String[] ) で行われるので、取得時になります。
	 *
	 * 設定しない場合の初期値は、制限なしです。
	 * 指定のMapのValue値には、このキーに対する解説を登録しておきます。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
	 *
	 * @param   useProp 使用可能キーのMap
	 */
	public void setUsableProparty( final Map<String,String> useProp ) {
//		usableProparty = new LinkedHashMap<>( useProp ) ;
		usableProparty.putAll( useProp ) ;
	}

	/**
	 * Argument の文字列から、そのタイプを判断します。
	 * 引数の形式が不正な場合(例えば、キーと値の分離の = の前後にスペースが入った場合)
	 * RuntimeException で強制終了します。
	 *
	 * ［コメント］  ： # で始まる引数で、使用されません。(登録もされません。)
	 * ［引数］      ： #,-,= 以外で始まる通常の文字列。登録の順番が指定されます。
	 * ［プロパティ］： - で始まり、キーと値を=で区切っているパラメータです。順序は無関係。
	 *
	 * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
	 *
	 * @og.rev 5.3.4.0 (2011/04/01) 空文字列など無関係なパラメータは処理しないように変更
	 *
	 * @param   arg 引数
	 *
	 * @return  引数タイプ(CMNT,ARGS,PROP)
	 * @see Argument#CMNT ［コメント］
	 * @see Argument#ARGS ［引数］
	 * @see Argument#PROP ［プロパティ］
	 */
	public int getArgumentType( final String arg ) {
		if( arg == null || arg.trim().isEmpty() || '#' == arg.charAt(0) ) {
			return CMNT;
		}
		else if( '=' == arg.charAt(0) || arg.endsWith( "=" ) ) {	// 不正引数
			final String errMsg = "引数の = の前後には、スペースを入れないで下さい。"
					+	" BAD Argument=[" + arg + "]"  ;
			throw new OgRuntimeException( errMsg );
		}
		else if( '-' == arg.charAt(0) ) {
			final int sep = arg.indexOf( '=' );
			if( sep > 0 && sep < arg.length()-1 ) {
				return PROP;
			}
			else {
				final String errMsg = "-KEY を指定する場合は、= を続けて、VALUEを指定して下さい。"
						+	"  -KEY=VALUE 形式 BAD Argument=[" + arg + "]"  ;
				throw new OgRuntimeException( errMsg );
			}
		}
		else {
			return ARGS ;
		}
	}

	/**
	 * 指定の番号に対する［引数］を返します。
	 * ［引数］は、#,-,= 以外で始まる通常の文字列として登録されています。
	 * 登録された順番で取得します。
	 *
	 * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
	 *
	 * @param   adrs 番号
	 *
	 * @return  ［引数］
	 */
	public String getArgument( final int adrs ) {
		// 以下のチェックは、getArgument が呼ばれて一度のみの実行でよい。
		if( ! argOkFlag ) {
			if( argRangeMin < argments.size() || argments.size() < argRangeMax ) {
				final String errMsg = "［引数］個数が最小/最大個数を満たしていません。"
						+	"  Min:" + argRangeMin + " <= " + argments.size() + " < Max:" + argRangeMax  ;
				throw new OgRuntimeException( errMsg );
			}
			argOkFlag = true;
		}

		if( argments.size() <= adrs ) {
			final String errMsg = "指定のアドレスは、［引数］設定個数外です。"
					+	"  Size:" + argments.size() + " <= " + adrs ;
			throw new OgRuntimeException( errMsg );
		}

		return argments.get( adrs );
	}

	/**
	 * 指定の番号に対する［引数］を返します。
	 * def には、文字列の初期値を指定しておきます。adrs に対応する値が、null の場合、
	 * この def をそのまま返します。
	 *
	 * 処理は、getArgument( int ) の結果を、使用しています。
	 *
	 * @param   adrs 番号
	 * @param   def 値が null の場合の初期値
	 *
	 * @return  ［引数］
	 * @see #getArgument( int )
	 */
	public String getArgument( final int adrs, final String def ) {
		final String value = getArgument( adrs );
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		return value == null ? def : value ;
//		return ( value != null ) ? value : def ;
	}

	/**
	 * 指定の番号に対する［引数］を返します。
	 * def には、数字の初期値を指定しておきます。adrs に対応する値が、null の場合、
	 * この def をそのまま返します。
	 *
	 * 処理は、getArgument( int ) の結果を、使用しています。
	 *
	 * @param   adrs 番号
	 * @param   def 値が null の場合の初期値
	 *
	 * @return  ［引数］
	 * @see #getArgument( int )
	 */
	public int getArgument( final int adrs, final int def ) {
		final String value = getArgument( adrs );
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		return value == null ? def : Integer.parseInt( value ) ;
//		return ( value != null ) ? Integer.parseInt( value ) : def ;
	}

	/**
	 * 指定の番号に対する［引数］を返します。
	 * def には、boolean の初期値を指定しておきます。adrs に対応する値が、null の場合、
	 * この def をそのまま返します。
	 *
	 * 処理は、getArgument( int ) の結果を、使用しています。
	 *
	 * @param   adrs 番号
	 * @param   def 値が null の場合の初期値
	 *
	 * @return  ［引数］
	 * @see #getArgument( int )
	 */
	public boolean getArgument( final int adrs, final boolean def ) {
		final String value = getArgument( adrs );
		return ( value == null ) ? def : Boolean.parseBoolean( value ) ;		// 6.1.0.0 (2014/12/26) refactoring
	}

	/**
	 * ［プロパティ］整合性チェック 実行
	 * 設定された整合性チェックを実行します。
	 * 複数キーに対応する為に、先頭からの判定も行います。
	 * チェックするキーの大文字･小文字は、厳格に判定しています。
	 *
	 * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
	 *
	 * @og.rev 5.1.5.0 (2010/04/01) 判定の条件が、重複していたので修正。
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
	 * @og.rev 6.4.3.3 (2016/03/04) Iterator 処理を、拡張for文に変更。
	 *
	 * @param   key チェックする入力キー
	 */
	private void checkProparty( final String key ) {

		// 第１の判定。 propMap にすでに存在していれば、エラーになる。
		if( propMap.get( key ) != null ) {
			final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
				.append( "キー[" ).append( key ).append( "]は、すでに指定済みです。" ).append( CR )
				.append( "  登録済み：-" )
				.append( key ).append( '=' ).append( propMap.get( key ) )		// 6.0.2.5 (2014/10/31) char を append する。
				.append( CR );
			throw new OgRuntimeException( errMsg.toString() );
		}

		// 6.4.3.1 (2016/02/12) インスタンスで初期化しているため、null はない。
//		if( mustProparty != null ) {
		if( !mustProparty.isEmpty() ) {								// 6.4.3.1 (2016/02/12)
			// 第２の判定。 mustProparty に存在すれば、即抜けする。
			if( mustProparty.containsKey( key ) ) { return; }

			// 第３の判定。複数キー(先頭一致キー)の場合もありうるため、先頭からの比較を行う。
			// 6.4.3.3 (2016/03/04) Iterator 処理を、拡張for分に変更。判定は、keyのみでよい
			for( final String propKey : mustProparty.keySet() ) {
				if( key.startsWith( propKey ) ) { return ; }		// マッチすれば、即抜ける。
			}
//			final Iterator<Map.Entry<String,String>> ite = mustProparty.entrySet().iterator();	// 4.3.3.6 (2008/11/15) Generics警告対応
//			while( ite.hasNext() ) {
//				final Map.Entry<String,String> entry = ite.next();	// 4.3.3.6 (2008/11/15) Generics警告対応
//				final String propKey = entry.getKey();				// 4.3.3.6 (2008/11/15) Generics警告対応
//				if( key.startsWith( propKey ) ) { return ; }		// マッチすれば、即抜ける。
//			}
		}

		// 5.1.5.0 (2010/04/01) 判定の条件が、重複していたので修正。
		// 6.4.3.1 (2016/02/12) インスタンスで初期化しているため、null はない。
//		if( usableProparty != null ) {
		if( !usableProparty.isEmpty() ) {							// 6.4.3.1 (2016/02/12)
			// 第４の判定。 usableProparty に存在すれば、即抜けする。
			if( usableProparty.containsKey( key ) ) { return ; }

			// 第５の判定。複数キー(先頭一致キー)の場合もありうるため、先頭からの比較を行う。
			// 6.4.3.3 (2016/03/04) Iterator 処理を、拡張for分に変更。判定は、keyのみでよい
			for( final String propKey : usableProparty.keySet() ) {
				if( key.startsWith( propKey ) ) { return ; }		// マッチすれば、即抜ける。
			}
//			final Iterator<Map.Entry<String,String>> ite = usableProparty.entrySet().iterator();	// 4.3.3.6 (2008/11/15) Generics警告対応
//			while( ite.hasNext() ) {
//				final Map.Entry<String,String> entry = ite.next();	// 4.3.3.6 (2008/11/15) Generics警告対応
//				final String propKey = entry.getKey();				// 4.3.3.6 (2008/11/15) Generics警告対応
//				if( key.startsWith( propKey ) ) { return ; }		// マッチすれば、即抜ける。
//			}

			// そこまで探して見つからない場合は、定義外引数エラー
			final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
				.append( "-KEY が、指定の整合性リストに含まれていません。" )
				.append( CR )
				.append( "  -KEY=VALUE 形式 BAD Key=[" ).append( key ).append( ']' )		// 6.0.2.5 (2014/10/31) char を append する。
				.append( CR )
				.append( toString() );
			throw new OgRuntimeException( errMsg.toString() );
		}
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを指定して取得します。
	 * 値が設定されていない場合は、 null を返します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 *
	 * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
	 *
	 * @param   key 引数のキー
	 *
	 * @return  引数に対する値
	 */
	public String getProparty( final String key ) {

		final String value = propMap.get( key );

		// 値が null で must 設定があり、かつマストキーが指定している場合。
		if( value == null &&
			// 6.4.3.1 (2016/02/12) インスタンスで初期化しているため、null はない。
//			mustProparty != null &&
			!mustProparty.isEmpty() &&					// 6.4.3.1 (2016/02/12)
			mustProparty.containsKey( key ) ) {
				final String errMsg = "指定の［プロパティ］は、必須キーですが、値が null です。"
						+	"  Key:" + key + "  説明:" + mustProparty.get( key )
						+ CR + toString() ;
				throw new OgRuntimeException( errMsg );
		}

		return value ;
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを指定して取得します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * def には、文字列の初期値を指定しておきます。key に対応する値が、null の場合、
	 * この def をそのまま返します。
	 *
	 * 処理は、getProparty( String ) の結果を、使用しています。
	 *
	 * @param   key キー
	 * @param   def 値が null の場合の初期値
	 *
	 * @return  ［プロパティ］
	 * @see #getProparty( String )
	 */
	public String getProparty( final String key, final String def ) {
		final String value = getProparty( key );
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		return value == null ? def : value ;
//		return ( value != null ) ? value : def ;
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを指定して取得します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * def には、文字列の初期値を指定しておきます。key に対応する値が、null の場合、
	 * この def をそのまま返します。
	 * list 配列には、登録できる文字列配列を指定します。この文字列に含まれない
	 * 値が設定されていた場合は、エラーになります。
	 *
	 * 処理は、getProparty( String ) の結果を、使用しています。
	 *
	 * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
	 *
	 * @param   key キー
	 * @param   def  値が null の場合の初期値
	 * @param   list 値として存在できる文字列配列(可変長引数)
	 *
	 * @return  ［プロパティ］
	 * @see #getProparty( String )
	 */
	public String getProparty( final String key, final String def, final String... list ) {
		final String value = getProparty( key,def );
		if( value != null ) {
			boolean isOK = false;
			for( int i=0; i<list.length; i++ ) {
				if( value.equalsIgnoreCase( list[i] ) ) {
					isOK = true; break;
				}
			}
			if( !isOK ) {
				final String errMsg = key + " は、" + Arrays.toString( list )
									+ " から指定してください。" + CR
									+ "-" + key + "=[" + value + "]" ;
				throw new OgRuntimeException( errMsg );
			}
		}

		return value ;
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを指定して取得します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * def には、数字の初期値を指定しておきます。key に対応する値が、null の場合、
	 * この def をそのまま返します。
	 *
	 * 処理は、getProparty( String ) の結果を、使用しています。
	 *
	 * @param   key キー
	 * @param   def 値が null の場合の初期値
	 *
	 * @return  ［プロパティ］
	 * @see #getProparty( String )
	 */
	public int getProparty( final String key, final int def ) {
		final String value = getProparty( key );
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		return value == null ? def : Integer.parseInt( value ) ;
//		return ( value != null ) ? Integer.parseInt( value ) : def ;
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを指定して取得します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * def には、boolean の初期値を指定しておきます。key に対応する値が、null の場合、
	 * この def をそのまま返します。
	 *
	 * 処理は、getProparty( String ) の結果を、使用しています。
	 *
	 * @param   key キー
	 * @param   def 値が null の場合の初期値
	 *
	 * @return  ［プロパティ］
	 * @see #getProparty( String )
	 */
	public boolean getProparty( final String key, final boolean def ) {
		final String value = getProparty( key );
		return ( value == null ) ? def : Boolean.parseBoolean( value ) ;		// 6.1.0.0 (2014/12/26) refactoring
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを指定して取得します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * key プロパティは、通常の引数として指定できる簡易的な値を設定します。
	 * keyFile プロパティ は、ファイル名を指定し、そのファイルの中身を
	 * 取得して返します。通常は１行であるが、時には複数行のデータを指定
	 * したい場合に、２つのパラメータを使いますが、設定値は、同じ引数として
	 * 使用したいケースに便利です。
	 * key プロパティと、keyFile プロパティ は、同時指定できません。
	 * これは、指定方法の間違い等を避ける為です。
	 * どちらも、null である可能性はあります。
	 *
	 * ※ 同時指定時、または、must 必須時に null の場合、RuntimeException が throw されます。
	 *
	 * @param   key キー
	 * @param   keyFile  設定ファイル名
	 * @param   must 必須条件[true/false]
	 *
	 * @return  ［プロパティ］
	 * @see #getProparty( String )
	 */
	public String getFileProparty( final String key, final String keyFile, final boolean must ) {
		return getFileProparty( key,keyFile,null,must );
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを指定して取得します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * key プロパティは、通常の引数として指定できる簡易的な値を設定します。
	 * keyFile プロパティ は、ファイル名を指定し、そのファイルの中身を
	 * 取得して返します。通常は１行であるが、時には複数行のデータを指定
	 * したい場合に、２つのパラメータを使いますが、設定値は、同じ引数として
	 * 使用したいケースに便利です。
	 * key プロパティと、keyFile プロパティ は、同時指定できません。
	 * これは、指定方法の間違い等を避ける為です。
	 * どちらも、null である可能性はあります。
	 *
	 * ※ 同時指定時、または、must 必須時に null の場合、RuntimeException が throw されます。
	 *
	 * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
	 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
	 *
	 * @param   key キー
	 * @param   keyFile 設定ファイル名
	 * @param   encode keyFile読取エンコード(null はデフォルトエンコード)
	 * @param   must 必須条件[true/false]
	 *
	 * @return  ［プロパティ］
	 * @see #getProparty( String )
	 */
	public String getFileProparty( final String key, final String keyFile,
									 final String encode,final boolean must ) {
		String val     = getProparty( key );
		final String valFile = getProparty( keyFile );

		if( val != null && valFile != null ) {
			final String errMsg = key + "か、" + keyFile + " は、両方同時に指定できません。[" + val + "],[" + valFile + "]";
			throw new OgRuntimeException( errMsg );
		}

		if( valFile != null ) {
			// 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
//			final FileString fs = new FileString();
//			fs.setFilename( valFile );
//			fs.setEncode( encode );
//			final FileString fs = new FileString( valFile , encode );
//			val = fs.getValue();
			val = FileUtil.getValue( valFile , encode );		// 6.4.5.2 (2016/05/06)
		}

		if( must && val == null ) {
			final String errMsg = key + "か、" + keyFile + " は、片方必須です。";
			throw new OgRuntimeException( errMsg );
		}

		return val;
	}

	/**
	 * 内部で使用する［プロパティ］を、キーを先頭に含む値を取得します。
	 * ［プロパティ］のキー部の大文字･小文字は、厳格に判定しています。
	 * 値が設定されていない場合は、String[0] を返します。
	 * HybsEntry のキーに設定される値は、引数の先頭キーを除いた文字列です。
	 * 例えば、"const_" のような値を与えて、const_AA, const_BB, const_CC の
	 * ３つのキーが選定された場合、キーは、AA, BB, CC のみ返します。
	 *
	 * @param   startsKey 引数の先頭のキー
	 *
	 * @return  引数に対する［プロパティ］のHybsEntry
	 * @og.rtnNotNull
	 */
	public HybsEntry[] getEntrys( final String startsKey ) {
		final ArrayList<HybsEntry> list = new ArrayList<>();
		final int len = startsKey.length();

		final Iterator<Map.Entry<String,String>> ite = propMap.entrySet().iterator();	// 4.3.3.6 (2008/11/15) Generics警告対応
		while( ite.hasNext() ) {
			final Map.Entry<String,String> entry = ite.next();	// 4.3.3.6 (2008/11/15) Generics警告対応
			final String key = entry.getKey();					// 4.3.3.6 (2008/11/15) Generics警告対応
			if( key.startsWith( startsKey ) ) {
				list.add( new HybsEntry( key.substring( len ), entry.getValue() ) );	// 4.3.3.6 (2008/11/15) Generics警告対応
			}
		}

		return list.toArray( new HybsEntry[list.size()] ) ;
	}

	/**
	 * 入力文字列に、{&#064;XXXX}関係の文字列変換を行います。
	 * 引数に含まれる {&#064;XXXX}=YYYY という入力に対して、inMsg に
	 * 含まれる{&#064;XXXX} 文字列を、YYYY という文字列に変換します。
	 * それ以外に、予約文字変換として、
	 *   {&#064;ARG.XXX}  引数に使用された値を再利用(割り当て)します。
	 *   {&#064;DATE.XXX} SimpleDateFormat 形式の文字を変換します。(日付、時刻等)
	 *   {&#064;ENV.XXX}  システムプロパティーの文字を変換します。(java -Dkey=value オプション)
	 *
	 * @param  inMsg 入力文字列
	 *
	 * @return  変換後文字列
	 */
	public String changeParam( final String inMsg ) {
		if( inMsg == null ) { return inMsg; }

		String message = inMsg;

		// {@ARG.XXXX} 変数の置換処理
		int adrs = message.indexOf( "{@ARG." ) ;
		while( adrs >= 0 ) {
			final int end = message.indexOf( '}',adrs ) ;
			final String key = message.substring( adrs+6,end );
			final String oldData = "{@ARG." + key + "}" ;
			// 注意：{@XXX}と異なり、{@ARG.XXX} では、XXX で propMap を検索する。
			final String newData = StringUtil.nval( getProparty( key ),"" );
			message = StringUtil.replace( message,oldData,newData );
			adrs = message.indexOf( "{@ARG.",adrs ) ;
		}
		// {@DATE.XXXX} 変数の置換処理
		adrs = message.indexOf( "{@DATE." ) ;
		if( adrs >= 0 ) {
			final Date dt = new Date();
			while( adrs >= 0 ) {
				final int end = message.indexOf( '}',adrs ) ;
				final String key = message.substring( adrs+7,end );
				final String oldData = "{@DATE." + key + "}" ;
				final DateFormat formatter = new SimpleDateFormat( key, Locale.JAPAN );
				final String newData = StringUtil.nval( formatter.format(dt),"" );
				message = StringUtil.replace( message,oldData,newData );
				adrs = message.indexOf( "{@DATE.",adrs ) ;
			}
		}
		// {@ENV.XXXX} 変数の置換処理
		adrs = message.indexOf( "{@ENV." ) ;
		while( adrs >= 0 ) {
			final int end = message.indexOf( '}',adrs ) ;
			final String key = message.substring( adrs+6,end );
			final String oldData = "{@ENV." + key + "}" ;
			final String newData = System.getProperty( key,"" );
			message = StringUtil.replace( message,oldData,newData );
			adrs = message.indexOf( "{@ARG.",adrs ) ;
		}

		// 残りのメッセージ本文中の置換文字列を処理します。
		adrs = message.indexOf( "{@" ) ;
		while( adrs >= 0 ) {
			final int end = message.indexOf( '}',adrs ) ;
			final String key = message.substring( adrs,end+1 );	// +1 注意
			final String oldData = key ;
			// 注意：{@ARG.XXX} と異なり、{@XXX} そのもので propMap を検索する。
			final String newData = StringUtil.nval( getProparty( key ),"" );
			message = StringUtil.replace( message,oldData,newData );
			adrs = message.indexOf( "{@",adrs ) ;
		}

		return message;
	}

	/**
	 * このオブジェクトの内部表現を、文字列にして返します。
	 * クラス名 ＋ 起動時の引数リストを表示します。
	 *
	 * @return  引数に対する値
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		buf.append( "java " ).append( programID ).append( CR );

		if( ! argments.isEmpty() ) {
			for( int i=0; i<argments.size(); i++ ) {
				buf.append( ' ' ).append( argments.get(i) );		// 6.0.2.5 (2014/10/31) char を append する。
			}
			buf.append( CR );
		}

		final Iterator<Map.Entry<String,String>> propIte = propMap.entrySet().iterator();	// 4.3.3.6 (2008/11/15) Generics警告対応
		while( propIte.hasNext() ) {
			final Map.Entry<String,String> entry = propIte.next();	// 4.3.3.6 (2008/11/15) Generics警告対応
			final String key = entry.getKey();						// 4.3.3.6 (2008/11/15) Generics警告対応
			Object val = entry.getValue();
			if( key.startsWith( "passwd" ) ) {
				val = "*****" ;
			}

			buf.append( "    -" ).append( key ).append( '=' ).append( val );		// 6.0.2.5 (2014/10/31) char を append する。
			buf.append( CR );
		}

		return buf.toString();
	}

	/**
	 * このクラスの使用方法を返します。
	 *
	 * @return	このクラスの使用方法
	 * @og.rtnNotNull
	 */
	public String usage() {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( toString() )
			.append( CR )
			.append( propMapToString( "[ Must Proparty List ]"  , mustProparty   ) )
			.append( propMapToString( "[ Usable Proparty List ]", usableProparty ) );

		return buf.toString();
	}

	/**
	 * プロパティーを文字列に変換します。
	 *
	 * propMap の キーの最大長さを求め、位置あわせのためのスペースを追加します。
	 *
	 * @param	title タイトル
	 * @param	propMap プロパティー(Mapオブジェクト)
	 *
	 * @return	プロパティー文字列
	 * @og.rtnNotNull
	 */
	private String propMapToString( final String title,final Map<String,String> propMap ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		if( propMap != null ) {
			buf.append( title ).append( CR );

			// キーの長さをそろえるための処理
			int maxLen = 0;
			final Iterator<String> keyIte = propMap.keySet().iterator();	// 4.3.3.6 (2008/11/15) Generics警告対応
			while( keyIte.hasNext() ) {
				final int len = keyIte.next().length();	// 4.3.3.6 (2008/11/15) Generics警告対応
				if( len > maxLen ) { maxLen = len; }
			}

			final char[] ch = new char[maxLen];
			Arrays.fill( ch,' ' );		// スペースで埋めます。
			final String SPACE = new String( ch );
			final String VAL_SPACE = CR + SPACE + "        " ;

			final Iterator<Map.Entry<String,String>> propIte = propMap.entrySet().iterator();	// 4.3.3.6 (2008/11/15) Generics警告対応
			while( propIte.hasNext() ) {
				final Map.Entry<String,String> entry = propIte.next();	// 4.3.3.6 (2008/11/15) Generics警告対応
				final String key = entry.getKey();						// 4.3.3.6 (2008/11/15) Generics警告対応
				String val = entry.getValue();							// 4.3.3.6 (2008/11/15) Generics警告対応
				if( val != null ) { val = val.replaceAll( CR,VAL_SPACE ); }
				buf.append( "    -" ).append( key );
				buf.append( SPACE.substring( key.length() ) );	// 使用されるキー
				buf.append( " : " ).append( val ) ;	// その説明
				buf.append( CR );
			}
		}

		return buf.toString();
	}
}
