/*
 * Copyright (c) 2017 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.fileexec;

// import java.io.File;
import java.io.IOException;

import java.nio.file.WatchEvent;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;							// 7.4.4.0 (2021/06/30)
import java.nio.file.WatchKey;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchService;

import java.util.function.BiConsumer;
// import java.util.concurrent.atomic.AtomicBoolean;		// 7.2.9.4 (2020/11/20) volatile boolean の代替え , // 7.4.4.0 (2021/06/30) 戻す

/**
 * FileWatch は､ﾌｧｲﾙ監視を行うｸﾗｽです｡
 *
 *<pre>
 * ﾌｧｲﾙが､追加(作成)､変更､削除された場合に､ｲﾍﾞﾝﾄが発生します｡
 * このｸﾗｽは､Runnable ｲﾝﾀｰﾌｪｰｽを実装しているため､Thread で実行することで､
 * 個々のﾌｫﾙﾀﾞの監視を行います｡
 *
 *</pre>
 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
 *
 * @version  7.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.8,
 */
public class FileWatch implements Runnable {
	private static final XLogger LOGGER= XLogger.getLogger( FileWatch.class.getSimpleName() );		// ﾛｸﾞ出力

	/** Path に､WatchService を register するときの作成ｲﾍﾞﾝﾄの簡易指定できるように｡ */
	public static final WatchEvent.Kind<Path> CREATE = StandardWatchEventKinds.ENTRY_CREATE ;

	/** Path に､WatchService を register するときの変更ｲﾍﾞﾝﾄの簡易指定できるように｡ */
	public static final WatchEvent.Kind<Path> MODIFY = StandardWatchEventKinds.ENTRY_MODIFY ;

	/** Path に､WatchService を register するときの削除ｲﾍﾞﾝﾄの簡易指定できるように｡  */
	public static final WatchEvent.Kind<Path> DELETE = StandardWatchEventKinds.ENTRY_DELETE ;

	/** Path に､WatchService を register するときの特定不能時ｲﾍﾞﾝﾄの簡易指定できるように｡ */
	public static final WatchEvent.Kind<?>    OVERFLOW = StandardWatchEventKinds.OVERFLOW ;

	// Path に､WatchService を register するときのｲﾍﾞﾝﾄ
	private static final WatchEvent.Kind<?>[] WE_KIND = new WatchEvent.Kind<?>[] {
			CREATE , MODIFY , DELETE , OVERFLOW
	};

	// Path に､WatchService を register するときの登録方法の修飾子(修飾子 なしの場合)
	private static final WatchEvent.Modifier[] WE_MOD_ONE  = new WatchEvent.Modifier[0];	// Modifier なし

	// Path に､WatchService を register するときの登録方法の修飾子(以下の階層も監視対象にします)
	private static final WatchEvent.Modifier[] WE_MOD_TREE = new WatchEvent.Modifier[] {	// ﾂﾘｰ階層
					com.sun.nio.file.ExtendedWatchEventModifier.FILE_TREE
			};

	/** DirWatch でｽｷｬﾝした場合のｲﾍﾞﾝﾄ名 {@value} */
	public static final String DIR_WATCH_EVENT = "DirWatch";

	/** 7.4.4.0 (2021/06/30) stop() してから､実際に止まるまで 待機する時間 (ms ) */
	public static final int STOP_WATI_TIME = 500 ;

	/** 7.4.4.0 (2021/06/30) stop() してから､実際に止まるまで 待機する回数 */
	public static final int STOP_WATI_CNT = 5 ;

	// 監視対象のﾌｫﾙﾀﾞ
	private final Path dirPath ;

	// 監視方法
	private final boolean	useTree ;
	private final WatchEvent.Modifier[] extModifiers ;

	// callbackするための､関数型ｲﾝﾀｰﾌｪｰｽ(ﾒｿｯﾄﾞ参照)
	private BiConsumer<String,Path> action = (event,path) -> System.out.println( "Event=" + event + " , Path=" + path ) ;

	// Path に､WatchService を register するときのｲﾍﾞﾝﾄ
	private WatchEvent.Kind<?>[] weKind = WE_KIND ;						// 初期値は､すべて

	// ﾊﾟｽの照合操作を行うPathMatcher の初期値
	private final PathMatcherSet pathMchSet = new PathMatcherSet();		// PathMatcher ｲﾝﾀｰﾌｪｰｽを継承

