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