001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.fukurou.util;
017
018import java.util.Map;
019import java.util.LinkedHashMap;
020import java.util.Collections;                                                                           // 6.4.3.1 (2016/02/12) refactoring
021import java.util.List;
022import java.util.ArrayList;
023import java.util.Iterator;
024import java.util.Arrays;
025import java.util.Date;
026import java.util.Locale;
027
028import java.text.DateFormat;
029import java.text.SimpleDateFormat;
030
031import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
032import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
033import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
034
035/**
036 * Argument は、バッチ処理の main メソッドの引数を解析するクラスです。
037 * Argument は、3つのタイプに分かれます。
038 *
039 * [コメント]  : # で始まる引数で、使用されません。(登録もされません。)
040 * [引数]      : #,-,= 以外で始まる通常の文字列。登録の順番が指定されます。
041 * [プロパティ]: - で始まり、キーと値を=で区切っているパラメータです。順序は無関係。
042 *
043 * これらのタイプを混在させても構いません。[引数]は、[コメント] や[プロパティ]を
044 * 無視した、入力の順番が重要視されます。取り出す場合も、番号で取り出します。
045 * 最初の[引数]が、0 で、以降 引数個数-1 までの番号で取り出します。
046 * [プロパティ]は、順番は無視し、キー部を指定することで取り出せます。
047 * ただし、キー部を重複して登録することは出来ません。なお、キー部の頭の文字列のみで
048 * 取り出すメソッドがあるため、key1,key2,key3 などと指定して、key で取り出せば、
049 * 複数プロパティを同一キーで取り出すことが可能です。
050 * [プロパティ]の指定では、キーと値を=で区切りますが、その前後にスペースを
051 * 入れないで下さい。引数の前後に = が付く文字列は指定できません。
052 *
053 * java Program AAA BBB #CCC -DD=XX -EE=YY -FF=ZZ GGG
054 *              ~~~ ~~~ ~~~~ ~~~~~~ ~~~~~~ ~~~~~~ ~~~
055 * [コメント]  : #CCC
056 * [引数]      : [0]=AAA , [1]=BBB , [2]=GGG
057 * [プロパティ]: key=DD,val=XX  key=EE,val=YY  key=FF,val=ZZ
058 *
059 * Argument の整合性チェックは、3つのパターンがあります。
060 *
061 * [引数]個数指定 :引数自身の最小個数、最大個数を登録しておくことで、プロパティのハイフン忘れ等を防止します。
062 * [プロパティ]必須チェック :必須キーが登録されたかどうかのチェックを行います。
063 * [プロパティ]整合性チェック : 指定されているキーのみ登録可能です。
064 *
065 * これらのチェックで、整合性チェックのみ、Argument の登録時に行います。
066 * それ以外は、取り出し時まで、判断できません。
067 * (取り出しは、登録がすべて終了したのちに行われると仮定しています)
068 *
069 * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
070 * [プロパティ]設定可能なプロパティの値を指定することで、誤記入を防止します。
071 *
072 * @version  4.0
073 * @author   Kazuhiko Hasegawa
074 * @since    JDK5.0,
075 */
076public final class Argument  {
077        /** Argument引数のタイプ [コメント]は、無視されます。 {@value} */
078        public static final int CMNT = 0;       // [コメント]
079
080        /** Argument引数のタイプ [引数]は、入力順にアクセスできます。 {@value} */
081        public static final int ARGS = 1;       // [引数]
082
083        /** Argument引数のタイプ [プロパティ]は、-KEY=VALUE 形式でキーでアクセスできます。 {@value} */
084        public static final int PROP = 2;       // [プロパティ]
085
086        private boolean argOkFlag       ;
087        private final List<String> argments = new ArrayList<>();
088        /** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。 */
089        private final Map<String,String> propMap = Collections.synchronizedMap( new LinkedHashMap<>() );
090
091        private int argRangeMin ;                       // 初期値:0
092        private int argRangeMax = 200 ;         // 本当は、Windows の引数の上限値を設定
093
094        /** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。 */
095        private final Map<String,String> mustProparty   = Collections.synchronizedMap( new LinkedHashMap<>() );
096        /** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。 */
097        private final Map<String,String> usableProparty = Collections.synchronizedMap( new LinkedHashMap<>() );
098
099        private final String programID  ;
100
101        /**
102         * この Argument を使用している プログラムID(Javaクラス名)を指定して
103         * インスタンスを作成します。
104         * toString() する際に、表示します。
105         *
106         * @param   pgid プログラムID
107         */
108        public Argument( final String pgid ) {
109                programID = pgid;
110        }
111
112        /**
113         * Argument の配列文字列から、引数やプロパティをセットします。
114         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
115         * これは、main メソッド等で単独起動する場合に、引数そのままを
116         * セットする場合に使用します。
117         *
118         * @param   args 引数配列(可変長引数)
119         * @see  #putArgument( String )
120         */
121        public void setArgument( final String... args ) {
122                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
123//              for( int i=0; i<args.length; i++ ) {
124//                      putArgument( args[i] );
125//              }
126                for( final String arg : args ) {
127                        putArgument( arg );
128                }
129        }
130
131        /**
132         * Argument の文字列から、引数かプロパティをセットします。
133         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
134         * Argument を設定する時に、タイプ判断として、getArgumentType( String ) を
135         * 使用します。よって、不正な Argument を設定した場合は、強制終了されます。
136         *
137         * @og.rev 6.4.8.3 (2016/07/15) key,val 分解後は、#putArgument(String,String)
138         * @og.rev 8.5.5.1 (2024/02/29) switch を if 文に置き換えます。
139         *
140         * @param   arg 引数
141         * @see  #putArgument( String,String )
142         */
143        public void putArgument( final String arg ) {
144                final int type = getArgumentType( arg );
145                if( ARGS == type ) { argments.add( arg ); }
146                else if( PROP == type ) {
147                        final int sep = arg.indexOf( '=' );     // sep は、 0 以上保証済み
148                        final String key = arg.substring(1,sep);
149                        final String val = arg.substring(sep+1);
150                        putArgument( key,val );         // 6.4.8.3 (2016/07/15)
151                }
152
153                // 8.5.5.1 (2024/02/29) switch を if 文に置き換え
154//              switch( type ) {
155//                      case CMNT : break;
156//                      case ARGS : argments.add( arg ); break;
157//                      case PROP :
158//                              final int sep = arg.indexOf( '=' );     // sep は、 0 以上保証済み
159//                              final String key = arg.substring(1,sep);
160//                              final String val = arg.substring(sep+1);
161//                              putArgument( key,val );         // 6.4.8.3 (2016/07/15)
162//                              break;
163//                      default: break;
164//              }
165        }
166
167        /**
168         * Argument の文字列から、プロパティをセットします。
169         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
170         * このメソッドは、引数 や コメントの判断を行いません。プロパティ のみ
171         * 設定されるものとして、処理します。
172         * プロパティの key=val が初めから分割されている場合の簡易メソッドです。
173         *
174         * @og.rev 6.4.8.3 (2016/07/15) val で、「\t」と、「\n」の文字列を、タブと改行に変換します。
175         * @og.rev 6.4.8.4 (2016/07/22) 元に戻します。タブと改行は、ここで変換できません。
176         *
177         * @param   key プロパティのキー
178         * @param   val プロパティの値
179         * @see  #putArgument( String )
180         */
181        public void putArgument( final String key,final String val ) {
182                checkProparty( key );           // 3.8.0.1 (2005/06/17)
183                propMap.put( key,val );
184        }
185
186        /**
187         * [引数]個数指定を設定します。
188         * 最大値、最小値を登録しておくことで、個数が、規定から外れていないか
189         * どうかを確認します。
190         * エラー判定は、実際に、[引数]を取り出すときに行われます。
191         * このチェックの登録は、putArgument( String ) の前でも後でもよく、
192         * getArgument の実行前であれば、いつでも構いません。
193         * 設定しない場合の初期値は、0~200 です。
194         *
195         * @param   min [引数]の最小個数(初期値:0)
196         * @param   max [引数]の最大個数(初期値:200)
197         */
198        public void setArgRange( final int min, final int max ) {
199                argRangeMin = min ;
200                argRangeMax = max ;
201        }
202
203        /**
204         * [プロパティ]必須チェック  Map 登録
205         * 必須キーが登録されたかどうかのチェックを行います。
206         * マスト判定は、実際に、[プロパティ]を取り出すときに行われます。
207         * すべてのプロパティーがセットし終わったかどうかの判断が出来ないためです。
208         * それ以外のチェックは、putArgument( String ) 時に行われるので、それまでに
209         * mustProparty のMapを登録しておく必要があります。
210         * ただし、引数文字列の記述チェック(使用してもよい値の配列チェック)は、
211         * #getProparty( String , String , String[] ) で行われるので、取得時になります。
212         *
213         * 設定しない場合の初期値は、制限なしです。
214         * 指定のMapのValue値には、エラー時のコメントを記述しておきます。
215         *
216         * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
217         *
218         * @param   mustProp 必須キーのMap
219         * @see #getProparty( String , String , String[] )
220         */
221        public void setMustProparty( final Map<String,String> mustProp ) {
222                mustProparty.putAll( mustProp ) ;
223        }
224
225        /**
226         * [プロパティ]整合性チェック Map 登録
227         * 指定されているキーのみ登録可能です。
228         * エラー判定は、実際に、[プロパティ]を取り出すときに行われます。
229         * このチェックの登録は、putArgument( String ) 時に行われるので、それまでに
230         * usableProparty のMapを登録しておく必要があります。
231         * ただし、引数文字列の記述チェック(使用してもよい値の配列チェック)は、
232         * #getProparty( String , String , String[] ) で行われるので、取得時になります。
233         *
234         * 設定しない場合の初期値は、制限なしです。
235         * 指定のMapのValue値には、このキーに対する解説を登録しておきます。
236         *
237         * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
238         *
239         * @param   useProp 使用可能キーのMap
240         */
241        public void setUsableProparty( final Map<String,String> useProp ) {
242                usableProparty.putAll( useProp ) ;
243        }
244
245        /**
246         * Argument の文字列から、そのタイプを判断します。
247         * 引数の形式が不正な場合(例えば、キーと値の分離の = の前後にスペースが入った場合)
248         * RuntimeException で強制終了します。
249         *
250         * [コメント]  : # で始まる引数で、使用されません。(登録もされません。)
251         * [引数]      : #,-,= 以外で始まる通常の文字列。登録の順番が指定されます。
252         * [プロパティ]: - で始まり、キーと値を=で区切っているパラメータです。順序は無関係。
253         *
254         * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
255         *
256         * @og.rev 5.3.4.0 (2011/04/01) 空文字列など無関係なパラメータは処理しないように変更
257         * @og.rev 6.4.8.3 (2016/07/15) KEY=VALUE の VALUE が、ゼロ文字列でも許可します。
258         *
259         * @param   arg 引数
260         *
261         * @return  引数タイプ(CMNT,ARGS,PROP)
262         * @see Argument#CMNT [コメント]
263         * @see Argument#ARGS [引数]
264         * @see Argument#PROP [プロパティ]
265         */
266        public int getArgumentType( final String arg ) {
267                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
268                final int rtn;
269
270                // 8.5.4.2 (2024/01/12) PMD 7.0.0 InefficientEmptyStringCheck 対応
271//              if( arg == null || arg.trim().isEmpty() || '#' == arg.charAt(0) ) {
272                if( StringUtil.isNull( arg ) || '#' == arg.charAt(0) ) {
273//                      return CMNT;
274                        rtn = CMNT;
275                }
276                else if( '=' == arg.charAt(0)  ) {      // 不正引数
277                        final String errMsg = "引数の = の前後には、スペースを入れないで下さい。"
278                                        +       " BAD Argument=[" + arg + "]"  ;
279                        throw new OgRuntimeException( errMsg );
280                }
281                else if( '-' == arg.charAt(0) ) {
282                        final int sep = arg.indexOf( '=' );
283        //              if( sep > 0 && sep < arg.length()-1 ) {
284                        if( sep > 0 && sep < arg.length() ) {                                   // 6.4.8.3 (2016/07/15)
285//                              return PROP;
286                                rtn = PROP;
287                        }
288                        else {
289                                final String errMsg = "-KEY を指定する場合は、= を続けて、VALUEを指定して下さい。"
290                                                +       "  -KEY=VALUE 形式 BAD Argument=[" + arg + "]"  ;
291                                throw new OgRuntimeException( errMsg );
292                        }
293                }
294                else {
295//                      return ARGS ;
296                        rtn = ARGS ;
297                }
298                return rtn;
299        }
300
301        /**
302         * 指定の番号に対する[引数]を返します。
303         * [引数]は、#,-,= 以外で始まる通常の文字列として登録されています。
304         * 登録された順番で取得します。
305         *
306         * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
307         *
308         * @param   adrs 番号
309         *
310         * @return  [引数]
311         */
312        public String getArgument( final int adrs ) {
313                // 以下のチェックは、getArgument が呼ばれて一度のみの実行でよい。
314                if( ! argOkFlag ) {
315                        if( argRangeMin < argments.size() || argments.size() < argRangeMax ) {
316                                final String errMsg = "[引数]個数が最小/最大個数を満たしていません。"
317                                                +       "  Min:" + argRangeMin + " <= " + argments.size() + " < Max:" + argRangeMax  ;
318                                throw new OgRuntimeException( errMsg );
319                        }
320                        argOkFlag = true;
321                }
322
323                if( argments.size() <= adrs ) {
324                        final String errMsg = "指定のアドレスは、[引数]設定個数外です。"
325                                        +       "  Size:" + argments.size() + " <= " + adrs ;
326                        throw new OgRuntimeException( errMsg );
327                }
328
329                return argments.get( adrs );
330        }
331
332        /**
333         * 指定の番号に対する[引数]を返します。
334         * def には、文字列の初期値を指定しておきます。adrs に対応する値が、null の場合、
335         * この def をそのまま返します。
336         *
337         * 処理は、getArgument( int ) の結果を、使用しています。
338         *
339         * @param   adrs 番号
340         * @param   def 値が null の場合の初期値
341         *
342         * @return  [引数]
343         * @see #getArgument( int )
344         */
345        public String getArgument( final int adrs, final String def ) {
346                final String value = getArgument( adrs );
347                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
348                return value == null ? def : value ;
349        }
350
351        /**
352         * 指定の番号に対する[引数]を返します。
353         * def には、数字の初期値を指定しておきます。adrs に対応する値が、null の場合、
354         * この def をそのまま返します。
355         *
356         * 処理は、getArgument( int ) の結果を、使用しています。
357         *
358         * @param   adrs 番号
359         * @param   def 値が null の場合の初期値
360         *
361         * @return  [引数]
362         * @see #getArgument( int )
363         */
364        public int getArgument( final int adrs, final int def ) {
365                final String value = getArgument( adrs );
366                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
367                return value == null ? def : Integer.parseInt( value ) ;
368        }
369
370        /**
371         * 指定の番号に対する[引数]を返します。
372         * def には、boolean の初期値を指定しておきます。adrs に対応する値が、null の場合、
373         * この def をそのまま返します。
374         *
375         * 処理は、getArgument( int ) の結果を、使用しています。
376         *
377         * @param   adrs 番号
378         * @param   def 値が null の場合の初期値
379         *
380         * @return  [引数]
381         * @see #getArgument( int )
382         */
383        public boolean getArgument( final int adrs, final boolean def ) {
384                final String value = getArgument( adrs );
385                return ( value == null ) ? def : Boolean.parseBoolean( value ) ;                // 6.1.0.0 (2014/12/26) refactoring
386        }
387
388        /**
389         * [プロパティ]整合性チェック 実行
390         * 設定された整合性チェックを実行します。
391         * 複数キーに対応する為に、先頭からの判定も行います。
392         * チェックするキーの大文字・小文字は、厳格に判定しています。
393         *
394         * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
395         *
396         * @og.rev 5.1.5.0 (2010/04/01) 判定の条件が、重複していたので修正。
397         * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
398         * @og.rev 6.4.3.3 (2016/03/04) Iterator 処理を、拡張for文に変更。
399         *
400         * @param   key チェックする入力キー
401         */
402        private void checkProparty( final String key ) {
403
404                // 第1の判定。 propMap にすでに存在していれば、エラーになる。
405                if( propMap.get( key ) != null ) {
406                        final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
407                                .append( "キー[" ).append( key ).append( "]は、すでに指定済みです。" ).append( CR )
408                                .append( "  登録済み:-" )
409                                .append( key ).append( '=' ).append( propMap.get( key ) )               // 6.0.2.5 (2014/10/31) char を append する。
410                                .append( CR );
411                        throw new OgRuntimeException( errMsg.toString() );
412                }
413
414                // 6.4.3.1 (2016/02/12) インスタンスで初期化しているため、null はない。
415                if( !mustProparty.isEmpty() ) {                                                         // 6.4.3.1 (2016/02/12)
416                        // 第2の判定。 mustProparty に存在すれば、即抜けする。
417                        if( mustProparty.containsKey( key ) ) { return; }
418
419                        // 第3の判定。複数キー(先頭一致キー)の場合もありうるため、先頭からの比較を行う。
420                        // 6.4.3.3 (2016/03/04) Iterator 処理を、拡張for分に変更。判定は、keyのみでよい
421                        for( final String propKey : mustProparty.keySet() ) {
422                                if( key.startsWith( propKey ) ) { return ; }            // マッチすれば、即抜ける。
423                        }
424                }
425
426                // 5.1.5.0 (2010/04/01) 判定の条件が、重複していたので修正。
427                // 6.4.3.1 (2016/02/12) インスタンスで初期化しているため、null はない。
428                if( !usableProparty.isEmpty() ) {                                                       // 6.4.3.1 (2016/02/12)
429                        // 第4の判定。 usableProparty に存在すれば、即抜けする。
430                        if( usableProparty.containsKey( key ) ) { return ; }
431
432                        // 第5の判定。複数キー(先頭一致キー)の場合もありうるため、先頭からの比較を行う。
433                        // 6.4.3.3 (2016/03/04) Iterator 処理を、拡張for分に変更。判定は、keyのみでよい
434                        for( final String propKey : usableProparty.keySet() ) {
435                                if( key.startsWith( propKey ) ) { return ; }            // マッチすれば、即抜ける。
436                        }
437
438                        // そこまで探して見つからない場合は、定義外引数エラー
439                        final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
440                                .append( "-KEY が、指定の整合性リストに含まれていません。" )
441                                .append( CR )
442                                .append( "  -KEY=VALUE 形式 BAD Key=[" ).append( key ).append( ']' )              // 6.0.2.5 (2014/10/31) char を append する。
443                                .append( CR )
444                                .append( toString() );
445                        throw new OgRuntimeException( errMsg.toString() );
446                }
447        }
448
449        /**
450         * 内部で使用する[プロパティ]を、キーを指定して取得します。
451         * 値が設定されていない場合は、 null を返します。
452         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
453         *
454         * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
455         *
456         * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。
457         *
458         * @param   key 引数のキー
459         *
460         * @return  引数に対する値
461         */
462        public String getProparty( final String key ) {
463
464                final String value = propMap.get( key );
465
466                // 値が null で must 設定があり、かつマストキーが指定している場合。
467                if( value == null &&
468                        // 6.4.3.1 (2016/02/12) インスタンスで初期化しているため、null はない。
469                        !mustProparty.isEmpty() &&                                      // 6.4.3.1 (2016/02/12)
470                        mustProparty.containsKey( key ) ) {
471                                final String errMsg = "指定の[プロパティ]は、必須キーですが、値が null です。"
472                                                +       "  Key:" + key + "  説明:" + mustProparty.get( key )
473                                                + CR + toString() ;
474                                throw new OgRuntimeException( errMsg );
475                }
476
477                return value ;
478        }
479
480        /**
481         * 内部で使用する[プロパティ]を、キーを指定して取得します。
482         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
483         * def には、文字列の初期値を指定しておきます。key に対応する値が、null の場合、
484         * この def をそのまま返します。
485         *
486         * 処理は、getProparty( String ) の結果を、使用しています。
487         *
488         * @param   key キー
489         * @param   def 値が null の場合の初期値
490         *
491         * @return  [プロパティ]
492         * @see #getProparty( String )
493         */
494        public String getProparty( final String key, final String def ) {
495                final String value = getProparty( key );
496                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
497                return value == null ? def : value ;
498        }
499
500        /**
501         * 内部で使用する[プロパティ]を、キーを指定して取得します。
502         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
503         * def には、文字列の初期値を指定しておきます。key に対応する値が、null の場合、
504         * この def をそのまま返します。
505         * list 配列には、登録できる文字列配列を指定します。この文字列に含まれない
506         * 値が設定されていた場合は、エラーになります。
507         *
508         * 処理は、getProparty( String ) の結果を、使用しています。
509         *
510         * ※ 引数の設定方法が間違っている場合、RuntimeException が throw されます。
511         *
512         * @param   key キー
513         * @param   def  値が null の場合の初期値
514         * @param   list 値として存在できる文字列配列(可変長引数)
515         *
516         * @return  [プロパティ]
517         * @see #getProparty( String )
518         */
519        public String getProparty( final String key, final String def, final String... list ) {
520                final String value = getProparty( key,def );
521                if( value != null ) {
522                        boolean isOK = false;
523                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
524//                      for( int i=0; i<list.length; i++ ) {
525//                              if( value.equalsIgnoreCase( list[i] ) ) {
526                        for( final String str : list ) {
527                                if( value.equalsIgnoreCase( str ) ) {
528                                        isOK = true; break;
529                                }
530                        }
531                        if( !isOK ) {
532                                final String errMsg = key + " は、" + Arrays.toString( list )
533                                                                        + " から指定してください。" + CR
534                                                                        + "-" + key + "=[" + value + "]" ;
535                                throw new OgRuntimeException( errMsg );
536                        }
537                }
538
539                return value ;
540        }
541
542        /**
543         * 内部で使用する[プロパティ]を、キーを指定して取得します。
544         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
545         * def には、数字の初期値を指定しておきます。key に対応する値が、null の場合、
546         * この def をそのまま返します。
547         *
548         * 処理は、getProparty( String ) の結果を、使用しています。
549         *
550         * @param   key キー
551         * @param   def 値が null の場合の初期値
552         *
553         * @return  [プロパティ]
554         * @see #getProparty( String )
555         */
556        public int getProparty( final String key, final int def ) {
557                final String value = getProparty( key );
558                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
559                return value == null ? def : Integer.parseInt( value ) ;
560        }
561
562        /**
563         * 内部で使用する[プロパティ]を、キーを指定して取得します。
564         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
565         * def には、boolean の初期値を指定しておきます。key に対応する値が、null の場合、
566         * この def をそのまま返します。
567         *
568         * 処理は、getProparty( String ) の結果を、使用しています。
569         *
570         * @param   key キー
571         * @param   def 値が null の場合の初期値
572         *
573         * @return  [プロパティ]
574         * @see #getProparty( String )
575         */
576        public boolean getProparty( final String key, final boolean def ) {
577                final String value = getProparty( key );
578                return ( value == null ) ? def : Boolean.parseBoolean( value ) ;                // 6.1.0.0 (2014/12/26) refactoring
579        }
580
581        /**
582         * 内部で使用する[プロパティ]を、キーを指定して取得します。
583         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
584         * key プロパティは、通常の引数として指定できる簡易的な値を設定します。
585         * keyFile プロパティ は、ファイル名を指定し、そのファイルの中身を
586         * 取得して返します。通常は1行であるが、時には複数行のデータを指定
587         * したい場合に、2つのパラメータを使いますが、設定値は、同じ引数として
588         * 使用したいケースに便利です。
589         * key プロパティと、keyFile プロパティ は、同時指定できません。
590         * これは、指定方法の間違い等を避ける為です。
591         * どちらも、null である可能性はあります。
592         *
593         * ※ 同時指定時、または、must 必須時に null の場合、RuntimeException が throw されます。
594         *
595         * @param   key キー
596         * @param   keyFile  設定ファイル名
597         * @param   must 必須条件[true/false]
598         *
599         * @return  [プロパティ]
600         * @see #getProparty( String )
601         */
602        public String getFileProparty( final String key, final String keyFile, final boolean must ) {
603                return getFileProparty( key,keyFile,null,must );
604        }
605
606        /**
607         * 内部で使用する[プロパティ]を、キーを指定して取得します。
608         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
609         * key プロパティは、通常の引数として指定できる簡易的な値を設定します。
610         * keyFile プロパティ は、ファイル名を指定し、そのファイルの中身を
611         * 取得して返します。通常は1行であるが、時には複数行のデータを指定
612         * したい場合に、2つのパラメータを使いますが、設定値は、同じ引数として
613         * 使用したいケースに便利です。
614         * key プロパティと、keyFile プロパティ は、同時指定できません。
615         * これは、指定方法の間違い等を避ける為です。
616         * どちらも、null である可能性はあります。
617         *
618         * ※ 同時指定時、または、must 必須時に null の場合、RuntimeException が throw されます。
619         *
620         * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
621         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
622         *
623         * @param   key キー
624         * @param   keyFile 設定ファイル名
625         * @param   encode keyFile読取エンコード(null はデフォルトエンコード)
626         * @param   must 必須条件[true/false]
627         *
628         * @return  [プロパティ]
629         * @see #getProparty( String )
630         */
631        public String getFileProparty( final String key, final String keyFile,
632                                                                         final String encode,final boolean must ) {
633                String val     = getProparty( key );
634                final String valFile = getProparty( keyFile );
635
636                if( val != null && valFile != null ) {
637                        final String errMsg = key + "か、" + keyFile + " は、両方同時に指定できません。[" + val + "],[" + valFile + "]";
638                        throw new OgRuntimeException( errMsg );
639                }
640
641                if( valFile != null ) {
642                        // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
643                        val = FileUtil.getValue( valFile , encode );            // 6.4.5.2 (2016/05/06)
644                }
645
646                if( must && val == null ) {
647                        final String errMsg = key + "か、" + keyFile + " は、片方必須です。";
648                        throw new OgRuntimeException( errMsg );
649                }
650
651                return val;
652        }
653
654        /**
655         * 内部で使用する[プロパティ]を、キーを先頭に含む値を取得します。
656         * [プロパティ]のキー部の大文字・小文字は、厳格に判定しています。
657         * 値が設定されていない場合は、String[0] を返します。
658         * HybsEntry のキーに設定される値は、引数の先頭キーを除いた文字列です。
659         * 例えば、"const_" のような値を与えて、const_AA, const_BB, const_CC の
660         * 3つのキーが選定された場合、キーは、AA, BB, CC のみ返します。
661         *
662         * @param   startsKey 引数の先頭のキー
663         *
664         * @return  引数に対する[プロパティ]のHybsEntry
665         * @og.rtnNotNull
666         */
667        public HybsEntry[] getEntrys( final String startsKey ) {
668//              final ArrayList<HybsEntry> list = new ArrayList<>();
669                final List<HybsEntry> list = new ArrayList<>();                 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LooseCoupling
670                final int len = startsKey.length();
671
672                final Iterator<Map.Entry<String,String>> ite = propMap.entrySet().iterator();   // 4.3.3.6 (2008/11/15) Generics警告対応
673                while( ite.hasNext() ) {
674                        final Map.Entry<String,String> entry = ite.next();      // 4.3.3.6 (2008/11/15) Generics警告対応
675                        final String key = entry.getKey();                                      // 4.3.3.6 (2008/11/15) Generics警告対応
676                        if( key.startsWith( startsKey ) ) {
677                                list.add( new HybsEntry( key.substring( len ), entry.getValue() ) );    // 4.3.3.6 (2008/11/15) Generics警告対応
678                        }
679                }
680
681//              return list.toArray( new HybsEntry[list.size()] ) ;
682                return list.toArray( new HybsEntry[0] ) ;       // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応
683        }
684
685        /**
686         * 入力文字列に、{&#064;XXXX}関係の文字列変換を行います。
687         * 引数に含まれる {&#064;XXXX}=YYYY という入力に対して、inMsg に
688         * 含まれる{&#064;XXXX} 文字列を、YYYY という文字列に変換します。
689         * それ以外に、予約文字変換として、
690         *   {&#064;ARG.XXX}  引数に使用された値を再利用(割り当て)します。
691         *   {&#064;DATE.XXX} SimpleDateFormat 形式の文字を変換します。(日付、時刻等)
692         *   {&#064;ENV.XXX}  システムプロパティーの文字を変換します。(java -Dkey=value オプション)
693         *
694         * @param  inMsg 入力文字列
695         *
696         * @return  変換後文字列
697         */
698        public String changeParam( final String inMsg ) {
699                if( inMsg == null ) { return inMsg; }
700
701                String message = inMsg;
702
703                // {@ARG.XXXX} 変数の置換処理
704                int adrs = message.indexOf( "{@ARG." ) ;
705                while( adrs >= 0 ) {
706                        final int end = message.indexOf( '}',adrs ) ;
707                        final String key = message.substring( adrs+6,end );
708                        final String oldData = "{@ARG." + key + "}" ;
709                        // 注意:{@XXX}と異なり、{@ARG.XXX} では、XXX で propMap を検索する。
710                        final String newData = StringUtil.nval( getProparty( key ),"" );
711                        message = StringUtil.replace( message,oldData,newData );
712                        adrs = message.indexOf( "{@ARG.",adrs ) ;
713                }
714                // {@DATE.XXXX} 変数の置換処理
715                adrs = message.indexOf( "{@DATE." ) ;
716                if( adrs >= 0 ) {
717                        final Date dt = new Date();
718                        while( adrs >= 0 ) {
719                                final int end = message.indexOf( '}',adrs ) ;
720                                final String key = message.substring( adrs+7,end );
721                                final String oldData = "{@DATE." + key + "}" ;
722                                final DateFormat formatter = new SimpleDateFormat( key, Locale.JAPAN );
723                                final String newData = StringUtil.nval( formatter.format(dt),"" );
724                                message = StringUtil.replace( message,oldData,newData );
725                                adrs = message.indexOf( "{@DATE.",adrs ) ;
726                        }
727                }
728                // {@ENV.XXXX} 変数の置換処理
729                adrs = message.indexOf( "{@ENV." ) ;
730                while( adrs >= 0 ) {
731                        final int end = message.indexOf( '}',adrs ) ;
732                        final String key = message.substring( adrs+6,end );
733                        final String oldData = "{@ENV." + key + "}" ;
734                        final String newData = System.getProperty( key,"" );
735                        message = StringUtil.replace( message,oldData,newData );
736                        adrs = message.indexOf( "{@ENV.",adrs ) ;
737                }
738
739                // 残りのメッセージ本文中の置換文字列を処理します。
740                adrs = message.indexOf( "{@" ) ;
741                while( adrs >= 0 ) {
742                        final int end = message.indexOf( '}',adrs ) ;
743                        final String key = message.substring( adrs,end+1 );     // +1 注意
744                        final String oldData = key ;
745                        // 注意:{@ARG.XXX} と異なり、{@XXX} そのもので propMap を検索する。
746                        final String newData = StringUtil.nval( getProparty( key ),"" );
747                        message = StringUtil.replace( message,oldData,newData );
748                        adrs = message.indexOf( "{@",adrs ) ;
749                }
750
751                return message;
752        }
753
754        /**
755         * このオブジェクトの内部表現を、文字列にして返します。
756         * クラス名 + 起動時の引数リストを表示します。
757         *
758         * @return  引数に対する値
759         * @og.rtnNotNull
760         */
761        @Override               // Object
762        public String toString() {
763                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
764
765                buf.append( "java " ).append( programID ).append( CR );
766
767                if( ! argments.isEmpty() ) {
768                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
769//                      for( int i=0; i<argments.size(); i++ ) {
770//                              buf.append( ' ' ).append( argments.get(i) );            // 6.0.2.5 (2014/10/31) char を append する。
771//                      }
772                        for( final String arg : argments ) {
773                                buf.append( ' ' ).append( arg );                                        // 6.0.2.5 (2014/10/31) char を append する。
774                        }
775                        buf.append( CR );
776                }
777
778                final Iterator<Map.Entry<String,String>> propIte = propMap.entrySet().iterator();       // 4.3.3.6 (2008/11/15) Generics警告対応
779                while( propIte.hasNext() ) {
780                        final Map.Entry<String,String> entry = propIte.next();  // 4.3.3.6 (2008/11/15) Generics警告対応
781                        final String key = entry.getKey();                                              // 4.3.3.6 (2008/11/15) Generics警告対応
782                        Object val = entry.getValue();
783                        if( key.startsWith( "passwd" ) ) {
784                                val = "*****" ;
785                        }
786
787                        buf.append( "    -" ).append( key ).append( '=' ).append( val );                // 6.0.2.5 (2014/10/31) char を append する。
788                        buf.append( CR );
789                }
790
791                return buf.toString();
792        }
793
794        /**
795         * このクラスの使用方法を返します。
796         *
797         * @return      このクラスの使用方法
798         * @og.rtnNotNull
799         */
800        public String usage() {
801                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
802                        .append( toString() )
803                        .append( CR )
804                        .append( propMapToString( "[ Must Proparty List ]"  , mustProparty   ) )
805                        .append( propMapToString( "[ Usable Proparty List ]", usableProparty ) );
806
807                return buf.toString();
808        }
809
810        /**
811         * プロパティーを文字列に変換します。
812         *
813         * propMap の キーの最大長さを求め、位置あわせのためのスペースを追加します。
814         *
815         * @param       title タイトル
816         * @param       propMap プロパティー(Mapオブジェクト)
817         *
818         * @return      プロパティー文字列
819         * @og.rtnNotNull
820         */
821        private String propMapToString( final String title,final Map<String,String> propMap ) {
822                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
823
824                if( propMap != null ) {
825                        buf.append( title ).append( CR );
826
827                        // キーの長さをそろえるための処理
828                        int maxLen = 0;
829                        final Iterator<String> keyIte = propMap.keySet().iterator();    // 4.3.3.6 (2008/11/15) Generics警告対応
830                        while( keyIte.hasNext() ) {
831                                final int len = keyIte.next().length(); // 4.3.3.6 (2008/11/15) Generics警告対応
832                                if( len > maxLen ) { maxLen = len; }
833                        }
834
835                        final char[] ch = new char[maxLen];
836                        Arrays.fill( ch,' ' );          // スペースで埋めます。
837                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 StringInstantiation 対応
838//                      final String SPACE = new String( ch );
839                        final String SPACE = String.valueOf( ch );
840
841                        final String VAL_SPACE = CR + SPACE + "        " ;
842
843                        final Iterator<Map.Entry<String,String>> propIte = propMap.entrySet().iterator();       // 4.3.3.6 (2008/11/15) Generics警告対応
844                        while( propIte.hasNext() ) {
845                                final Map.Entry<String,String> entry = propIte.next();  // 4.3.3.6 (2008/11/15) Generics警告対応
846                                final String key = entry.getKey();                                              // 4.3.3.6 (2008/11/15) Generics警告対応
847                                String val = entry.getValue();                                                  // 4.3.3.6 (2008/11/15) Generics警告対応
848                                if( val != null ) { val = val.replaceAll( CR,VAL_SPACE ); }
849                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ConsecutiveAppendsShouldReuse 対応
850                                buf.append( "    -" ).append( key )
851                                        .append( SPACE.substring( key.length() ) )      // 使用されるキー
852                                        .append( " : " ).append( val )                          // その説明
853                                        .append( CR );
854                        }
855                }
856
857                return buf.toString();
858        }
859}