	// DirWatchのﾊﾟｽの照合操作を行うPathMatcher の初期値
	private final PathMatcherSet dirWatchMch = new PathMatcherSet();	// PathMatcher ｲﾝﾀｰﾌｪｰｽを継承

	// 何らかの原因でｲﾍﾞﾝﾄもれした場合､ﾌｫﾙﾀﾞｽｷｬﾝを行います｡
	private boolean		useDirWatch	= true;								// 初期値は､ｲﾍﾞﾝﾄ漏れ監視を行います｡
	private DirWatch	dWatch ;										// DirWatch のstop時に呼び出すための変数
	private Thread		thread ;										// 停止するときに呼び出すため

	private volatile boolean running ;									// 状態とThreadの停止に使用する｡ // 7.4.4.0 (2021/06/30) 復活
//	private final AtomicBoolean running = new AtomicBoolean();			// 7.2.9.4 (2020/11/20) volatile boolean の代替え ( 状態とThreadの停止に使用する｡)

	/**
	 * 処理対象のﾌｫﾙﾀﾞのﾊﾟｽｵﾌﾞｼﾞｪｸﾄを指定して､ﾌｧｲﾙ監視ｲﾝｽﾀﾝｽを作成します｡
	 *
	 * ここでは､指定のﾌｫﾙﾀﾞの内のﾌｧｲﾙのみ監視します｡
	 * これは､new FileWatch( dir , false ) とまったく同じです｡
	 *
	 * @param dir	処理対象のﾌｫﾙﾀﾞｵﾌﾞｼﾞｪｸﾄ
	 */
	public FileWatch( final Path dir ) {
		this( dir , false );
	}

	/**
	 * 処理対象のﾌｫﾙﾀﾞのﾊﾟｽｵﾌﾞｼﾞｪｸﾄと､監視対象方法を指定して､ﾌｧｲﾙ監視ｲﾝｽﾀﾝｽを作成します｡
	 *
	 * useTree を true に設定すると､指定のﾌｫﾙﾀﾞの内のﾌｫﾙﾀﾞ階層を､すべて監視対象とします｡
	 *
	 * @param dir	処理対象のﾌｫﾙﾀﾞのﾊﾟｽｵﾌﾞｼﾞｪｸﾄ
	 * @param useTree	ﾌｫﾙﾀﾞﾂﾘｰの階層をさかのぼって監視するかどうか(true:ﾌｫﾙﾀﾞ階層を下る)
	 */
	public FileWatch( final Path dir , final boolean useTree ) {
		dirPath		 = dir ;
		this.useTree = useTree;
		extModifiers = useTree ? WE_MOD_TREE : WE_MOD_ONE ;
	}

	/**
	 * 指定のｲﾍﾞﾝﾄの種類のみ､監視対象に設定します｡
	 *
	 * ここで指定したｲﾍﾞﾝﾄのみ､監視対象になり､callback されます｡
	 * 第一引数は､ｲﾍﾞﾝﾄの種類(ENTRY_CREATE,ENTRY_MODIFY,ENTRY_DELETE,OVERFLOW)
	 *
	 * @param	kind 監視対象に設定するｲﾍﾞﾝﾄの種類
	 * @see		java.nio.file.StandardWatchEventKinds
	 */
	public void setEventKinds( final WatchEvent.Kind<?>... kind ) {
		if( kind != null && kind.length > 0 ) {
			weKind = kind;
		}
	}

	/**
	 * 指定のﾊﾟｽの照合操作で､ﾊﾟﾀｰﾝに一致したﾊﾟｽのみ､callback されます｡
	 *
	 * ここで指定したﾊﾟﾀｰﾝの一致を判定し､一致した場合は､callback されます｡
	 * 指定しない場合は､すべて許可されたことになります｡
	 * なお､#setPathEndsWith(String...) と､この設定は同時には行うことは出来ません｡
	 * (最後に登録した条件が､適用されます｡)
	 *
	 * @param	pathMch ﾊﾟｽの照合操作のﾊﾟﾀｰﾝ
	 * @see		java.nio.file.PathMatcher
	 * @see		#setPathEndsWith(String...)
	 */
	public void setPathMatcher( final PathMatcher pathMch ) {
		pathMchSet.addPathMatcher( pathMch );
	}

