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.TimerTask;
019import java.util.Date;
020import java.util.Map;
021import java.util.concurrent.ConcurrentMap;                                      // 6.4.3.3 (2016/03/04)
022import java.util.concurrent.ConcurrentHashMap;                          // 6.4.3.3 (2016/03/04)
023import java.util.concurrent.atomic.AtomicInteger;                       // 5.5.2.6 (2012/05/25) findbugs対応
024
025import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
026import org.opengion.fukurou.system.LogWriter;                           // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
027import org.opengion.fukurou.system.DateSet;                                     // 6.4.2.0 (2016/01/29)
028
029/**
030 * HybsTimerTask.java は、String 型キーにString型値を Map するクラスです。
031 *
032 * HTMLのPOST/GET等の受け渡しや、String型の引数が多い場合に効果があります。
033 * 特に、getHybsTimerTask( String[] param ) による属性リスト作成は、
034 * HTMLタグの属性定義を行う上で、非常に便利に利用できます。
035 *
036 * この実装は同期化されません。
037 *
038 * @version  4.0
039 * @author   Kazuhiko Hasegawa
040 * @since    JDK5.0,
041 */
042public abstract class HybsTimerTask extends TimerTask implements Comparable<HybsTimerTask> {    // 4.3.3.6 (2008/11/15) Generics警告対応
043
044        private static AtomicInteger counter = new AtomicInteger();             // 5.5.2.6 (2012/05/25) findbugs対応
045
046        private final int       uniqKey ;                               // タイマータスクのユニークキー
047        private final long      createTime      = System.currentTimeMillis() ;  // オブジェクトが生成された時刻
048        // 3.2.2.0 (2003/05/31) HybsTimerTask に対して、設定値を渡せるように変更。
049        /** 6.4.3.3 (2016/03/04) ConcurrentHashMap に置き換えます。 */
050        private final ConcurrentMap<String,String>      paramMap = new ConcurrentHashMap<>();   // タイマータスクのパラメータ 3.6.0.7 (2004/11/06)
051
052        private String          name            ;                       // タイマータスクの名称
053        private String          comment         ;                       // タイマータスクの説明
054        private String          body            ;                       // タイマータスクのボディー要素
055        private String          startTime       = "000000";     // 24時間制の開始時刻
056        private String          stopTime        = "000000";     // 24時間制の終了時刻
057        private int                     startStop       ;                       // 24時間制の日付またがりをチェック。
058        private boolean         aliveFlag       = true ;        // 活きているかどうか
059        private int                     errorSleep      ;                       // TimerTask がエラーのときのスリープ時間(s) 3.7.0.4 (2005/03/14)
060
061        /**
062         * デフォルトコンストラクター
063         * オブジェクトは、newInstance でのみ、生成されます。
064         *
065         * @og.rev 5.5.2.6 (2012/05/25) findbugs対応。staticフィールドへの書き込みに、AtomicInteger を利用します。
066         * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
067         */
068        public HybsTimerTask() {
069                super();
070                // 4.3.4.4 (2009/01/01)
071                uniqKey = counter.getAndIncrement() ;   // 5.5.2.6 (2012/05/25) findbugs対応
072        }
073
074        /**
075         * このタイマータスクによって実行されるアクションです。
076         * ここでは、エラートラップを入れていますので、サブクラスで
077         * 再定義できないように、final 化しています。
078         * サブクラスでは、stratDaemon() をオーバーライドしてください。
079         *
080         * @see java.util.TimerTask#run()
081         * @see #startDaemon()
082         */
083        @Override
084        public final void run() {
085                try {
086                        if( isExecution() ) {
087                                startDaemon();
088                        }
089                }
090                catch( final Throwable th) {
091                        // 3.7.0.4 (2005/03/14)
092                        if( errorSleep > 0 ) {
093                                try { Thread.sleep( errorSleep * 1000L ); }
094                                catch( final InterruptedException ex ) { LogWriter.log( ex ); }
095                                System.out.println( th.getMessage() );
096                        }
097                        else {
098                                cancel();
099                                throw new OgRuntimeException( th );
100                        }
101                }
102        }
103
104        /**
105         * このタイマータスクによって実行されるアクションです。
106         * run メソッドより呼ばれます。
107         * サブクラスでは、startDaemon() をオーバーライドしてください。
108         *
109         * @see #run()
110         */
111        protected abstract void startDaemon() ;
112
113        /**
114         * このタイマータスクによって初期化されるアクションです。
115         * サブクラスでは、initDaemon() をオーバーライドしてください。
116         */
117        public abstract void initDaemon() ;
118//      public void initDaemon() {
119//              // ここでは処理を行いません。
120//      }
121
122        /**
123         * タイマータスクの名称(ユニークキー)を設定します。
124         *
125         * @param   nm タイマータスクの名称
126         */
127        public void setName( final String nm ) {
128                name = nm;
129        }
130
131        /**
132         * タイマータスクの名称(ユニークキー)を取得します。
133         *
134         * @return   タイマータスクの名称
135         */
136        public String getName() {
137                return name;
138        }
139
140        /**
141         * タイマータスクの説明を設定します。
142         *
143         * @param   cmt タイマータスクの説明
144         */
145        public void setComment( final String cmt ) {
146                comment = cmt;
147        }
148
149        /**
150         * タイマータスクの説明を取得します。
151         *
152         * @return   タイマータスクの説明
153         */
154        public String getComment() {
155                return comment;
156        }
157
158        /**
159         * このオブジェクトの内部ユニークキー値を返します。
160         * オブジェクト生成毎に、+1 されて、内部に持っています。
161         *
162         * @return   オブジェクトの内部ユニークキー
163         */
164        public int getUniqKey() {
165                return uniqKey;
166        }
167
168        /**
169         * このオブジェクトが生成された時刻をミリ秒で返します。
170         * オブジェクト生成時に、System.currentTimeMillis() の値を取得しています。
171         *
172         * @return   オブジェクトが生成された時刻(ミリ秒)
173         */
174        public long getCreateTime() {
175                return createTime;
176        }
177
178        /**
179         * 内部で使用するパラメータを設定します。
180         *
181         * 外部より、引数として渡されてきます。これを利用して、各サブシステムは、
182         * パラメーターを設定したり、初期化したり利用出来ます。
183         *
184         * ※ 6.4.3.1 (2016/02/12) で、内部のMapを ConcurrentHashMap に置き換えたが、引数のMapが
185         *    not null制限が入っているかどうか不明なので、取りえず元に戻しておきます。
186         * @og.rev 6.4.3.3 (2016/03/04) 受け取って、Mapに登録するときに、not null制限チェックを入れます。
187         *
188         * @param       map     パラメータマップ
189         */
190        public void setParameter( final Map<String,String> map ) {
191                if( map != null ) {
192                        // ※ 通常の HashMap から、ConcurrentHashMap への値の設定なので、null チェックが必要です。
193                        map.forEach( (k,v) -> { if( k != null && v != null ) { paramMap.put( k,v ); } } );
194                }
195        }
196
197        /**
198         * 内部で使用するパラメータを返します。
199         *
200         * Mapオブジェクトそのものですので、サブクラス等で書き換えた場合、内容も書き換わってしまいます。
201         *
202         * @og.rev 6.9.0.0 (2018/01/31) 新規追加
203         *
204         * @return      内部で使用するパラメータMap
205         */
206        protected ConcurrentMap<String,String> getParameter() {
207                return paramMap;
208        }
209
210        /**
211         * 内部で使用するパラメータのキーに対する値を取得します。
212         *
213         * 各サブシステムは、パラメーターを設定したり、初期化したり利用出来ます。
214         *
215         * @param       key 引数のキー
216         *
217         * @return      キーに対する値
218         */
219        public String getValue( final String key ) {
220                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
221                // 反転注意
222                return key == null ? null : paramMap.get( key ) ;                                       // 6.4.3.3 (2016/03/04)
223        }
224
225        /**
226         * 内部で使用するBody要素の値を設定します。
227         *
228         * 外部より、引数として渡されてきます。これを利用して、各サブシステムは、
229         * パラメーターを設定したり、初期化したり利用出来ます。
230         *
231         * @param       body Body要素の値
232         */
233        public void setBody( final String body ) {
234                this.body = body ;
235        }
236
237        /**
238         * 内部で使用するBody要素の値を取得します。
239         *
240         * 各サブシステムは、パラメーターを設定したり、初期化したり利用出来ます。
241         *
242         * @return      Body要素の値
243         */
244        public String getBody() {
245                return body ;
246        }
247
248        /**
249         * 24時間制(YYMMDD)の開始時刻を設定します。
250         *
251         * 指定時刻範囲内での実行のみ許可するように開始時刻を設定します。
252         * これは、タイマーで指定した間隔ごとにチェックを入れるので、チェック時間が
253         * 長い場合は、正確に開始時刻から始まるというものではありません。
254         * 初期値は、"000000" です。
255         *
256         * @param       st 開始時刻
257         */
258        public void setStartTime( final String st ) {
259                if( st == null || st.length() != 6 ) {
260                        final String errMsg = "startTime is inaccurate." +
261                                                        "startTime=[" + st + "]" ;
262                        throw new OgRuntimeException( errMsg );
263                }
264                startTime = st ;
265                initStartStop();
266        }
267
268        /**
269         * 24時間制(YYMMDD)の終了時刻を設定します。
270         *
271         * 指定時刻範囲内での実行のみ許可するように終了時刻を設定します。
272         * これは、タイマーで指定した間隔ごとにチェックを入れるので、チェック時間が
273         * 長い場合は、正確に終了時刻で終了するというものではありません。
274         * (終了時刻を越えてからの新規実行はありません。)
275         * 初期値は、"000000" です。
276         *
277         * @param       st 終了時刻
278         */
279        public void setStopTime( final String st ) {
280                if( st == null || st.length() != 6 ) {
281                        final String errMsg = "stopTime is inaccurate." +
282                                                        "stopTime=[" + st + "]" ;
283                        throw new OgRuntimeException( errMsg );
284                }
285                stopTime = st ;
286                initStartStop();
287        }
288
289        /**
290         * TimerTask がエラー発生時のスリープ時間(s) 設定します(初期値:0)。
291         *
292         * これは、予期せぬエラー(たとえば、データベースが落ちていたなど)が
293         * 発生したときでも、TimerTask を終了させずに、Sleep させて待機させる
294         * 事により、原因が除去された場合に、自動復帰するようにします。
295         * これに、0 を設定すると、エラー時に即 終了します。
296         * 設定は、秒で指定してください。
297         *
298         * @param       erTime スリープ時間(s)(初期値:0)
299         */
300        public void setErrorSleepSec( final int erTime ) {
301                errorSleep = erTime ;
302        }
303
304        /**
305         * 24時間制の開始/終了時刻の日付またがりを初期設定します。
306         *
307         * 開始時刻、終了時刻は、24時間制でしか指定できません。(日付指定できない)
308         * そのため、朝7:00から夜22:00などの 開始<終了 の時は単純な範囲チェックで
309         * 判断できますが、夜22:00から朝7:00に実行したい場合は、異なるチェックが
310         * 必要になります。
311         * また、開始時刻と終了時刻が未設定の場合や、同じ時刻の場合は、常に実行する必要が
312         * あります。これらのチェックを毎回行うのではなく、開始/終了時刻設定時にチェックして
313         * おきます。
314         *
315         */
316        private void initStartStop() {
317                if( startTime == null || stopTime == null ) {
318                        startStop = 0;
319                }
320                else {
321                        startStop = startTime.compareTo( stopTime );
322                }
323        }
324
325        /**
326         * 実行可能時間内かどうかを判定します。
327         *
328         * 設定された開始時刻と終了時刻に基づいて、現時刻で実行可能かどうか
329         * 判断します。
330         *
331         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
332         *
333         * @return      (true:実行許可  false:停止)
334         */
335        private boolean isExecution() {
336                boolean rtnFlag = false;
337                final String time = DateSet.getDate( "HHmmss" );                // 5.5.7.2 (2012/10/09) HybsDateUtil を利用
338
339                if( startStop == 0 ) {
340                        rtnFlag = true;
341                }
342                else if( startStop < 0 ) {
343                        if( startTime.compareTo( time ) < 0 &&
344                                time.compareTo( stopTime )  < 0 ) {
345                                        rtnFlag = true;
346                        }
347                }
348                else {                                                                                                          // 6.3.9.0 (2015/11/06) 条件は効果がない(findbugs)
349                        if( startTime.compareTo( time ) < 0 ||
350                                time.compareTo( stopTime )  < 0 ) {
351                                        rtnFlag = true;
352                        }
353                }
354                return rtnFlag;
355        }
356
357        /**
358         * このオブジェクトと指定されたオブジェクトの順序を比較します。
359         * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、
360         * 等しい場合はゼロ、大きい場合は正の整数を返します。
361         *
362         * @param    other 比較対象の Object
363         *
364         * @return   このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数
365         * @throws ClassCastException 指定されたオブジェクトの型が原因で、この Object と比較できない場合
366         */
367        @Override       // Comparable
368        public int compareTo( final HybsTimerTask other ) {             // 4.3.3.6 (2008/11/15) Generics警告対応
369                if( name == null && other.name != null ) { return -1; }
370                if( name != null && other.name == null ) { return  1; }
371
372                if( name != null && other.name != null ) {
373                        final int nmComp = name.compareTo( other.name );
374                        if( nmComp != 0 ) { return nmComp; }
375                }
376
377                return uniqKey - other.uniqKey ;
378        }
379
380        /**
381         * このオブジェクトの文字列表現を返します。
382         * 基本的にデバッグ目的に使用します。
383         *
384         * @return このクラスの文字列表現
385         * @og.rtnNotNull
386         */
387        @Override
388        public String toString() {
389                return  name + " [" + uniqKey + "]";
390        }
391
392        /**
393         * このオブジェクトと他のオブジェクトが等しいかどうかを示します。
394         *
395         * @og.rev 8.4.2.2 (2023/03/17) instanceof を使用した比較方法に統一します。
396         *
397         * @param  object 比較対象の参照オブジェクト
398         *
399         * @return      引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false
400         */
401        @Override
402        public boolean equals( final Object object ) {
403                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
404                // 条件反転注意
405//              return object != null
406//                              && getClass().equals( object.getClass() )
407//                              && super.equals( object )
408//                              && uniqKey == ((HybsTimerTask)object).uniqKey ;
409
410                // 8.4.2.2 (2023/03/17) instanceof を使用した比較方法に統一します。
411//              return super.equals( object )                                                           // 6.9.7.0 (2018/05/14) object != null が保障されます。
412//                              && getClass().equals( object.getClass() )
413//                              && uniqKey == ((HybsTimerTask)object).uniqKey ;
414
415                return object instanceof HybsTimerTask
416                                && uniqKey == ((HybsTimerTask)object).uniqKey ;
417        }
418
419        /**
420         * オブジェクトが生存しているかどうかを判定します。
421         *
422         * @return 生存しているかどうか。true:生存 / false:キャンセル済み
423         */
424        public boolean isAlive() {
425                return aliveFlag ;
426        }
427
428        /**
429         * オブジェクトのハッシュコード値を返します。
430         *
431         * @return このオブジェクトのハッシュコード値
432         */
433        @Override
434        public int hashCode() {
435                return uniqKey ;
436        }
437
438        /**
439         * このタイマータスクのcancel() メソッドをオーバーライドします。
440         *
441         * @return キャンセルが正常に終了できたかどうか
442         * @see java.util.TimerTask#cancel()
443         */
444        @Override
445        public boolean cancel() {
446                if( aliveFlag ) {
447                        System.out.println();
448                        System.out.println( toString() + " " + new Date()  + " Stoped" );
449                        aliveFlag = false;              // cancelTask を呼ぶ前に必ず『false』にしておく。
450                }
451                return super.cancel();
452        }
453}