    001/*
    002 * Copyright (c) 2017 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.fileexec;
    017
    018// import java.io.File;
    019import java.io.IOException;
    020
    021import java.nio.file.WatchEvent;
    022import java.nio.file.Path;
    023import java.nio.file.PathMatcher;
    024import java.nio.file.FileSystem;
    025import java.nio.file.FileSystems;                                                       // 7.4.4.0 (2021/06/30)
    026import java.nio.file.WatchKey;
    027import java.nio.file.StandardWatchEventKinds;
    028import java.nio.file.WatchService;
    029
    030import java.util.function.BiConsumer;
    031// import java.util.concurrent.atomic.AtomicBoolean;            // 7.2.9.4 (2020/11/20) volatile boolean の代替え , // 7.4.4.0 (2021/06/30) 戻す
    032
    033/**
    034 * FileWatch は、ファイル監視を行うクラスです。
    035 *
    036 *<pre>
    037 * ファイルが、追加(作成)、変更、削除された場合に、イベントが発生します。
    038 * このクラスは、Runnable インターフェースを実装しているため、Thread で実行することで、
    039 * 個々のフォルダの監視を行います。
    040 *
    041 *</pre>
    042 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
    043 *
    044 * @version  7.0
    045 * @author   Kazuhiko Hasegawa
    046 * @since    JDK1.8,
    047 */
    048public class FileWatch implements Runnable {
    049        private static final XLogger LOGGER= XLogger.getLogger( FileWatch.class.getSimpleName() );              // ログ出力
    050
    051        /** Path に、WatchService を register するときの作成イベントの簡易指定できるように。 */
    052        public static final WatchEvent.Kind<Path> CREATE = StandardWatchEventKinds.ENTRY_CREATE ;
    053
    054        /** Path に、WatchService を register するときの変更イベントの簡易指定できるように。 */
    055        public static final WatchEvent.Kind<Path> MODIFY = StandardWatchEventKinds.ENTRY_MODIFY ;
    056
    057        /** Path に、WatchService を register するときの削除イベントの簡易指定できるように。  */
    058        public static final WatchEvent.Kind<Path> DELETE = StandardWatchEventKinds.ENTRY_DELETE ;
    059
    060        /** Path に、WatchService を register するときの特定不能時イベントの簡易指定できるように。 */
    061        public static final WatchEvent.Kind<?>    OVERFLOW = StandardWatchEventKinds.OVERFLOW ;
    062
    063        // Path に、WatchService を register するときのイベント
    064        private static final WatchEvent.Kind<?>[] WE_KIND = new WatchEvent.Kind<?>[] {
    065                        CREATE , MODIFY , DELETE , OVERFLOW
    066        };
    067
    068        // Path に、WatchService を register するときの登録方法の修飾子(修飾子 なしの場合)
    069        private static final WatchEvent.Modifier[] WE_MOD_ONE  = new WatchEvent.Modifier[0];    // Modifier なし
    070
    071        // Path に、WatchService を register するときの登録方法の修飾子(以下の階層も監視対象にします)
    072        private static final WatchEvent.Modifier[] WE_MOD_TREE = new WatchEvent.Modifier[] {    // ツリー階層
    073                                        com.sun.nio.file.ExtendedWatchEventModifier.FILE_TREE
    074                        };
    075
    076        /** DirWatch でスキャンした場合のｲﾍﾞﾝﾄ名 {@value} */
    077        public static final String DIR_WATCH_EVENT = "DirWatch";
    078
    079        /** 7.4.4.0 (2021/06/30) stop() してから、実際に止まるまで 待機する時間 (ms ) */
    080        public static final int STOP_WATI_TIME = 500 ;
    081
    082        /** 7.4.4.0 (2021/06/30) stop() してから、実際に止まるまで 待機する回数 */
    083        public static final int STOP_WATI_CNT = 5 ;
    084
    085        // 監視対象のフォルダ
    086        private final Path dirPath ;
    087
    088        // 監視方法
    089        private final boolean   useTree ;
    090        private final WatchEvent.Modifier[] extModifiers ;
    091
    092        // callbackするための、関数型インターフェース(メソッド参照)
    093        private BiConsumer<String,Path> action = (event,path) -> System.out.println( "Event=" + event + " , Path=" + path ) ;
    094
    095        // Path に、WatchService を register するときのイベント
    096        private WatchEvent.Kind<?>[] weKind = WE_KIND ;                                         // 初期値は、すべて
    097
    098        // パスの照合操作を行うPathMatcher の初期値
    099        private final PathMatcherSet pathMchSet = new PathMatcherSet();         // PathMatcher インターフェースを継承
    100
    101        // DirWatchのパスの照合操作を行うPathMatcher の初期値
    102        private final PathMatcherSet dirWatchMch = new PathMatcherSet();        // PathMatcher インターフェースを継承
    103
    104        // 何らかの原因でイベントもれした場合、フォルダスキャンを行います。
    105        private boolean         useDirWatch     = true;                                                         // 初期値は、イベント漏れ監視を行います。
    106        private DirWatch        dWatch ;                                                                                // DirWatch のstop時に呼び出すための変数
    107        private Thread          thread ;                                                                                // 停止するときに呼び出すため
    108
    109        private volatile boolean running ;                                                                      // 状態とThreadの停止に使用する。 // 7.4.4.0 (2021/06/30) 復活
    110//      private final AtomicBoolean running = new AtomicBoolean();                      // 7.2.9.4 (2020/11/20) volatile boolean の代替え ( 状態とThreadの停止に使用する。)
    111
    112        /**
    113         * 処理対象のフォルダのパスオブジェクトを指定して、ファイル監視インスタンスを作成します。
    114         *
    115         * ここでは、指定のフォルダの内のファイルのみ監視します。
    116         * これは、new FileWatch( dir , false ) とまったく同じです。
    117         *
    118         * @param dir   処理対象のフォルダオブジェクト
    119         */
    120        public FileWatch( final Path dir ) {
    121                this( dir , false );
    122        }
    123
    124        /**
    125         * 処理対象のフォルダのパスオブジェクトと、監視対象方法を指定して、ファイル監視インスタンスを作成します。
    126         *
    127         * useTree を true に設定すると、指定のフォルダの内のフォルダ階層を、すべて監視対象とします。
    128         *
    129         * @param dir   処理対象のフォルダのパスオブジェクト
    130         * @param useTree       フォルダツリーの階層をさかのぼって監視するかどうか(true:フォルダ階層を下る)
    131         */
    132        public FileWatch( final Path dir , final boolean useTree ) {
    133                dirPath          = dir ;
    134                this.useTree = useTree;
    135                extModifiers = useTree ? WE_MOD_TREE : WE_MOD_ONE ;
    136        }
    137
    138        /**
    139         * 指定のイベントの種類のみ、監視対象に設定します。
    140         *
    141         * ここで指定したイベントのみ、監視対象になり、callback されます。
    142         * 第一引数は、イベントの種類(ENTRY_CREATE,ENTRY_MODIFY,ENTRY_DELETE,OVERFLOW)
    143         *
    144         * @param       kind 監視対象に設定するイベントの種類
    145         * @see         java.nio.file.StandardWatchEventKinds
    146         */
    147        public void setEventKinds( final WatchEvent.Kind<?>... kind ) {
    148                if( kind != null && kind.length > 0 ) {
    149                        weKind = kind;
    150                }
    151        }
    152
    153        /**
    154         * 指定のパスの照合操作で、パターンに一致したパスのみ、callback されます。
    155         *
    156         * ここで指定したパターンの一致を判定し、一致した場合は、callback されます。
    157         * 指定しない場合は、すべて許可されたことになります。
    158         * なお、#setPathEndsWith(String...) と、この設定は同時には行うことは出来ません。
    159         * (最後に登録した条件が、適用されます。)
    160         *
    161         * @param       pathMch パスの照合操作のパターン
    162         * @see         java.nio.file.PathMatcher
    163         * @see         #setPathEndsWith(String...)
    164         */
    165        public void setPathMatcher( final PathMatcher pathMch ) {
    166                pathMchSet.addPathMatcher( pathMch );
    167        }
    168
    169        /**
    170         * 指定のパスが、指定の文字列と、終端一致(endsWith) したパスのみ、callback されます。
    171         *
    172         * これは、#setPathMatcher(PathMatcher) の簡易指定版です。
    173         * 指定の終端文字列(一般には拡張子)のうち、ひとつでも一致すれば、true となりcallback されます。
    174         * 指定しない場合(null)は、すべて許可されたことになります。
    175         * 終端文字列の判定には、大文字小文字の区別を行いません。
    176         * なお、#setPathMatcher(PathMatcher) と、この設定は同時には行うことは出来ません。
    177         * (最後に登録した条件が、適用されます。)
    178         *
    179         * @param       endKey パスの終端一致のパターン
    180         * @see         #setPathMatcher(PathMatcher)
    181         */
    182        public void setPathEndsWith( final String... endKey ) {
    183                pathMchSet.addEndsWith( endKey );
    184        }
    185
    186        /**
    187         * イベントの種類と、ファイルパスを、引数に取る BiConsumer ダオブジェクトを設定します。
    188         *
    189         * これは、関数型インタフェースなので、ラムダ式またはメソッド参照の代入先として使用できます。
    190         * イベントが発生したときの イベントの種類と、そのファイルパスを引数に、accept(String,Path) メソッドが呼ばれます。
    191         * 第一引数は、イベントの種類(ENTRY_CREATE,ENTRY_MODIFY,ENTRY_DELETE,OVERFLOW)
    192         * 第二引数は、ファイルパス(監視フォルダで、resolveされた、正式なフルパス)
    193         *
    194         * @param       act 2つの入力(イベントの種類 とファイルパス) を受け取る関数型インタフェース
    195         * @see         BiConsumer#accept(Object,Object)
    196         */
    197        public void callback( final BiConsumer<String,Path> act ) {
    198                if( act != null ) {
    199                        action = act ;
    200                }
    201        }
    202
    203        /**
    204         * 何らかの原因でイベントを掴み損ねた場合に、フォルダスキャンするかどうかを指定します。
    205         *
    206         * スキャン開始の遅延時間と、スキャン間隔、ファイルのタイムスタンプとの比較時間等は、
    207         * DirWatch の初期値をそのまま使用するため、ここでは指定できません。
    208         * 個別に指定したい場合は、このﾌﾗｸﾞをfalse にセットして、個別に、DirWatch を作成してください。
    209         * このメソッドでは、#setPathEndsWith( String... )や、#setPathMatcher( PathMatcher ) で
    210         * 指定した条件が、そのまま適用されます。
    211         *
    212         * @param       flag フォルダスキャンするかどうか(true:する/false:しない)
    213         * @see         DirWatch
    214         */
    215        public void setUseDirWatch( final boolean flag ) {
    216                useDirWatch = flag;
    217        }
    218
    219        /**
    220         * 何らかの原因でイベントを掴み損ねた場合の、フォルダスキャンの対象ファイルの拡張子を指定します。
    221         *
    222         * このメソッドを使用する場合は、useDirWatch は、true にセットされます。
    223         * スキャン開始の遅延時間と、スキャン間隔、ファイルのタイムスタンプとの比較時間等は、
    224         * DirWatch の初期値をそのまま使用するため、ここでは指定できません。
    225         * このメソッドでは、DirWatch 対象の終端パターンを独自に指定できますが、FileWatch で
    226         * で指定した条件も、クリアされるので、含める必要があります。
    227         *
    228         * @param       endKey パスの終端一致のパターン
    229         * @see         DirWatch
    230         */
    231        public void setDirWatchEndsWith( final String... endKey ) {
    232                if( endKey != null && endKey.length > 0 ) {
    233                        useDirWatch = true;                                     // 対象があれば、実行するが、true になる。
    234
    235                        dirWatchMch.addEndsWith( endKey );
    236                }
    237        }
    238
    239        /**
    240         * このファイル監視で、最後に処理した結果が、エラーの場合に、true を返します。
    241         *
    242         * 通常は、対象フォルダが見つからない場合や、フォルダスキャン（DirWatch）で
    243         * エラーが発生した場合に、true にセットされます。
    244         * また、stop() メソッドが呼ばれた場合も、true にセットされます。
    245         *
    246         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え。
    247         *
    248         * @return      エラー状態(true:エラー,false:正常)
    249         */
    250        public boolean isErrorStatus() {
    251                // DirWatch を使用している場合は、その結果も加味します。
    252//              return isError || dWatch != null && dWatch.isErrorStatus() ;
    253                return !running || dWatch != null && dWatch.isErrorStatus() ;                   // 7.4.4.0 (2021/06/30) 復活
    254
    255//              return !running.get() || dWatch != null && dWatch.isErrorStatus() ;             // 7.2.9.4 (2020/11/20)
    256        }
    257
    258        /**
    259         * フォルダの監視を開始します。
    260         *
    261         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え。
    262         * @og.rev 7.4.4.0 (2021/06/30) stop() してから、実際に止まるまでの時間、待機します。
    263         *
    264         * 自身を、Threadに登録して、Thread#start() を実行します。
    265         * 内部の Thread オブジェクトがなければ、新しく作成します。
    266         * すでに、実行中の場合は、何もしません。
    267         * 条件を変えて、実行したい場合は、stop() メソッドで、一旦スレッドを
    268         * 停止させてから、再び、#start() メソッドを呼び出してください。
    269         */
    270        public void start() {
    271                // 7.4.4.0 (2021/06/30) stop() してから、実際に止まるまでの時間、待機します。
    272                int cnt = 0;
    273                while( running ) {
    274                        cnt++ ;
    275                        try{ Thread.sleep( STOP_WATI_TIME ); } catch( final InterruptedException ex ){}
    276                        if( cnt >= STOP_WATI_CNT ) {    // ループ後も、まだ、stop() 出来ていない場合。
    277                                LOGGER.warning( () -> "FileWatch Stop Error : [" + dirPath + "]" );
    278                        }
    279                }
    280
    281                running = true;                         // 7.4.4.0 (2021/06/30) 復活
    282
    283                if( thread == null ) {
    284                        thread = new Thread( this );
    285//                      running = true;
    286//                      running.set( true );    // 7.2.9.4 (2020/11/20)
    287                        thread.start();                 // running=true; を先に行わないと、すぐに終了してしまう。
    288                }
    289
    290                // 監視漏れのファイルを、一定時間でスキャンする
    291                if( useDirWatch ) {
    292                        dWatch = new DirWatch( dirPath,useTree );
    293                        if( dirWatchMch.isEmpty() ) {                   // 初期値は、未登録時は、本体と同じPathMatcher を使用します。
    294                                dWatch.setPathMatcher( pathMchSet );
    295                        }
    296                        else {
    297                                dWatch.setPathMatcher( dirWatchMch );
    298                        }
    299                        dWatch.callback( path -> action.accept( DIR_WATCH_EVENT , path ) ) ;    // BiConsumer<String,Path> を Consumer<Path> に変換しています。
    300                        dWatch.start();
    301                }
    302        }
    303
    304        /**
    305         * フォルダの監視を終了します。
    306         *
    307         * 自身を登録しているThreadに、割り込みをかけるため、
    308         * Thread#interrupt() を実行します。
    309         * フォルダ監視は、ファイル変更イベントが発生するまで待機していますが、
    310         * interrupt() を実行すると、強制的に中断できます。
    311         * 内部の Thread オブジェクトは、破棄するため、再び、start() メソッドで
    312         * 実行再開することが可能です。
    313         *
    314         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え。
    315         * @og.rev 7.4.4.0 (2021/06/30) thread の存在有無にかかわらず、running は停止状態にする。
    316         */
    317        public void stop() {
    318                // 7.4.4.0 (2021/06/30) thread の存在有無にかかわらず、running は停止状態にする。
    319                running = false;                // 7.4.4.0 (2021/06/30) 復活
    320
    321                if( thread != null ) {
    322//                      running = false;
    323        //              running.set( false );           // 7.2.9.4 (2020/11/20)
    324                        thread.interrupt();
    325        //              thread = null;                  1.1.0 (2018/02/01) stop() 時に null を入れると、interrupt() 後の処理が継続できなくなる。
    326        //              なので、run()の最後に、thread = null を入れておきます。
    327                }
    328
    329                if( dWatch != null ) {
    330                        dWatch.stop();
    331                        dWatch = null;
    332                }
    333        }
    334
    335        /**
    336         * Runnableインターフェースのrunメソッドです。
    337         *
    338         * 規定のスケジュール時刻が来ると、呼ばれる runメソッドです。
    339         *
    340         * @og.rev 7.2.5.0 (2020/06/01) LOGGERを使用します。
    341         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え。
    342         */
    343        @Override
    344        public void run() {
    345                try {
    346                        execute();
    347                }
    348                catch( final IOException ex ) {
    349                        // MSG0102 = ﾌｧｲﾙ監視に失敗しました。 Path=[{0}]
    350//                      MsgUtil.errPrintln( ex , "MSG0102" , dirPath );
    351                        final String errMsg = "FileWatch#run : Path=" + dirPath ;
    352                        LOGGER.warning( ex , "MSG0102" , errMsg );
    353                }
    354                catch( final Throwable th ) {
    355                        // MSG0021 = 予期せぬｴﾗｰが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
    356//                      MsgUtil.errPrintln( th , "MSG0021" , toString() );
    357                        final String errMsg = "FileWatch#run : Path=" + dirPath ;
    358                        LOGGER.warning( th , "MSG0021" , errMsg );
    359                }
    360                finally {
    361                        running = false;                        // 7.4.4.0 (2021/06/30) 停止条件だが、予期せぬエラーで停止した場合も、設定する。
    362                        thread  = null;                         // 7.2.5.0 (2020/06/01) 停止処理
    363//                      running = false;
    364//                      running.set( false );           // 7.2.9.4 (2020/11/20)
    365                }
    366        }
    367
    368        /**
    369         * runメソッドから呼ばれる、実際の処理。
    370         *
    371         * try ･･･ catch( Throwable ) 構文を、runメソッドの標準的な作りにしておきたいため、
    372         * あえて、実行メソッドを分けているだけです。
    373         *
    374         * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
    375         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え
    376         * @og.rev 8.0.0.0 (2021/07/01) dirPathのsynchronized作成
    377         */
    378        private void execute() throws IOException {
    379                // ファイル監視などの機能は新しいNIO2クラスで拡張されたので
    380                // 旧File型から、新しいPath型に変換する.
    381                LOGGER.info( () -> "FileWatch Start: " + dirPath );
    382
    383                // デフォルトのファイル・システムを閉じることはできません。(UnsupportedOperationException がスローされる)
    384                // なので、try-with-resources 文 (AutoCloseable) に、入れません。
    385//              final FileSystem fs = dirPath.getFileSystem();                  // フォルダが属するファイルシステムを得る()
    386                final FileSystem fs = FileSystems.getDefault();                 // 7.4.4.0 (2021/06/30) 上記と同じｵﾌﾞｼﾞｪｸﾄだから。
    387
    388                // try-with-resources 文 (AutoCloseable)
    389                // ファイルシステムに対応する監視サービスを構築する.
    390                // (一つのサービスで複数の監視が可能)
    391                try( WatchService watcher = fs.newWatchService() ) {
    392                        // フォルダに対して監視サービスを登録する.
    393                        final WatchKey watchKey = dirPath.register( watcher , weKind , extModifiers );
    394
    395                        // 監視が有効であるかぎり、ループする.
    396                        // (監視がcancelされるか、監視サービスが停止した場合はfalseとなる)
    397                        try{
    398                                boolean flag = true;
    399                                while( flag && running ) {                                                                              // 7.4.4.0 (2021/06/30) 復活
    400//                              while( flag && running.get() ) {                                                                // 7.2.9.4 (2020/11/20)
    401                                        // スレッドの割り込み = 終了要求を判定する.
    402                        //              if( Thread.currentThread().isInterrupted() ) {
    403                        //                      throw new InterruptedException();
    404                        //              }
    405
    406                                        // take は、ファイル変更イベントが発生するまで待機する.
    407                                        final WatchKey detectKey = watcher.take();                      // poll() は、キューが空の場合はブロックせずに null を返す
    408
    409                                        // イベント発生元を判定する
    410//                                      if( detectKey.equals( watchKey ) ) {
    411                                        if( watchKey.equals( detectKey ) ) {                            // 8.0.0.0 (2021/07/01) 入れ替え(null対応)
    412                                                // 発生したイベント内容をプリントする.
    413                                                for( final WatchEvent<?> event : detectKey.pollEvents() ) {
    414                                                        // 追加・変更・削除対象のファイルを取得する.
    415                                                        // (ただし、overflow時などはnullとなることに注意)
    416                                                        final Path path = (Path)event.context();
    417                                                        if( path != null && pathMchSet.matches( path ) ) {
    418                                                                final Path fpath = dirPath.resolve( path );
    419                                                                synchronized( dirPath ) {                               // 8.0.0.0 (2021/07/01) dirPathのsynchronized作成
    420                                                                        if( dWatch == null || dWatch.setAdd( fpath) ) {         // このセット内に、指定された要素がなかった場合はtrue
    421                                                                                action.accept( event.kind().name() , fpath );
    422                                                                        }
    423                                                                        else {
    424                                                                                // CREATE と MODIFY などのイベントが連続して発生するケースへの対応
    425                                                                                LOGGER.info( () -> "WatchEvent Duplication: " + fpath );
    426                                                                        }
    427                                                                }
    428                                                        }
    429                                                }
    430                                        }
    431
    432                                        // イベントの受付を再開する.
    433                                        if( detectKey != null ) {               // 8.0.0.0 (2021/07/01) null対応
    434                                                detectKey.reset();
    435                                        }
    436
    437                                        if( dWatch != null ) {
    438                                                dWatch.setClear();                      // Path重複ﾁｪｯｸ用のSetは、一連のｲﾍﾞﾝﾄ完了時にｸﾘｱしておきます。
    439                                        }
    440
    441                                        // 監視サービスが活きている、または、スレッドの割り込み( = 終了要求)がないことを、をチェックする。
    442                                        flag = watchKey.isValid() && !Thread.currentThread().isInterrupted() ;
    443
    444                                        // 7.4.4.0 (2021/06/30) ※ 63ﾌｫﾙﾀﾞ以上は、監視できない？(Tomcat上では？)
    445                                        if( !watchKey.isValid() ) {
    446                                                LOGGER.warning( () -> "FileWatch No isValid : [" + dirPath + "]" );
    447                                        }
    448                                }
    449                        }
    450                        catch( final InterruptedException ex ) {
    451//                              LOGGER.warning( () -> "【WARNING】 FileWatch Canceled:" + dirPath );
    452                                LOGGER.warning( () -> "FileWatch Canceled : [" + dirPath + "]" );
    453                        }
    454                        finally {
    455                                // スレッドの割り込み = 終了要求なので監視をキャンセルしループを終了する。
    456                                if( watchKey != null ) {
    457                                        watchKey.cancel();
    458                                }
    459                        }
    460                }
    461                // FileSystemの実装(sun.nio.fs.WindowsFileSystem)は、close() 未サポート
    462                catch( final UnsupportedOperationException ex ) {
    463                        LOGGER.warning( () -> "FileSystem close : [" + dirPath + "]" );
    464                }
    465
    466                // 7.4.4.0 (2021/06/30) 念のため、入れておきます。
    467                catch( final Throwable th ) {
    468                        // MSG0021 = 予期せぬｴﾗｰが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
    469                        final String errMsg = "FileWatch#execute : Path=" + dirPath ;
    470                        LOGGER.warning( th , "MSG0021" , errMsg );
    471                }
    472
    473//              LOGGER.info( () -> "FileWatch End: " + dirPath );
    474                LOGGER.info( () -> "FileWatch End : [" + dirPath + "]" );
    475
    476//              thread  = null;                                 // 1.1.0 (2018/02/01) 停止処理
    477        //      isError = true;                                 // 何らかの原因で停止すれば、エラーと判断します。
    478        }
    479
    480        /**
    481         *このオブジェクトの文字列表現を返します。
    482         *
    483         * @return      このオブジェクトの文字列表現
    484         */
    485        @Override
    486        public String toString() {
    487                return getClass().getSimpleName() + ":" + dirPath + " , " + DIR_WATCH_EVENT + "=[" + useDirWatch + "]" ;
    488        }
    489}