	/**
	 * 指定のﾊﾟｽが､指定の文字列と､終端一致(endsWith) したﾊﾟｽのみ､callback されます｡
	 *
	 * これは､#setPathMatcher(PathMatcher) の簡易指定版です｡
	 * 指定の終端文字列(一般には拡張子)のうち､ひとつでも一致すれば､true となりcallback されます｡
	 * 指定しない場合(null)は､すべて許可されたことになります｡
	 * 終端文字列の判定には､大文字小文字の区別を行いません｡
	 * なお､#setPathMatcher(PathMatcher) と､この設定は同時には行うことは出来ません｡
	 * (最後に登録した条件が､適用されます｡)
	 *
	 * @param	endKey ﾊﾟｽの終端一致のﾊﾟﾀｰﾝ
	 * @see		#setPathMatcher(PathMatcher)
	 */
	public void setPathEndsWith( final String... endKey ) {
		pathMchSet.addEndsWith( endKey );
	}

	/**
	 * ｲﾍﾞﾝﾄの種類と､ﾌｧｲﾙﾊﾟｽを､引数に取る BiConsumer ﾀﾞｵﾌﾞｼﾞｪｸﾄを設定します｡
	 *
	 * これは､関数型ｲﾝﾀﾌｪｰｽなので､ﾗﾑﾀﾞ式またはﾒｿｯﾄﾞ参照の代入先として使用できます｡
	 * ｲﾍﾞﾝﾄが発生したときの ｲﾍﾞﾝﾄの種類と､そのﾌｧｲﾙﾊﾟｽを引数に､accept(String,Path) ﾒｿｯﾄﾞが呼ばれます｡
	 * 第一引数は､ｲﾍﾞﾝﾄの種類(ENTRY_CREATE,ENTRY_MODIFY,ENTRY_DELETE,OVERFLOW)
	 * 第二引数は､ﾌｧｲﾙﾊﾟｽ(監視ﾌｫﾙﾀﾞで､resolveされた､正式なﾌﾙﾊﾟｽ)
	 *
	 * @param	act 2つの入力(ｲﾍﾞﾝﾄの種類 とﾌｧｲﾙﾊﾟｽ) を受け取る関数型ｲﾝﾀﾌｪｰｽ
	 * @see		BiConsumer#accept(Object,Object)
	 */
	public void callback( final BiConsumer<String,Path> act ) {
		if( act != null ) {
			action = act ;
		}
	}

	/**
	 * 何らかの原因でｲﾍﾞﾝﾄを掴み損ねた場合に､ﾌｫﾙﾀﾞｽｷｬﾝするかどうかを指定します｡
	 *
	 * ｽｷｬﾝ開始の遅延時間と､ｽｷｬﾝ間隔､ﾌｧｲﾙのﾀｲﾑｽﾀﾝﾌﾟとの比較時間等は､
	 * DirWatch の初期値をそのまま使用するため､ここでは指定できません｡
	 * 個別に指定したい場合は､このﾌﾗｸﾞをfalse にｾｯﾄして､個別に､DirWatch を作成してください｡
	 * このﾒｿｯﾄﾞでは､#setPathEndsWith( String... )や､#setPathMatcher( PathMatcher ) で
	 * 指定した条件が､そのまま適用されます｡
	 *
	 * @param	flag ﾌｫﾙﾀﾞｽｷｬﾝするかどうか(true:する/false:しない)
	 * @see		DirWatch
	 */
	public void setUseDirWatch( final boolean flag ) {
		useDirWatch = flag;
	}

	/**
	 * 何らかの原因でｲﾍﾞﾝﾄを掴み損ねた場合の､ﾌｫﾙﾀﾞｽｷｬﾝの対象ﾌｧｲﾙの拡張子を指定します｡
	 *
	 * このﾒｿｯﾄﾞを使用する場合は､useDirWatch は､true にｾｯﾄされます｡
	 * ｽｷｬﾝ開始の遅延時間と､ｽｷｬﾝ間隔､ﾌｧｲﾙのﾀｲﾑｽﾀﾝﾌﾟとの比較時間等は､
	 * DirWatch の初期値をそのまま使用するため､ここでは指定できません｡
	 * このﾒｿｯﾄﾞでは､DirWatch 対象の終端ﾊﾟﾀｰﾝを独自に指定できますが､FileWatch で
	 * で指定した条件も､ｸﾘｱされるので､含める必要があります｡
	 *
	 * @param	endKey ﾊﾟｽの終端一致のﾊﾟﾀｰﾝ
	 * @see		DirWatch
	 */
	public void setDirWatchEndsWith( final String... endKey ) {
		if( endKey != null && endKey.length > 0 ) {
			useDirWatch = true;					// 対象があれば､実行するが､true になる｡

			dirWatchMch.addEndsWith( endKey );
		}
	}

