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