    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         * @og.rev 8.1.0.3 (2022/01/21) ｽﾚｯﾄﾞに名前を付けておきます｡
    264         *
    265         * 自身を､Threadに登録して､Thread#start() を実行します｡
    266         * 内部の Thread ｵﾌﾞｼﾞｪｸﾄがなければ､新しく作成します｡
    267         * すでに､実行中の場合は､何もしません｡
    268         * 条件を変えて､実行したい場合は､stop() ﾒｿｯﾄﾞで､一旦ｽﾚｯﾄﾞを
    269         * 停止させてから､再び､#start() ﾒｿｯﾄﾞを呼び出してください｡
    270         */
    271        public void start() {
    272                // 7.4.4.0 (2021/06/30) stop() してから､実際に止まるまでの時間､待機します｡
    273                int cnt = 0;
    274                while( running ) {
    275                        cnt++ ;
    276                        try{ Thread.sleep( STOP_WATI_TIME ); } catch( final InterruptedException ex ){}
    277                        if( cnt >= STOP_WATI_CNT ) {    // ﾙｰﾌﾟ後も､まだ､stop() 出来ていない場合｡
    278                                LOGGER.warning( () -> "FileWatch Stop Error : [" + dirPath + "]" );
    279                        }
    280                }
    281
    282                running = true;                         // 7.4.4.0 (2021/06/30) 復活
    283
    284                if( thread == null ) {
    285//                      thread = new Thread( this );
    286                        thread = new Thread( this,"FileWatch" );                // 8.1.0.3 (2022/01/21)
    287//                      running = true;
    288//                      running.set( true );    // 7.2.9.4 (2020/11/20)
    289                        thread.start();                 // running=true; を先に行わないと､すぐに終了してしまう｡
    290                }
    291
    292                // 監視漏れのﾌｧｲﾙを､一定時間でｽｷｬﾝする
    293                if( useDirWatch ) {
    294                        dWatch = new DirWatch( dirPath,useTree );
    295                        if( dirWatchMch.isEmpty() ) {                   // 初期値は､未登録時は､本体と同じPathMatcher を使用します｡
    296                                dWatch.setPathMatcher( pathMchSet );
    297                        }
    298                        else {
    299                                dWatch.setPathMatcher( dirWatchMch );
    300                        }
    301                        dWatch.callback( path -> action.accept( DIR_WATCH_EVENT , path ) ) ;    // BiConsumer<String,Path> を Consumer<Path> に変換しています｡
    302                        dWatch.start();
    303                }
    304        }
    305
    306        /**
    307         * ﾌｫﾙﾀﾞの監視を終了します｡
    308         *
    309         * 自身を登録しているThreadに､割り込みをかけるため､
    310         * Thread#interrupt() を実行します｡
    311         * ﾌｫﾙﾀﾞ監視は､ﾌｧｲﾙ変更ｲﾍﾞﾝﾄが発生するまで待機していますが､
    312         * interrupt() を実行すると､強制的に中断できます｡
    313         * 内部の Thread ｵﾌﾞｼﾞｪｸﾄは､破棄するため､再び､start() ﾒｿｯﾄﾞで
    314         * 実行再開することが可能です｡
    315         *
    316         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え｡
    317         * @og.rev 7.4.4.0 (2021/06/30) thread の存在有無にかかわらず､running は停止状態にする｡
    318         */
    319        public void stop() {
    320                // 7.4.4.0 (2021/06/30) thread の存在有無にかかわらず､running は停止状態にする｡
    321                running = false;                // 7.4.4.0 (2021/06/30) 復活
    322
    323                if( thread != null ) {
    324//                      running = false;
    325        //              running.set( false );           // 7.2.9.4 (2020/11/20)
    326                        thread.interrupt();
    327        //              thread = null;                  1.1.0 (2018/02/01) stop() 時に null を入れると､interrupt() 後の処理が継続できなくなる｡
    328        //              なので､run()の最後に､thread = null を入れておきます｡
    329                }
    330
    331                if( dWatch != null ) {
    332                        dWatch.stop();
    333                        dWatch = null;
    334                }
    335        }
    336
    337        /**
    338         * Runnableｲﾝﾀｰﾌｪｰｽのrunﾒｿｯﾄﾞです｡
    339         *
    340         * 規定のｽｹｼﾞｭｰﾙ時刻が来ると､呼ばれる runﾒｿｯﾄﾞです｡
    341         *
    342         * @og.rev 7.2.5.0 (2020/06/01) LOGGERを使用します｡
    343         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え｡
    344         */
    345        @Override       // Runnable
    346        public void run() {
    347                try {
    348                        execute();
    349                }
    350                catch( final IOException ex ) {
    351                        // MSG0102 = ﾌｧｲﾙ監視に失敗しました｡ Path=[{0}]
    352//                      MsgUtil.errPrintln( ex , "MSG0102" , dirPath );
    353                        final String errMsg = "FileWatch#run : Path=" + dirPath ;
    354                        LOGGER.warning( ex , "MSG0102" , errMsg );
    355                }
    356                catch( final Throwable th ) {
    357                        // MSG0021 = 予期せぬｴﾗｰが発生しました｡\n\tﾒｯｾｰｼﾞ=[{0}]
    358//                      MsgUtil.errPrintln( th , "MSG0021" , toString() );
    359                        final String errMsg = "FileWatch#run : Path=" + dirPath ;
    360                        LOGGER.warning( th , "MSG0021" , errMsg );
    361                }
    362                finally {
    363                        running = false;                        // 7.4.4.0 (2021/06/30) 停止条件だが､予期せぬｴﾗｰで停止した場合も､設定する｡
    364                        thread  = null;                         // 7.2.5.0 (2020/06/01) 停止処理
    365//                      running = false;
    366//                      running.set( false );           // 7.2.9.4 (2020/11/20)
    367                }
    368        }
    369
    370        /**
    371         * runﾒｿｯﾄﾞから呼ばれる､実際の処理｡
    372         *
    373         * try ･･･ catch( Throwable ) 構文を､runﾒｿｯﾄﾞの標準的な作りにしておきたいため､
    374         * あえて､実行ﾒｿｯﾄﾞを分けているだけです｡
    375         *
    376         * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
    377         * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え
    378         * @og.rev 8.0.0.0 (2021/07/01) dirPathのsynchronized作成
    379         */
    380        private void execute() throws IOException {
    381                // ﾌｧｲﾙ監視などの機能は新しいNIO2ｸﾗｽで拡張されたので
    382                // 旧File型から､新しいPath型に変換する.
    383                LOGGER.info( () -> "FileWatch Start: " + dirPath );
    384
    385                // ﾃﾞﾌｫﾙﾄのﾌｧｲﾙ･ｼｽﾃﾑを閉じることはできません｡(UnsupportedOperationException がｽﾛｰされる)
    386                // なので､try-with-resources 文 (AutoCloseable) に､入れません｡
    387//              final FileSystem fs = dirPath.getFileSystem();                  // ﾌｫﾙﾀﾞが属するﾌｧｲﾙｼｽﾃﾑを得る()
    388                final FileSystem fs = FileSystems.getDefault();                 // 7.4.4.0 (2021/06/30) 上記と同じｵﾌﾞｼﾞｪｸﾄだから｡
    389
    390                // try-with-resources 文 (AutoCloseable)
    391                // ﾌｧｲﾙｼｽﾃﾑに対応する監視ｻｰﾋﾞｽを構築する.
    392                // (一つのｻｰﾋﾞｽで複数の監視が可能)
    393                try( WatchService watcher = fs.newWatchService() ) {
    394                        // ﾌｫﾙﾀﾞに対して監視ｻｰﾋﾞｽを登録する.
    395                        final WatchKey watchKey = dirPath.register( watcher , weKind , extModifiers );
    396
    397                        // 監視が有効であるかぎり､ﾙｰﾌﾟする.
    398                        // (監視がcancelされるか､監視ｻｰﾋﾞｽが停止した場合はfalseとなる)
    399                        try{
    400                                boolean flag = true;
    401                                while( flag && running ) {                                                                              // 7.4.4.0 (2021/06/30) 復活
    402//                              while( flag && running.get() ) {                                                                // 7.2.9.4 (2020/11/20)
    403                                        // ｽﾚｯﾄﾞの割り込み = 終了要求を判定する.
    404                        //              if( Thread.currentThread().isInterrupted() ) {
    405                        //                      throw new InterruptedException();
    406                        //              }
    407
    408                                        // take は､ﾌｧｲﾙ変更ｲﾍﾞﾝﾄが発生するまで待機する.
    409                                        final WatchKey detectKey = watcher.take();                      // poll() は､ｷｭｰが空の場合はﾌﾞﾛｯｸせずに null を返す
    410
    411                                        // ｲﾍﾞﾝﾄ発生元を判定する
    412//                                      if( detectKey.equals( watchKey ) ) {
    413                                        if( watchKey.equals( detectKey ) ) {                            // 8.0.0.0 (2021/07/01) 入れ替え(null対応)
    414                                                // 発生したｲﾍﾞﾝﾄ内容をﾌﾟﾘﾝﾄする.
    415                                                for( final WatchEvent<?> event : detectKey.pollEvents() ) {
    416                                                        // 追加･変更･削除対象のﾌｧｲﾙを取得する.
    417                                                        // (ただし､overflow時などはnullとなることに注意)
    418                                                        final Path path = (Path)event.context();
    419                                                        if( path != null && pathMchSet.matches( path ) ) {
    420                                                                final Path fpath = dirPath.resolve( path );
    421                                                                synchronized( dirPath ) {                               // 8.0.0.0 (2021/07/01) dirPathのsynchronized作成
    422                                                                        if( dWatch == null || dWatch.setAdd( fpath) ) {         // このｾｯﾄ内に､指定された要素がなかった場合はtrue
    423                                                                                action.accept( event.kind().name() , fpath );
    424                                                                        }
    425                                                                        else {
    426                                                                                // CREATE と MODIFY などのｲﾍﾞﾝﾄが連続して発生するｹｰｽへの対応
    427                                                                                LOGGER.info( () -> "WatchEvent Duplication: " + fpath );
    428                                                                        }
    429                                                                }
    430                                                        }
    431                                                }
    432                                        }
    433
    434                                        // ｲﾍﾞﾝﾄの受付を再開する.
    435                                        if( detectKey != null ) {               // 8.0.0.0 (2021/07/01) null対応
    436                                                detectKey.reset();
    437                                        }
    438
    439                                        if( dWatch != null ) {
    440                                                dWatch.setClear();                      // Path重複ﾁｪｯｸ用のSetは､一連のｲﾍﾞﾝﾄ完了時にｸﾘｱしておきます｡
    441                                        }
    442
    443                                        // 監視ｻｰﾋﾞｽが活きている､または､ｽﾚｯﾄﾞの割り込み( = 終了要求)がないことを､をﾁｪｯｸする｡
    444                                        flag = watchKey.isValid() && !Thread.currentThread().isInterrupted() ;
    445
    446                                        // 7.4.4.0 (2021/06/30) ※ 63ﾌｫﾙﾀﾞ以上は､監視できない？(Tomcat上では？)
    447                                        if( !watchKey.isValid() ) {
    448                                                LOGGER.warning( () -> "FileWatch No isValid : [" + dirPath + "]" );
    449                                        }
    450                                }
    451                        }
    452                        catch( final InterruptedException ex ) {
    453//                              LOGGER.warning( () -> "【WARNING】 FileWatch Canceled:" + dirPath );
    454                                LOGGER.warning( () -> "FileWatch Canceled : [" + dirPath + "]" );
    455                        }
    456                        finally {
    457                                // ｽﾚｯﾄﾞの割り込み = 終了要求なので監視をｷｬﾝｾﾙしﾙｰﾌﾟを終了する｡
    458                                if( watchKey != null ) {
    459                                        watchKey.cancel();
    460                                }
    461                        }
    462                }
    463                // FileSystemの実装(sun.nio.fs.WindowsFileSystem)は､close() 未ｻﾎﾟｰﾄ
    464                catch( final UnsupportedOperationException ex ) {
    465                        LOGGER.warning( () -> "FileSystem close : [" + dirPath + "]" );
    466                }
    467
    468                // 7.4.4.0 (2021/06/30) 念のため､入れておきます｡
    469                catch( final Throwable th ) {
    470                        // MSG0021 = 予期せぬｴﾗｰが発生しました｡\n\tﾒｯｾｰｼﾞ=[{0}]
    471                        final String errMsg = "FileWatch#execute : Path=" + dirPath ;
    472                        LOGGER.warning( th , "MSG0021" , errMsg );
    473                }
    474
    475//              LOGGER.info( () -> "FileWatch End: " + dirPath );
    476                LOGGER.info( () -> "FileWatch End : [" + dirPath + "]" );
    477
    478//              thread  = null;                                 // 1.1.0 (2018/02/01) 停止処理
    479        //      isError = true;                                 // 何らかの原因で停止すれば､ｴﾗｰと判断します｡
    480        }
    481
    482        /**
    483         *このｵﾌﾞｼﾞｪｸﾄの文字列表現を返します｡
    484         *
    485         * @return      このｵﾌﾞｼﾞｪｸﾄの文字列表現
    486         */
    487        @Override
    488        public String toString() {
    489                return getClass().getSimpleName() + ":" + dirPath + " , " + DIR_WATCH_EVENT + "=[" + useDirWatch + "]" ;
    490        }
    491}