	/**
	 * このﾌｧｲﾙ監視で､最後に処理した結果が､ｴﾗｰの場合に､true を返します｡
	 *
	 * 通常は､対象ﾌｫﾙﾀﾞが見つからない場合や､ﾌｫﾙﾀﾞｽｷｬﾝ（DirWatch）で
	 * ｴﾗｰが発生した場合に､true にｾｯﾄされます｡
	 * また､stop() ﾒｿｯﾄﾞが呼ばれた場合も､true にｾｯﾄされます｡
	 *
	 * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え｡
	 *
	 * @return	ｴﾗｰ状態(true:ｴﾗｰ,false:正常)
	 */
	public boolean isErrorStatus() {
		// DirWatch を使用している場合は､その結果も加味します｡
//		return isError || dWatch != null && dWatch.isErrorStatus() ;
		return !running || dWatch != null && dWatch.isErrorStatus() ;			// 7.4.4.0 (2021/06/30) 復活

//		return !running.get() || dWatch != null && dWatch.isErrorStatus() ;		// 7.2.9.4 (2020/11/20)
	}

	/**
	 * ﾌｫﾙﾀﾞの監視を開始します｡
	 *
	 * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え｡
	 * @og.rev 7.4.4.0 (2021/06/30) stop() してから､実際に止まるまでの時間､待機します｡
	 * @og.rev 8.1.0.3 (2022/01/21) ｽﾚｯﾄﾞに名前を付けておきます｡
	 *
	 * 自身を､Threadに登録して､Thread#start() を実行します｡
	 * 内部の Thread ｵﾌﾞｼﾞｪｸﾄがなければ､新しく作成します｡
	 * すでに､実行中の場合は､何もしません｡
	 * 条件を変えて､実行したい場合は､stop() ﾒｿｯﾄﾞで､一旦ｽﾚｯﾄﾞを
	 * 停止させてから､再び､#start() ﾒｿｯﾄﾞを呼び出してください｡
	 */
	public void start() {
		// 7.4.4.0 (2021/06/30) stop() してから､実際に止まるまでの時間､待機します｡
		int cnt = 0;
		while( running ) {
			cnt++ ;
			try{ Thread.sleep( STOP_WATI_TIME ); } catch( final InterruptedException ex ){}
			if( cnt >= STOP_WATI_CNT ) {	// ﾙｰﾌﾟ後も､まだ､stop() 出来ていない場合｡
				LOGGER.warning( () -> "FileWatch Stop Error : [" + dirPath + "]" );
			}
		}

		running = true;				// 7.4.4.0 (2021/06/30) 復活

		if( thread == null ) {
//			thread = new Thread( this );
			thread = new Thread( this,"FileWatch" );		// 8.1.0.3 (2022/01/21)
//			running = true;
//			running.set( true );	// 7.2.9.4 (2020/11/20)
			thread.start();			// running=true; を先に行わないと､すぐに終了してしまう｡
		}

		// 監視漏れのﾌｧｲﾙを､一定時間でｽｷｬﾝする
		if( useDirWatch ) {
			dWatch = new DirWatch( dirPath,useTree );
			if( dirWatchMch.isEmpty() ) {			// 初期値は､未登録時は､本体と同じPathMatcher を使用します｡
				dWatch.setPathMatcher( pathMchSet );
			}
			else {
				dWatch.setPathMatcher( dirWatchMch );
			}
			dWatch.callback( path -> action.accept( DIR_WATCH_EVENT , path ) ) ;	// BiConsumer<String,Path> を Consumer<Path> に変換しています｡
			dWatch.start();
		}
	}

	/**
	 * ﾌｫﾙﾀﾞの監視を終了します｡
	 *
	 * 自身を登録しているThreadに､割り込みをかけるため､
	 * Thread#interrupt() を実行します｡
	 * ﾌｫﾙﾀﾞ監視は､ﾌｧｲﾙ変更ｲﾍﾞﾝﾄが発生するまで待機していますが､
	 * interrupt() を実行すると､強制的に中断できます｡
	 * 内部の Thread ｵﾌﾞｼﾞｪｸﾄは､破棄するため､再び､start() ﾒｿｯﾄﾞで
	 * 実行再開することが可能です｡
	 *
	 * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え｡
	 * @og.rev 7.4.4.0 (2021/06/30) thread の存在有無にかかわらず､running は停止状態にする｡
	 */
	public void stop() {
		// 7.4.4.0 (2021/06/30) thread の存在有無にかかわらず､running は停止状態にする｡
		running = false;		// 7.4.4.0 (2021/06/30) 復活

		if( thread != null ) {
//			running = false;
	//		running.set( false );		// 7.2.9.4 (2020/11/20)
			thread.interrupt();
	//		thread = null;			1.1.0 (2018/02/01) stop() 時に null を入れると､interrupt() 後の処理が継続できなくなる｡
	//		なので､run()の最後に､thread = null を入れておきます｡
		}

		if( dWatch != null ) {
			dWatch.stop();
			dWatch = null;
		}
	}

