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