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 018import java.util.List; 019import java.util.function.Consumer; 020 021import java.io.File; 022import java.io.PrintWriter; 023import java.io.BufferedReader; 024import java.io.FileInputStream ; 025import java.io.InputStreamReader ; 026import java.io.IOException; 027 028import java.nio.file.Path; 029import java.nio.file.Files; 030import java.nio.file.Paths; 031import java.nio.file.FileVisitor; 032import java.nio.file.SimpleFileVisitor; 033import java.nio.file.FileVisitResult; 034import java.nio.file.StandardOpenOption; 035import java.nio.file.StandardCopyOption; 036import java.nio.file.attribute.BasicFileAttributes; 037import java.nio.file.OpenOption; 038import java.nio.file.NoSuchFileException; // 7.2.5.0 (2020/06/01) 039import java.nio.channels.FileChannel; 040import java.nio.channels.OverlappingFileLockException; 041import java.nio.charset.Charset; 042import java.nio.charset.MalformedInputException; // 7.2.5.0 (2020/06/01) 043import static java.nio.charset.StandardCharsets.UTF_8; // 7.2.5.0 (2020/06/01) 044 045/** 046 * FileUtilは、共通的に使用されるファイル操作関連のメソッドを集約した、ユーティリティークラスです。 047 * 048 *<pre> 049 * 読み込みチェックや、書き出しチェックなどの簡易的な処理をまとめているだけです。 050 * 051 *</pre> 052 * @og.rev 7.0.0.0 (2017/07/07) 新規作成 053 * 054 * @version 7.0 055 * @author Kazuhiko Hasegawa 056 * @since JDK1.8, 057 */ 058public final class FileUtil { 059 private static final XLogger LOGGER= XLogger.getLogger( FileUtil.class.getSimpleName() ); // ログ出力 060 061 /** ファイルが安定するまでの待ち時間(ミリ秒) {@value} */ 062 public static final int STABLE_SLEEP_TIME = 2000 ; // ファイルが安定するまで、2秒待つ 063 /** ファイルが安定するまでのリトライ回数 {@value} */ 064 public static final int STABLE_RETRY_COUNT = 10 ; // ファイルが安定するまで、10回リトライする。 065 066 /** ファイルロックの獲得までの待ち時間(ミリ秒) {@value} */ 067 public static final int LOCK_SLEEP_TIME = 2000 ; // ロックの獲得まで、2秒待つ 068 /** ファイルロックの獲得までのリトライ回数 {@value} */ 069 public static final int LOCK_RETRY_COUNT = 10 ; // ロックの獲得まで、10回リトライする。 070 071 /** 日本語用の、Windows-31J の、Charset */ 072 public static final Charset WINDOWS_31J = Charset.forName( "Windows-31J" ); 073 074// /** 日本語用の、UTF-8 の、Charset (Windows-31Jと同じように指定できるようにしておきます。) */ 075// public static final Charset UTF_8 = StandardCharsets.UTF_8; 076 077 private static final OpenOption[] CREATE = new OpenOption[] { StandardOpenOption.WRITE , StandardOpenOption.CREATE , StandardOpenOption.TRUNCATE_EXISTING }; 078 private static final OpenOption[] APPEND = new OpenOption[] { StandardOpenOption.WRITE , StandardOpenOption.CREATE , StandardOpenOption.APPEND }; 079 080 private static final Object STATIC_LOCK = new Object(); // staticレベルのロック 081 082 /** 083 * デフォルトコンストラクターをprivateにして、 084 * オブジェクトの生成をさせないようにする。 085 */ 086 private FileUtil() {} 087 088 /** 089 * 引数の文字列を連結した読み込み用パスのチェックを行い、存在する場合は、そのパスオブジェクトを返します。 090 * 091 * Paths#get(String,String...) で作成したパスオブジェクトに存在チェックを加えたものです。 092 * そのパスが存在しなければ、例外をThrowします。 093 * 094 * @og.rev 1.0.0 (2016/04/28) 新規追加 095 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 096 * 097 * @param first パス文字列またはパス文字列の最初の部分 098 * @param more 結合してパス文字列を形成するための追加文字列 099 * @return 指定の文字列を連結したパスオブジェクト 100 * @throws RuntimeException ファイル/フォルダは存在しない場合 101 * @see Paths#get(String,String...) 102 */ 103 public static Path readPath( final String first , final String... more ) { 104 final Path path = Paths.get( first,more ).toAbsolutePath().normalize() ; 105 106// if( !Files.exists( path ) ) { 107 if( !exists( path ) ) { // 7.2.5.0 (2020/06/01) 108 // MSG0002 = ファイル/フォルダは存在しません。file=[{0}] 109// throw MsgUtil.throwException( "MSG0002" , path ); 110 final String errMsg = "FileUtil#readPath : Path=" + path ; 111 throw MsgUtil.throwException( "MSG0002" , errMsg ); 112 } 113 114 return path; 115 } 116 117 /** 118 * 引数の文字列を連結した書き込み用パスを作成します。 119 * 120 * Paths#get(String,String...) で作成したパスオブジェクトに存在チェックを加え、 121 * そのパスが存在しなければ、作成します。 122 * パスが、フォルダの場合は、そのまま作成し、ファイルの場合は、親フォルダまでを作成します。 123 * パスがフォルダかファイルかの区別は、拡張子があるかどうかで判定します。 124 * 125 * @og.rev 1.0.0 (2016/04/28) 新規追加 126 * 127 * @param first パス文字列またはパス文字列の最初の部分 128 * @param more 結合してパス文字列を形成するための追加文字列 129 * @return 指定の文字列を連結したパスオブジェクト 130 * @throws RuntimeException ファイル/フォルダが作成できなかった場合 131 * @see Paths#get(String,String...) 132 */ 133 public static Path writePath( final String first , final String... more ) { 134 final Path path = Paths.get( first,more ).toAbsolutePath().normalize() ; 135 136 mkdirs( path,false ); 137 138 return path; 139 } 140 141 /** 142 * ファイルオブジェクトを作成します。 143 * 144 * 通常は、フォルダ+ファイル名で、新しいファイルオブジェクトを作成します。 145 * ここでは、第2引数のファイル名に、絶対パスを指定した場合は、第1引数の 146 * フォルダを使用せず、ファイル名だけで、ファイルオブジェクトを作成します。 147 * 第2引数のファイル名が、null か、ゼロ文字列の場合は、第1引数の 148 * フォルダを返します。 149 * 150 * @og.rev 7.2.1.0 (2020/03/13) isAbsolute(String)を利用します。 151 * 152 * @param path 基準となるフォルダ(ファイルの場合は、親フォルダ基準) 153 * @param fname ファイル名(絶対パス、または、相対パス) 154 * @return 合成されたファイルオブジェクト 155 */ 156 public static Path newPath( final Path path , final String fname ) { 157 if( fname == null || fname.isEmpty() ) { 158 return path; 159 } 160// else if( fname.charAt(0) == '/' || // 実フォルダが UNIX 161// fname.charAt(0) == '\\' || // 実フォルダが ネットワークパス 162// fname.length() > 1 && fname.charAt(1) == ':' ) { // 実フォルダが Windows 163 else if( isAbsolute( fname ) ) { 164 return new File( fname ).toPath(); 165 } 166 else { 167 return path.resolve( fname ); 168 } 169 } 170 171 /** 172 * ファイルアドレスが絶対パスかどうか[絶対パス:true]を判定します。 173 * 174 * ファイル名が、絶対パス('/' か、'\\' か、2文字目が ':' の場合)かどうかを 175 * 判定して、絶対パスの場合は、true を返します。 176 * それ以外(nullやゼロ文字列も含む)は、false になります。 177 * 178 * @og.rev 7.2.1.0 (2020/03/13) 新規追加 179 * 180 * @param fname ファイルパスの文字列(絶対パス、相対パス、null、ゼロ文字列) 181 * @return 絶対パスの場合は true 182 */ 183 public static boolean isAbsolute( final String fname ) { 184// return fname != null && ( 185 return fname != null && !fname.isEmpty() && ( 186 fname.charAt(0) == '/' // 実フォルダが UNIX 187 || fname.charAt(0) == '\\' // 実フォルダが ネットワークパス 188 || fname.length() > 1 && fname.charAt(1) == ':' ); // 実フォルダが Windows 189 } 190 191 /** 192 * 引数のファイルパスを親階層を含めて生成します。 193 * 194 * すでに存在している場合や作成が成功した場合は、true を返します。 195 * 作成に失敗した場合は、false です。 196 * 指定のファイルパスは、フォルダであることが前提ですが、簡易的に 197 * ファイルの場合は、その親階層のフォルダを作成します。 198 * ファイルかフォルダの判定は、拡張子があるか、ないかで判定します。 199 * 200 * @og.rev 1.0.0 (2016/04/28) 新規追加 201 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 202 * 203 * @param target ターゲットのファイルパス 204 * @param parentCheck 先に親フォルダの作成を行うかどうか(true:行う) 205 * @throws RuntimeException フォルダの作成に失敗した場合 206 */ 207// public static void mkdirs( final Path target ) { 208 public static void mkdirs( final Path target,final boolean parentCheck ) { 209// if( Files.notExists( target ) ) { // 存在しない場合 210 if( !exists( target ) ) { // 存在しない場合 7.2.5.0 (2020/06/01) 211 // 6.9.8.0 (2018/05/28) FindBugs:null になっている可能性があるメソッドの戻り値を利用している 212// final boolean isFile = target.getFileName().toString().contains( "." ); // ファイルかどうかは、拡張子の有無で判定する。 213 214 final Path tgtName = target.getFileName(); 215 if( tgtName == null ) { 216 // MSG0007 = ファイル/フォルダの作成に失敗しました。\n\tdir=[{0}] 217 throw MsgUtil.throwException( "MSG0007" , target.toString() ); 218 } 219 220 final boolean isFile = tgtName.toString().contains( "." ); // ファイルかどうかは、拡張子の有無で判定する。 221// final Path dir = isFile ? target.toAbsolutePath().getParent() : target ; // ファイルなら、親フォルダを取り出す。 222 final Path dir = isFile ? target.getParent() : target ; // ファイルなら、親フォルダを取り出す。 223 if( dir == null ) { 224 // MSG0007 = ファイル/フォルダの作成に失敗しました。\n\tdir=[{0}] 225 throw MsgUtil.throwException( "MSG0007" , target.toString() ); 226 } 227 228// if( Files.notExists( dir ) ) { // 存在しない場合 229 if( !exists( dir ) ) { // 存在しない場合 7.2.5.0 (2020/06/01) 230 try { 231 Files.createDirectories( dir ); 232 } 233 catch( final IOException ex ) { 234 // MSG0007 = ファイル/フォルダの作成に失敗しました。dir=[{0}] 235 throw MsgUtil.throwException( ex , "MSG0007" , dir ); 236 } 237 } 238 } 239 } 240 241 /** 242 * 単体ファイルをコピーします。 243 * 244 * コピー先がなければ、コピー先のフォルダ階層を作成します。 245 * コピー先がフォルダの場合は、コピー元と同じファイル名で、コピーします。 246 * コピー先のファイルがすでに存在する場合は、上書きされますので、 247 * 必要であれば、先にバックアップしておいて下さい。 248 * 249 * @og.rev 1.0.0 (2016/04/28) 新規追加 250 * 251 * @param from コピー元となるファイル 252 * @param to コピー先となるファイル 253 * @throws RuntimeException ファイル操作に失敗した場合 254 * @see #copy(Path,Path,boolean) 255 */ 256 public static void copy( final Path from , final Path to ) { 257 copy( from,to,false ); 258 } 259 260 /** 261 * パスの共有ロックを指定した、単体ファイルをコピーします。 262 * 263 * コピー先がなければ、コピー先のフォルダ階層を作成します。 264 * コピー先がフォルダの場合は、コピー元と同じファイル名で、コピーします。 265 * コピー先のファイルがすでに存在する場合は、上書きされますので、 266 * 必要であれば、先にバックアップしておいて下さい。 267 * 268 * ※ copy に関しては、コピー時間を最小化する意味で、synchronized しています。 269 * 270 * @og.rev 1.0.0 (2016/04/28) 新規追加 271 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 272 * 273 * @param from コピー元となるファイル 274 * @param to コピー先となるファイル 275 * @param useLock パスを共有ロックするかどうか 276 * @throws RuntimeException ファイル操作に失敗した場合 277 * @see #copy(Path,Path) 278 */ 279 public static void copy( final Path from , final Path to , final boolean useLock ) { 280// if( Files.exists( from ) ) { 281 if( exists( from ) ) { // 7.2.5.0 (2020/06/01) 282 mkdirs( to,false ); 283 284 // 6.9.8.0 (2018/05/28) FindBugs:null になっている可能性があるメソッドの戻り値を利用している 285// final boolean isFile = to.getFileName().toString().contains( "." ); // ファイルかどうかは、拡張子の有無で判定する。 286 287 final Path toName = to.getFileName(); 288 if( toName == null ) { 289 // MSG0008 = ファイルが移動できませんでした。\n\tfrom=[{0}] to=[{1}] 290 throw MsgUtil.throwException( "MSG0008" , from.toString() , to.toString() ); 291 } 292 293 final boolean isFile = toName.toString().contains( "." ); // ファイルかどうかは、拡張子の有無で判定する。 294 295 // コピー先がフォルダの場合は、コピー元と同じ名前のファイルにする。 296 final Path save = isFile ? to : to.resolve( from.getFileName() ); 297 298 synchronized( STATIC_LOCK ) { 299 if( useLock ) { 300 lockPath( from , in -> localCopy( in , save ) ); 301 } 302 else { 303 localCopy( from , save ); 304 } 305 } 306 } 307 else { 308 // 7.2.5.0 (2020/06/01) 309 // MSG0002 = ファイル/フォルダが存在しません。file=[{0}] 310// MsgUtil.errPrintln( "MSG0002" , from ); 311 final String errMsg = "FileUtil#copy : from=" + from ; 312 LOGGER.warning( "MSG0002" , errMsg ); 313 } 314 } 315 316 /** 317 * 単体ファイルをコピーします。 318 * 319 * これは、IOException の処理と、直前の存在チェックをまとめたメソッドです。 320 * 321 * @og.rev 1.0.0 (2016/04/28) 新規追加 322 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 323 * 324 * @param from コピー元となるファイル 325 * @param to コピー先となるファイル 326 */ 327 private static void localCopy( final Path from , final Path to ) { 328 try { 329 // 直前に存在チェックを行います。 330// if( Files.exists( from ) ) { 331 if( exists( from ) ) { // 7.2.5.0 (2020/06/01) 332 Files.copy( from , to , StandardCopyOption.REPLACE_EXISTING ); 333 } 334 } 335 catch( final IOException ex ) { 336 // MSG0012 = ファイルがコピーできませんでした。from=[{0}] to=[{1}] 337// MsgUtil.errPrintln( ex , "MSG0012" , from , to ); 338 LOGGER.warning( ex , "MSG0012" , from , to ); 339 } 340 } 341 342 /** 343 * 単体ファイルを移動します。 344 * 345 * 移動先がなければ、移動先のフォルダ階層を作成します。 346 * 移動先がフォルダの場合は、移動元と同じファイル名で、移動します。 347 * 移動先のファイルがすでに存在する場合は、上書きされますので、 348 * 必要であれば、先にバックアップしておいて下さい。 349 * 350 * @og.rev 1.0.0 (2016/04/28) 新規追加 351 * 352 * @param from 移動元となるファイル 353 * @param to 移動先となるファイル 354 * @throws RuntimeException ファイル操作に失敗した場合 355 * @see #move(Path,Path,boolean) 356 */ 357 public static void move( final Path from , final Path to ) { 358 move( from,to,false ); 359 } 360 361 /** 362 * パスの共有ロックを指定した、単体ファイルを移動します。 363 * 364 * 移動先がなければ、移動先のフォルダ階層を作成します。 365 * 移動先がフォルダの場合は、移動元と同じファイル名で、移動します。 366 * 移動先のファイルがすでに存在する場合は、上書きされますので、 367 * 必要であれば、先にバックアップしておいて下さい。 368 * 369 * ※ move に関しては、ムーブ時間を最小化する意味で、synchronized しています。 370 * 371 * @og.rev 1.0.0 (2016/04/28) 新規追加 372 * @og.rev 7.2.1.0 (2020/03/13) from,to が null の場合、処理しない。 373 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 374 * 375 * @param from 移動元となるファイル 376 * @param to 移動先となるファイル 377 * @param useLock パスを共有ロックするかどうか 378 * @throws RuntimeException ファイル操作に失敗した場合 379 * @see #move(Path,Path) 380 */ 381 public static void move( final Path from , final Path to , final boolean useLock ) { 382 if( from == null || to == null ) { return; } // 7.2.1.0 (2020/03/13) 383 384// if( Files.exists( from ) ) { 385 if( exists( from ) ) { // 1.4.0 (2019/09/01) 386 mkdirs( to,false ); 387 388 // ファイルかどうかは、拡張子の有無で判定する。 389 // 6.9.8.0 (2018/05/28) FindBugs:null になっている可能性があるメソッドの戻り値を利用している 390// final boolean isFile = to.getFileName().toString().contains( "." ); 391 final Path toName = to.getFileName(); 392 if( toName == null ) { 393 // MSG0008 = ファイルが移動できませんでした。\n\tfrom=[{0}] to=[{1}] 394 throw MsgUtil.throwException( "MSG0008" , to.toString() ); 395 } 396 397 final boolean isFile = toName.toString().contains( "." ); // ファイルかどうかは、拡張子の有無で判定する。 398 399 // 移動先がフォルダの場合は、コピー元と同じ名前のファイルにする。 400 final Path save = isFile ? to : to.resolve( from.getFileName() ); 401 402 synchronized( STATIC_LOCK ) { 403 if( useLock ) { 404 lockPath( from , in -> localMove( in , save ) ); 405 } 406 else { 407 localMove( from , save ); 408 } 409 } 410 } 411 else { 412 // MSG0002 = ファイル/フォルダが存在しません。file=[{0}] 413// MsgUtil.errPrintln( "MSG0002" , from ); 414 final String errMsg = "FileUtil#move : from=" + from ; 415 LOGGER.warning( "MSG0002" , errMsg ); 416 } 417 } 418 419 /** 420 * 単体ファイルを移動します。 421 * 422 * これは、IOException の処理と、直前の存在チェックをまとめたメソッドです。 423 * 424 * @og.rev 1.0.0 (2016/04/28) 新規追加 425 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 426 * 427 * @param from 移動元となるファイル 428 * @param to 移動先となるファイル 429 */ 430 private static void localMove( final Path from , final Path to ) { 431 try { 432 // synchronized( from ) { 433 // 直前に存在チェックを行います。 434// if( Files.exists( from ) ) { 435 if( exists( from ) ) { // このメソッドの結果がすぐに古くなることに注意してください。 436 // CopyOption に、StandardCopyOption.ATOMIC_MOVE を指定すると、別サーバー等へのMOVEは、出来なくなります。 437 // try{ Thread.sleep( 2000 ); } catch( final InterruptedException ex ){} // 先に、無条件に待ちます。 438 Files.move( from , to , StandardCopyOption.REPLACE_EXISTING ); 439 } 440 // } 441 } 442 catch( final NoSuchFileException ex ) { // 7.2.5.0 (2020/06/01) 443 LOGGER.warning( "MSG0008" , from , to ); // 原因不明:FileWatchとDirWatchの両方が動いているから? 444 } 445 catch( final IOException ex ) { 446 // MSG0008 = ファイルが移動できませんでした。from=[{0}] to=[{1}] 447// MsgUtil.errPrintln( ex , "MSG0008" , from , to ); 448 LOGGER.warning( ex , "MSG0008" , from , to ); 449 } 450 } 451 452 /** 453 * 単体ファイルをバックアップフォルダに移動します。 454 * 455 * これは、#backup( from,to,true,false,sufix ); と同じ処理を実行します。 456 * 457 * 移動先は、フォルダ指定で、ファイル名は存在チェックせずに、必ず変更します。 458 * その際、移動元+サフィックス のファイルを作成します。 459 * ファイルのロックを行います。 460 * 461 * @og.rev 1.0.0 (2016/04/28) 新規追加 462 * 463 * @param from 移動元となるファイル 464 * @param to 移動先となるフォルダ(nullの場合は、移動元と同じフォルダ) 465 * @param sufix バックアップファイル名の後ろに付ける文字列 466 * @return バックアップしたファイルパス。 467 * @throws RuntimeException ファイル操作に失敗した場合 468 * @see #backup( Path , Path , boolean , boolean , String ) 469 */ 470 public static Path backup( final Path from , final Path to , final String sufix ) { 471 return backup( from,to,true,false,sufix ); // sufix を無条件につける為、existsCheck=false で登録 472 } 473 474 /** 475 * 単体ファイルをバックアップフォルダに移動します。 476 * 477 * これは、#backup( from,to,true,true ); と同じ処理を実行します。 478 * 479 * 移動先は、フォルダ指定で、ファイル名は存在チェックの上で、無ければ移動、 480 * あれば、移動元+時間情報 のファイルを作成します。 481 * ファイルのロックを行います。 482 * 移動先を指定しない(=null)場合は、自分自身のフォルダでの、ファイル名変更になります。 483 * 484 * @og.rev 1.0.0 (2016/04/28) 新規追加 485 * 486 * @param from 移動元となるファイル 487 * @param to 移動先となるフォルダ(nullの場合は、移動元と同じフォルダ) 488 * @return バックアップしたファイルパス。 489 * @throws RuntimeException ファイル操作に失敗した場合 490 * @see #backup( Path , Path , boolean , boolean , String ) 491 */ 492 public static Path backup( final Path from , final Path to ) { 493 return backup( from,to,true,true,null ); 494 } 495 496 /** 497 * パスの共有ロックを指定して、単体ファイルをバックアップフォルダに移動します。 498 * 499 * 移動先のファイル名は、existsCheckが、trueの場合は、移動先のファイル名をチェックして、 500 * 存在しなければ、移動元と同じファイル名で、バックアップフォルダに移動します。 501 * 存在すれば、ファイル名+サフィックス のファイルを作成します。(拡張子より後ろにサフィックスを追加します。) 502 * existsCheckが、false の場合は、無条件に、移動元のファイル名に、サフィックスを追加します。 503 * サフィックスがnullの場合は、時間情報になります。 504 * 移動先を指定しない(=null)場合は、自分自身のフォルダでの、ファイル名変更になります。 505 * 506 * @og.rev 1.0.0 (2016/04/28) 新規追加 507 * @og.rev 6.9.8.0 (2018/05/28) FindBugs:null になっている可能性があるメソッドの戻り値を利用している 508 * @og.rev 7.2.1.0 (2020/03/13) ファイル名変更処理の修正 509 * @og.rev 7.2.5.0 (2020/06/01) toパスに、環境変数と日付文字列置換機能を追加します。 510 * 511 * @param from 移動元となるファイル 512 * @param to 移動先となるフォルダ(nullの場合は、移動元と同じフォルダ) 513 * @param useLock パスを共有ロックするかどうか 514 * @param existsCheck 移動先のファイル存在チェックを行うかどうか(true:行う/false:行わない) 515 * @param sufix バックアップファイル名の後ろに付ける文字列 516 * 517 * @return バックアップしたファイルパス。 518 * @throws RuntimeException ファイル操作に失敗した場合 519 * @see #backup( Path , Path ) 520 */ 521 public static Path backup( final Path from , final Path to , final boolean useLock , final boolean existsCheck , final String sufix ) { 522// final Path movePath = to == null ? from.getParent() : to ; 523 Path movePath = to == null ? from.getParent() : to ; 524 525 // 6.9.8.0 (2018/05/28) FindBugs:null になっている可能性があるメソッドの戻り値を利用している 526 if( movePath == null ) { 527 // MSG0007 = ファイル/フォルダの作成に失敗しました。\n\tdir=[{0}] 528 throw MsgUtil.throwException( "MSG0007" , from.toString() ); 529 } 530 531 // 7.2.5.0 (2020/06/01) toパスに、環境変数と日付文字列置換機能を追加します。 532 String toStr = movePath.toString(); 533 // toStr = org.opengion.fukurou.util.StringUtil.replaceText( toStr , "{@ENV." , "}" , System::getenv ); // 環境変数置換 534 // toStr = org.opengion.fukurou.util.StringUtil.replaceText( toStr , "{@DATE." , "}" , StringUtil::getTimeFormat ); // 日付文字列置換 535 toStr = StringUtil.replaceText( toStr ); // 環境変数,日付文字列置換 536 movePath = Paths.get( toStr ); 537 538// final String fileName = from.getFileName().toString(); 539 final Path fName = from.getFileName(); 540 if( fName == null ) { 541 // MSG0002 = ファイル/フォルダが存在しません。\n\tfile=[{0}] 542 throw MsgUtil.throwException( "MSG0002" , from.toString() ); 543 } 544 545// final Path moveFile = movePath.resolve( fileName ); // 移動先のファイルパスを構築 546 final Path moveFile = movePath.resolve( fName ); // 移動先のファイルパスを構築 547 548// final boolean isExChk = existsCheck && Files.notExists( moveFile ); // 存在しない場合、true。存在するか、不明の場合は、false。 549 550 final Path bkupPath; 551// if( isExChk ) { 552 if( existsCheck && Files.notExists( moveFile ) ) { // 存在しない場合、true。存在するか、不明の場合は、false。 553 bkupPath = moveFile; 554 } 555 else { 556 final String fileName = fName.toString(); // from パスの名前 557 final int ad = fileName.lastIndexOf( '.' ); // ピリオドの手前に、タイムスタンプを入れる。 558 // 7.2.1.0 (2020/03/13) ファイル名変更処理の修正 559 if( ad > 0 ) { 560 bkupPath = movePath.resolve( 561 fileName.substring( 0,ad ) 562 + "_" 563 + StringUtil.nval( sufix , StringUtil.getTimeFormat() ) 564 + fileName.substring( ad ) // ad 以降なので、ピリオドも含む 565 ); 566 } 567 else { 568 bkupPath = null; 569 } 570 } 571 572 move( from,bkupPath,useLock ); 573 574 return bkupPath; 575 } 576 577 /** 578 * オリジナルファイルにバックアップファイルの行を追記します。 579 * 580 * オリジナルファイルに、バックアップファイルから読み取った行を追記していきます。 581 * 処理する条件は、オリジナルファイルとバックアップファイルが異なる場合のみ、実行されます。 582 * また、バックアップファイルから、追記する行で、COUNT,TIME,DATE の要素を持つ 583 * 行は、RPTファイルの先頭行なので、除外します。 584 * 585 * @og.rev 7.2.5.0 (2020/06/01) 新規追加。 586 * 587 * @param orgPath 追加されるオリジナルのパス名 588 * @param bkup 行データを取り出すバックアップファイル 589 */ 590 public static void mergeFile( final Path orgPath , final Path bkup ) { 591 if( exists( bkup ) && !bkup.equals( orgPath ) ) { // 追記するバックアップファイルの存在を条件に加える。 592 try { 593 final List<String> lines = FileUtil.readAllLines( bkup ); // 1.4.0 (2019/10/01) 594 // RPT,STS など、書き込み都度ヘッダー行を入れるファイルは、ヘッダー行を削除しておきます。 595 if( lines.size() >= 2 ) { 596 final String first = lines.get(0); // RPTの先頭行で、COUNT,TIME,DATE を持っていれば、その行は削除します。 597 if( first.contains( "COUNT" ) && first.contains( "DATE" ) && first.contains( "TIME" ) ) { lines.remove(0); } 598 } // 先頭行はトークン名 599 // ※ lockSave がうまく動きません。 600 // if( useLock ) { 601 // lockSave( orgPath , lines , true ); 602 // } 603 // else { 604// save( orgPath , lines , true ); 605 save( orgPath , lines , true , UTF_8 ); 606 // } 607 Files.deleteIfExists( bkup ); 608 } 609 catch( final IOException ex ) { 610 // MSG0003 = ファイルがオープン出来ませんでした。file=[{0}] 611 throw MsgUtil.throwException( ex , "MSG0003" , bkup.toAbsolutePath().normalize() ); 612 } 613 } 614 } 615 616 /** 617 * ファイルまたはフォルダ階層を削除します。 618 * 619 * これは、指定のパスが、フォルダの場合、階層すべてを削除します。 620 * 階層の途中にファイル等が存在していたとしても、削除します。 621 * 622 * Files.walkFileTree(Path,FileVisitor) を使用したファイル・ツリーの削除方式です。 623 * 624 * @og.rev 1.0.0 (2016/04/28) 新規追加 625 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 626 * 627 * @param start 削除開始ファイル 628 * @throws RuntimeException ファイル操作に失敗した場合 629 */ 630 public static void delete( final Path start ) { 631 try { 632// if( Files.exists( start ) ) { 633 if( exists( start ) ) { // 7.2.5.0 (2020/06/01) 634 Files.walkFileTree( start, DELETE_VISITOR ); 635 } 636 } 637 catch( final IOException ex ) { 638 // MSG0011 = ファイルが削除できませんでした。file=[{0}] 639 throw MsgUtil.throwException( ex , "MSG0011" , start ); 640 } 641 } 642 643 /** 644 * delete(Path)で使用する、Files.walkFileTree の引数の FileVisitor オブジェクトです。 645 * 646 * staticオブジェクトを作成しておき、使いまわします。 647 */ 648 private static final FileVisitor<Path> DELETE_VISITOR = new SimpleFileVisitor<Path>() { 649 /** 650 * ディレクトリ内のファイルに対して呼び出されます。 651 * 652 * @param file ファイルへの参照 653 * @param attrs ファイルの基本属性 654 * @throws IOException 入出力エラーが発生した場合 655 */ 656 @Override 657 public FileVisitResult visitFile( final Path file, final BasicFileAttributes attrs ) throws IOException { 658 Files.deleteIfExists( file ); // ファイルが存在する場合は削除 659 return FileVisitResult.CONTINUE; 660 } 661 662 /** 663 * ディレクトリ内のエントリ、およびそのすべての子孫がビジットされたあとにそのディレクトリに対して呼び出されます。 664 * 665 * @param dir ディレクトリへの参照 666 * @param ex エラーが発生せずにディレクトリの反復が完了した場合はnull、そうでない場合はディレクトリの反復が早く完了させた入出力例外 667 * @throws IOException 入出力エラーが発生した場合 668 */ 669 @Override 670 public FileVisitResult postVisitDirectory( final Path dir, final IOException ex ) throws IOException { 671 if( ex == null ) { 672 Files.deleteIfExists( dir ); // ファイルが存在する場合は削除 673 return FileVisitResult.CONTINUE; 674 } else { 675 // directory iteration failed 676 throw ex; 677 } 678 } 679 }; 680 681 /** 682 * 指定のパスのファイルが、書き込まれている途中かどうかを判定し、落ち着くまで待ちます。 683 * 684 * FileUtil.stablePath( path , STABLE_SLEEP_TIME , STABLE_RETRY_COUNT ); と同じです。 685 * 686 * @param path チェックするパスオブジェクト 687 * @return true:安定した/false:安定しなかった。またはファイルが存在していない。 688 * @see #STABLE_SLEEP_TIME 689 * @see #STABLE_RETRY_COUNT 690 */ 691 public static boolean stablePath( final Path path ) { 692 return stablePath( path , STABLE_SLEEP_TIME , STABLE_RETRY_COUNT ); 693 } 694 695 /** 696 * 指定のパスのファイルが、書き込まれている途中かどうかを判定し、落ち着くまで待ちます。 697 * 698 * ファイルの安定は、ファイルのサイズをチェックすることで求めます。まず、サイズをチェックし、 699 * sleepで指定した時間だけ、Thread.sleepします。再び、サイズをチェックして、同じであれば、 700 * 安定したとみなします。 701 * なので、必ず、sleep で指定したミリ秒だけは、待ちます。 702 * ファイルが存在しない、サイズが、0のままか、チェック回数を過ぎても安定しない場合は、 703 * false が返ります。 704 * サイズを求める際に、IOExceptionが発生した場合でも、falseを返します。 705 * 706 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 707 * 708 * @param path チェックするパスオブジェクト 709 * @param sleep 待機する時間(ミリ秒) 710 * @param cnt チェックする回数 711 * @return true:安定した/false:安定しなかった。またはファイルが存在していない。 712 */ 713 public static boolean stablePath( final Path path , final long sleep , final int cnt ) { 714 // 存在しない場合は、即抜けます。 715// if( Files.exists( path ) ) { 716 if( exists( path ) ) { // 仮想フォルダなどの場合、実態が存在しないことがある。 717 try{ Thread.sleep( sleep ); } catch( final InterruptedException ex ){} // 先に、無条件に待ちます。 718 try { 719 for( int i=0; i<cnt; i++ ) { 720// if( Files.notExists( path ) ) { return false; } // 存在チェック。無ければ、false 721 if( !exists( path ) ) { break; } // 存在チェック。無ければ、false 722 final long size1 = Files.size( path ); // exit point 警告が出ますが、Thread.sleep 前に、値を取得しておきたい。 723 724 try{ Thread.sleep( sleep ); } catch( final InterruptedException ex ){} // 無条件に待ちます。 725 726// if( Files.notExists( path ) ) { return false; } // 存在チェック。無ければ、false 727 if( !exists( path ) ) { break; } // 存在チェック。無ければ、false 728 final long size2 = Files.size( path ); 729 if( size1 != 0L && size1 == size2 ) { return true; } // 安定した 730 } 731 } 732 catch( final IOException ex ) { 733 // Exception は発生させません。 734 // MSG0005 = フォルダのファイル読み込み時にエラーが発生しました。file=[{0}] 735 MsgUtil.errPrintln( ex , "MSG0005" , path ); 736 } 737 } 738 739 return false; 740 } 741 742 /** 743 * 指定のパスを共有ロックして、Consumer#action(Path) メソッドを実行します。 744 * 共有ロック中は、ファイルを読み込むことは出来ますが、書き込むことは出来なくなります。 745 * 746 * 共有ロックの取得は、{@value #LOCK_RETRY_COUNT} 回実行し、{@value #LOCK_SLEEP_TIME} ミリ秒待機します。 747 * 748 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 749 * 750 * @param inPath 処理対象のPathオブジェクト 751 * @param action パスを引数に取るConsumerオブジェクト 752 * @throws RuntimeException ファイル読み込み時にエラーが発生した場合 753 * @see #forEach(Path,Consumer) 754 * @see #LOCK_RETRY_COUNT 755 * @see #LOCK_SLEEP_TIME 756 */ 757 public static void lockPath( final Path inPath , final Consumer<Path> action ) { 758 // 処理の直前で、処理対象のファイルが存在しているかどうか確認します。 759// if( Files.exists( inPath ) ) { 760 if( exists( inPath ) ) { // 7.2.5.0 (2020/06/01) 761 // try-with-resources 文 (AutoCloseable) 762 try( FileChannel channel = FileChannel.open( inPath, StandardOpenOption.READ ) ) { 763 for( int i=0; i<LOCK_RETRY_COUNT; i++ ) { 764 try { 765 if( channel.tryLock( 0L,Long.MAX_VALUE,true ) != null ) { // 共有ロック獲得成功 766 action.accept( inPath ); 767 return; // 共有ロック獲得成功したので、ループから抜ける。 768 } 769 } 770 // 要求された領域をオーバーラップするロックがこのJava仮想マシンにすでに確保されている場合。 771 // または、このメソッド内でブロックされている別のスレッドが同じファイルのオーバーラップした領域をロックしようとしている場合 772 catch( final OverlappingFileLockException ex ) { 773 // System.err.println( ex.getMessage() ); 774 if( i >= 3 ) { // とりあえず3回までは、何も出さない 775 // MSG0104 = 要求された領域のロックは、このJava仮想マシンにすでに確保されています。 \n\tfile=[{0}] 776 // LOGGER.warning( ex , "MSG0104" , inPath ); 777 LOGGER.warning( "MSG0104" , inPath ); // 1.5.0 (2020/04/01) メッセージだけにしておきます。 778 } 779 } 780 try{ Thread.sleep( LOCK_SLEEP_TIME ); } catch( final InterruptedException ex ){} 781 } 782 } 783 catch( final IOException ex ) { 784 // MSG0005 = フォルダのファイル読み込み時にエラーが発生しました。file=[{0}] 785 throw MsgUtil.throwException( ex , "MSG0005" , inPath ); 786 } 787 788 // Exception は発生させません。 789 // MSG0015 = ファイルのロック取得に失敗しました。file=[{0}] WAIT=[{1}](ms) COUNT=[{2}] 790// MsgUtil.errPrintln( "MSG0015" , inPath , LOCK_SLEEP_TIME , LOCK_RETRY_COUNT ); 791 LOGGER.warning( "MSG0015" , inPath , LOCK_SLEEP_TIME , LOCK_RETRY_COUNT ); 792 } 793 } 794 795 /** 796 * 指定のパスから、1行づつ読み取った結果をConsumerにセットする繰り返しメソッドです。 797 * 1行単位に、Consumer#action が呼ばれます。 798 * このメソッドでは、Charset は、UTF-8 です。 799 * 800 * ファイルを順次読み込むため、内部メモリを圧迫しません。 801 * 802 * @param inPath 処理対象のPathオブジェクト 803 * @param action 行を引数に取るConsumerオブジェクト 804 * @throws RuntimeException ファイル読み込み時にエラーが発生した場合 805 * @see #lockForEach(Path,Consumer) 806 */ 807 public static void forEach( final Path inPath , final Consumer<String> action ) { 808 forEach( inPath , UTF_8 , action ); 809 } 810 811 /** 812 * 指定のパスから、1行づつ読み取った結果をConsumerにセットする繰り返しメソッドです。 813 * 1行単位に、Consumer#action が呼ばれます。 814 * 815 * ファイルを順次読み込むため、内部メモリを圧迫しません。 816 * 817 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 818 * 819 * @param inPath 処理対象のPathオブジェクト 820 * @param chset ファイルを読み取るときのCharset 821 * @param action 行を引数に取るConsumerオブジェクト 822 * @throws RuntimeException ファイル読み込み時にエラーが発生した場合 823 * @see #lockForEach(Path,Consumer) 824 */ 825 public static void forEach( final Path inPath , final Charset chset , final Consumer<String> action ) { 826 // 処理の直前で、処理対象のファイルが存在しているかどうか確認します。 827// if( Files.exists( inPath ) ) { 828 if( exists( inPath ) ) { // 7.2.5.0 (2020/06/01) 829 // try-with-resources 文 (AutoCloseable) 830 String line = null; 831 int no = 0; 832 // // こちらの方法では、lockForEach から来た場合に、エラーになります。 833 // try( BufferedReader reader = Files.newBufferedReader( inPath , chset ) ) { 834 // 万一、コンストラクタでエラーが発生すると、リソース開放されない場合があるため、個別にインスタンスを作成しておきます。(念のため) 835 try( FileInputStream fin = new FileInputStream( inPath.toFile() ); 836 InputStreamReader isr = new InputStreamReader( fin , chset ); 837 BufferedReader reader = new BufferedReader( isr ) ) { 838 839 while( ( line = reader.readLine() ) != null ) { 840 // 1.2.0 (2018/09/01) UTF-8 BOM 対策 841 // UTF-8 の BOM(0xEF 0xBB 0xBF) は、Java内部文字コードの UTF-16 BE では、0xFE 0xFF になる。 842 // ファイルの先頭文字が、feff の場合は、その文字を削除します。 843 // if( no == 0 && !line.isEmpty() && Integer.toHexString(line.charAt(0)).equalsIgnoreCase("feff") ) { 844 if( no == 0 && !line.isEmpty() && (int)line.charAt(0) == (int)'\ufeff' ) { 845 // MSG0105 = 指定のファイルは、UTF-8 BOM付きです。BOM無しファイルで、運用してください。 \n\tfile=[{0}] 846 System.out.println( MsgUtil.getMsg( "MSG0105" , inPath ) ); 847 line = line.substring(1); // BOM の削除 : String#replace("\ufeff","") の方が良い? 848 } 849 850 action.accept( line ); 851 no++; 852 } 853 } 854 catch( final IOException ex ) { 855 // MSG0016 = ファイルの行データ読み込みに失敗しました。file:行番号:行\n\t{0}:{1}: {2} 856 // MSG0016 = ファイルの行データ読み込みに失敗しました。\n\tfile={0} , 行番号:{1} , 行:{2} 857 throw MsgUtil.throwException( ex , "MSG0016" , inPath , no , line ); 858 } 859 } 860 } 861 862 /** 863 * 指定のパスを共有ロックして、1行づつ読み取った結果をConsumerにセットする繰り返しメソッドです。 864 * 1行単位に、Consumer#action が呼ばれます。 865 * 866 * ファイルを順次読み込むため、内部メモリを圧迫しません。 867 * 868 * @param inPath 処理対象のPathオブジェクト 869 * @param action 行を引数に取るConsumerオブジェクト 870 * @see #forEach(Path,Consumer) 871 */ 872 public static void lockForEach( final Path inPath , final Consumer<String> action ) { 873 lockPath( inPath , in -> forEach( in , UTF_8 , action ) ); 874 } 875 876 /** 877 * 指定のパスを共有ロックして、1行づつ読み取った結果をConsumerにセットする繰り返しメソッドです。 878 * 1行単位に、Consumer#action が呼ばれます。 879 * 880 * ファイルを順次読み込むため、内部メモリを圧迫しません。 881 * 882 * @param inPath 処理対象のPathオブジェクト 883 * @param chset エンコードを指定するCharsetオブジェクト 884 * @param action 行を引数に取るConsumerオブジェクト 885 * @see #forEach(Path,Consumer) 886 */ 887 public static void lockForEach( final Path inPath , final Charset chset , final Consumer<String> action ) { 888 lockPath( inPath , in -> forEach( in , chset , action ) ); 889 } 890 891 /** 892 * 指定のパスに1行単位の文字列のListを書き込んでいきます。 893 * 1行単位の文字列のListを作成しますので、大きなファイルの作成には向いていません。 894 * 895 * 書き込むパスの親フォルダがなければ作成します。 896 * 第2引数は、書き込む行データです。 897 * このメソッドでは、Charset は、UTF-8 です。 898 * 899 * @og.rev 1.0.0 (2016/04/28) 新規追加 900 * 901 * @param savePath セーブするパスオブジェクト 902 * @param lines 行単位の書き込むデータ 903 * @throws RuntimeException ファイル操作に失敗した場合 904 * @see #save( Path , List , boolean , Charset ) 905 */ 906 public static void save( final Path savePath , final List<String> lines ) { 907 save( savePath , lines , false , UTF_8 ); // 新規作成 908 } 909 910 /** 911 * 指定のパスに1行単位の文字列のListを書き込んでいきます。 912 * 1行単位の文字列のListを作成しますので、大きなファイルの作成には向いていません。 913 * 914 * 書き込むパスの親フォルダがなければ作成します。 915 * 916 * 第2引数は、書き込む行データです。 917 * 918 * @og.rev 1.0.0 (2016/04/28) 新規追加 919 * @og.rev 7.2.5.0 (2020/06/01) BOM付きファイルを append する場合の対処 920 * 921 * @param savePath セーブするパスオブジェクト 922 * @param lines 行単位の書き込むデータ 923 * @param append trueの場合、ファイルの先頭ではなく最後に書き込まれる。 924 * @param chset ファイルを読み取るときのCharset 925 * @throws RuntimeException ファイル操作に失敗した場合 926 */ 927 public static void save( final Path savePath , final List<String> lines , final boolean append , final Charset chset ) { 928 // 6.9.8.0 (2018/05/28) FindBugs:null になっている可能性があるメソッドの戻り値を利用している 929 // ※ toAbsolutePath() する必要はないのと、getParent() は、null を返すことがある 930// mkdirs( savePath.toAbsolutePath().getParent() ); // savePathはファイルなので、親フォルダを作成する。 931 final Path parent = savePath.getParent(); 932 if( parent == null ) { 933 // MSG0007 = ファイル/フォルダの作成に失敗しました。\n\tdir=[{0}] 934 throw MsgUtil.throwException( "MSG0007" , savePath.toString() ); 935 } 936 else { 937 mkdirs( parent,false ); 938 } 939 940 String line = null; // エラー出力のための変数 941 int no = 0; 942 943 // try-with-resources 文 (AutoCloseable) 944 try( PrintWriter out = new PrintWriter( Files.newBufferedWriter( savePath, chset , append ? APPEND : CREATE ) ) ) { 945 for( final String ln : lines ) { 946// line = ln ; 947 // 7.2.5.0 (2020/06/01) BOM付きファイルを append する場合の対処 948 if( !ln.isEmpty() && (int)ln.charAt(0) == (int)'\ufeff' ) { 949 line = ln.substring(1); // BOM の削除 : String#replace("\ufeff","") の方が良い? 950 } 951 else { 952 line = ln ; 953 } 954 no++; 955 out.println( line ); 956 } 957 out.flush(); 958 } 959 catch( final IOException ex ) { 960 // MSG0017=ファイルのデータ書き込みに失敗しました。file:行番号:行\n\t{0}:{1}: {2} 961 throw MsgUtil.throwException( ex , "MSG0017" , savePath , no , line ); 962 } 963 } 964 965 /** 966 * 指定のパスの最終更新日付を、文字列で返します。 967 * 文字列のフォーマット指定も可能です。 968 * 969 * パスが無い場合や、最終更新日付を、取得できない場合は、現在時刻をベースに返します。 970 * 971 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。 972 * 973 * @param path 処理対象のPathオブジェクト 974 * @param format 文字列化する場合のフォーマット(yyyyMMddHHmmss) 975 * @return 指定のパスの最終更新日付の文字列 976 */ 977 public static String timeStamp( final Path path , final String format ) { 978 long tempTime = 0L; 979 try { 980 // 存在チェックを直前に入れますが、厳密には、非同期なので確率の問題です。 981// if( Files.exists( path ) ) { 982 if( exists( path ) ) { // 7.2.5.0 (2020/06/01) 983 tempTime = Files.getLastModifiedTime( path ).toMillis(); 984 } 985 } 986 catch( final IOException ex ) { 987 // MSG0018 = ファイルのタイムスタンプの取得に失敗しました。file=[{0}] 988// MsgUtil.errPrintln( ex , "MSG0018" , path , ex.getMessage() ); 989 // MSG0018 = ファイルのタイムスタンプの取得に失敗しました。\n\tfile=[{0}] 990 LOGGER.warning( ex , "MSG0018" , path ); 991 } 992 if( tempTime == 0L ) { 993 tempTime = System.currentTimeMillis(); // パスが無い場合や、エラー時は、現在時刻を使用 994 } 995 996 return StringUtil.getTimeFormat( tempTime , format ); 997 } 998 999 /** 1000 * ファイルからすべての行を読み取って、文字列のListとして返します。 1001 * 1002 * java.nio.file.Files#readAllLines(Path ) と同等ですが、ファイルが UTF-8 でない場合 1003 * 即座にエラーにするのではなく、Windows-31J でも読み取りを試みます。 1004 * それでもダメな場合は、IOException をスローします。 1005 * 1006 * @og.rev 7.2.5.0 (2020/06/01) Files.readAllLines の代用 1007 * 1008 * @param path 読み取り対象のPathオブジェクト 1009 * @return Listとしてファイルからの行 1010 * @throws IOException 読み取れない場合エラー 1011 */ 1012 public static List<String> readAllLines( final Path path ) throws IOException { 1013 try { 1014 return Files.readAllLines( path ); // StandardCharsets.UTF_8 指定と同等。 1015 } 1016 catch( final MalformedInputException ex ) { 1017 // MSG0030 = 指定のファイルは、UTF-8でオープン出来なかったため、Windows-31J で再実行します。\n\tfile=[{0}] 1018 LOGGER.warning( "MSG0030" , path ); // Exception は、引数に渡さないでおきます。 1019 1020 return Files.readAllLines( path,WINDOWS_31J ); 1021 } 1022 } 1023 1024 /** 1025 * Pathオブジェクトが存在しているかどうかを判定します。 1026 * 1027 * java.nio.file.Files#exists( Path ) を使用せず、java.io.File.exists() で判定します。 1028 * https://codeday.me/jp/qa/20190302/349168.html 1029 * ネットワークフォルダに存在するファイルの判定において、Files#exists( Path )と 1030 * File.exists() の結果が異なることがあります。 1031 * ここでは、File.exists() を使用して判定します。 1032 * 1033 * @og.rev 7.2.5.0 (2020/06/01) Files.exists の代用 1034 * 1035 * @param path 判定対象のPathオブジェクト 1036 * @return ファイルの存在チェック(あればtrue) 1037 */ 1038 public static boolean exists( final Path path ) { 1039 // return Files.exists( path ); 1040 return path != null && path.toFile().exists(); 1041 } 1042 1043 /** 1044 * Pathオブジェクトのファイル名(getFileName().toString()) を取得します。 1045 * 1046 * Path#getFileName() では、結果が null になる場合もあり、そのままでは、toString() できません。 1047 * また、引数の Path も null チェックが必要なので、それらを簡易的に行います。 1048 * 何らかの結果が、null の場合は、""(空文字列)を返します。 1049 * 1050 * @og.rev 7.2.9.4 (2020/11/20) Path.getFileName().toString() の簡易版 1051 * 1052 * @param path ファイル名取得元のPathオブジェクト(nullも可) 1053 * @return ファイル名(nullの場合は、空文字列) 1054 * @og.rtnNotNull 1055 */ 1056 public static String pathFileName( final Path path ) { 1057 // 対応済み:spotbugs:null になっている可能性があるメソッドの戻り値を利用している 1058 return path == null || path.getFileName() == null ? "" : path.getFileName().toString(); 1059 } 1060}