	/**
	 * Runnableｲﾝﾀｰﾌｪｰｽのrunﾒｿｯﾄﾞです｡
	 *
	 * 規定のｽｹｼﾞｭｰﾙ時刻が来ると､呼ばれる runﾒｿｯﾄﾞです｡
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) LOGGERを使用します｡
	 * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え｡
	 */
	@Override
	public void run() {
		try {
			execute();
		}
		catch( final IOException ex ) {
			// MSG0102 = ﾌｧｲﾙ監視に失敗しました｡ Path=[{0}]
//			MsgUtil.errPrintln( ex , "MSG0102" , dirPath );
			final String errMsg = "FileWatch#run : Path=" + dirPath ;
			LOGGER.warning( ex , "MSG0102" , errMsg );
		}
		catch( final Throwable th ) {
			// MSG0021 = 予期せぬｴﾗｰが発生しました｡\n\tﾒｯｾｰｼﾞ=[{0}]
//			MsgUtil.errPrintln( th , "MSG0021" , toString() );
			final String errMsg = "FileWatch#run : Path=" + dirPath ;
			LOGGER.warning( th , "MSG0021" , errMsg );
		}
		finally {
			running = false;			// 7.4.4.0 (2021/06/30) 停止条件だが､予期せぬｴﾗｰで停止した場合も､設定する｡
			thread  = null;				// 7.2.5.0 (2020/06/01) 停止処理
//			running = false;
//			running.set( false );		// 7.2.9.4 (2020/11/20)
		}
	}

	/**
	 * runﾒｿｯﾄﾞから呼ばれる､実際の処理｡
	 *
	 * try ･･･ catch( Throwable ) 構文を､runﾒｿｯﾄﾞの標準的な作りにしておきたいため､
	 * あえて､実行ﾒｿｯﾄﾞを分けているだけです｡
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え
	 * @og.rev 8.0.0.0 (2021/07/01) dirPathのsynchronized作成
	 */
	private void execute() throws IOException {
		// ﾌｧｲﾙ監視などの機能は新しいNIO2ｸﾗｽで拡張されたので
		// 旧File型から､新しいPath型に変換する.
		LOGGER.info( () -> "FileWatch Start: " + dirPath );

		// ﾃﾞﾌｫﾙﾄのﾌｧｲﾙ･ｼｽﾃﾑを閉じることはできません｡(UnsupportedOperationException がｽﾛｰされる)
		// なので､try-with-resources 文 (AutoCloseable) に､入れません｡
//		final FileSystem fs = dirPath.getFileSystem();			// ﾌｫﾙﾀﾞが属するﾌｧｲﾙｼｽﾃﾑを得る()
		final FileSystem fs = FileSystems.getDefault();			// 7.4.4.0 (2021/06/30) 上記と同じｵﾌﾞｼﾞｪｸﾄだから｡

		// try-with-resources 文 (AutoCloseable)
		// ﾌｧｲﾙｼｽﾃﾑに対応する監視ｻｰﾋﾞｽを構築する.
		// (一つのｻｰﾋﾞｽで複数の監視が可能)
		try( WatchService watcher = fs.newWatchService() ) {
			// ﾌｫﾙﾀﾞに対して監視ｻｰﾋﾞｽを登録する.
			final WatchKey watchKey = dirPath.register( watcher , weKind , extModifiers );

			// 監視が有効であるかぎり､ﾙｰﾌﾟする.
			// (監視がcancelされるか､監視ｻｰﾋﾞｽが停止した場合はfalseとなる)
			try{
				boolean flag = true;
				while( flag && running ) {										// 7.4.4.0 (2021/06/30) 復活
//				while( flag && running.get() ) {								// 7.2.9.4 (2020/11/20)
					// ｽﾚｯﾄﾞの割り込み = 終了要求を判定する.
			//		if( Thread.currentThread().isInterrupted() ) {
			//			throw new InterruptedException();
			//		}

					// take は､ﾌｧｲﾙ変更ｲﾍﾞﾝﾄが発生するまで待機する.
					final WatchKey detectKey = watcher.take();			// poll() は､ｷｭｰが空の場合はﾌﾞﾛｯｸせずに null を返す

					// ｲﾍﾞﾝﾄ発生元を判定する
//					if( detectKey.equals( watchKey ) ) {
					if( watchKey.equals( detectKey ) ) {				// 8.0.0.0 (2021/07/01) 入れ替え(null対応)
						// 発生したｲﾍﾞﾝﾄ内容をﾌﾟﾘﾝﾄする.
						for( final WatchEvent<?> event : detectKey.pollEvents() ) {
							// 追加･変更･削除対象のﾌｧｲﾙを取得する.
							// (ただし､overflow時などはnullとなることに注意)
							final Path path = (Path)event.context();
							if( path != null && pathMchSet.matches( path ) ) {
								final Path fpath = dirPath.resolve( path );
								synchronized( dirPath ) {				// 8.0.0.0 (2021/07/01) dirPathのsynchronized作成
									if( dWatch == null || dWatch.setAdd( fpath) ) {		// このｾｯﾄ内に､指定された要素がなかった場合はtrue
										action.accept( event.kind().name() , fpath );
									}
									else {
										// CREATE と MODIFY などのｲﾍﾞﾝﾄが連続して発生するｹｰｽへの対応
										LOGGER.info( () -> "WatchEvent Duplication: " + fpath );
									}
								}
							}
						}
					}

					// ｲﾍﾞﾝﾄの受付を再開する.
					if( detectKey != null ) {		// 8.0.0.0 (2021/07/01) null対応
						detectKey.reset();
					}

					if( dWatch != null ) {
						dWatch.setClear();			// Path重複ﾁｪｯｸ用のSetは､一連のｲﾍﾞﾝﾄ完了時にｸﾘｱしておきます｡
					}

					// 監視ｻｰﾋﾞｽが活きている､または､ｽﾚｯﾄﾞの割り込み( = 終了要求)がないことを､をﾁｪｯｸする｡
					flag = watchKey.isValid() && !Thread.currentThread().isInterrupted() ;

					// 7.4.4.0 (2021/06/30) ※ 63ﾌｫﾙﾀﾞ以上は､監視できない？(Tomcat上では？)
					if( !watchKey.isValid() ) {
						LOGGER.warning( () -> "FileWatch No isValid : [" + dirPath + "]" );
					}
				}
			}
			catch( final InterruptedException ex ) {
//				LOGGER.warning( () -> "【WARNING】 FileWatch Canceled:" + dirPath );
				LOGGER.warning( () -> "FileWatch Canceled : [" + dirPath + "]" );
			}
			finally {
				// ｽﾚｯﾄﾞの割り込み = 終了要求なので監視をｷｬﾝｾﾙしﾙｰﾌﾟを終了する｡
				if( watchKey != null ) {
					watchKey.cancel();
				}
			}
		}
		// FileSystemの実装(sun.nio.fs.WindowsFileSystem)は､close() 未ｻﾎﾟｰﾄ
		catch( final UnsupportedOperationException ex ) {
			LOGGER.warning( () -> "FileSystem close : [" + dirPath + "]" );
		}

		// 7.4.4.0 (2021/06/30) 念のため､入れておきます｡
		catch( final Throwable th ) {
			// MSG0021 = 予期せぬｴﾗｰが発生しました｡\n\tﾒｯｾｰｼﾞ=[{0}]
			final String errMsg = "FileWatch#execute : Path=" + dirPath ;
			LOGGER.warning( th , "MSG0021" , errMsg );
		}

//		LOGGER.info( () -> "FileWatch End: " + dirPath );
		LOGGER.info( () -> "FileWatch End : [" + dirPath + "]" );

//		thread  = null;					// 1.1.0 (2018/02/01) 停止処理
	//	isError = true;					// 何らかの原因で停止すれば､ｴﾗｰと判断します｡
	}

	/**
	 *このｵﾌﾞｼﾞｪｸﾄの文字列表現を返します｡
	 *
	 * @return	このｵﾌﾞｼﾞｪｸﾄの文字列表現
	 */
	@Override
	public String toString() {
		return getClass().getSimpleName() + ":" + dirPath + " , " + DIR_WATCH_EVENT + "=[" + useDirWatch + "]" ;
	}
}
