001/* 002 * Copyright (c) 2009 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.util; 017 018import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import java.io.BufferedInputStream; 020import java.io.BufferedOutputStream; 021import java.io.BufferedReader; 022import java.io.BufferedWriter; 023import java.io.File; 024import java.io.InputStream; 025import java.io.FileInputStream; 026import java.io.FileNotFoundException; 027import java.io.FileOutputStream; 028import java.io.IOException; 029import java.io.OutputStream; 030import java.io.OutputStreamWriter; 031import java.io.PrintWriter; 032import java.io.UnsupportedEncodingException; 033import java.io.Writer; 034import java.util.Collections; 035import java.util.List; 036 037import java.nio.channels.FileChannel; 038import java.nio.file.Files; // 6.2.0.0 (2015/02/27) 039import java.nio.charset.Charset; // 6.2.0.0 (2015/02/27) 040import org.opengion.fukurou.system.HybsConst; // 6.4.5.2 (2016/05/06) 041 042import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 043import org.opengion.fukurou.system.Closer; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 044import org.opengion.fukurou.system.LogWriter; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 045import org.opengion.fukurou.system.ThrowUtil; // 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system 046 047/** 048 * FileUtil.java は、共通的に使用される File関連メソッドを集約した、クラスです。 049 * 050 * 全変数は、public static final 宣言されており、全メソッドは、public static synchronized 宣言されています。 051 * 052 * @og.group ユーティリティ 053 * 054 * @version 4.0 055 * @author Kazuhiko Hasegawa 056 * @since JDK5.0, 057 */ 058public final class FileUtil { 059 private static final NonClosePrintWriter OUT_WRITER = new NonClosePrintWriter( System.out ); // 6.4.1.1 (2016/01/16) outWriter → OUT_WRITER refactoring 060 private static final NonClosePrintWriter ERR_WRITER = new NonClosePrintWriter( System.err ); // 6.4.1.1 (2016/01/16) errWriter → ERR_WRITER refactoring 061 062 /** 5.6.1.2 (2013/02/22) UNIX系のファイル名を表すセパレータ文字 */ 063 064 /** 5.6.1.2 (2013/02/22) Windwos系のファイル名を表すセパレータ文字 */ 065 066 /** 5.6.1.2 (2013/02/22) ファイルの拡張子の区切りを表す文字 */ 067 public static final char EXTENSION_SEPARATOR = '.'; 068 069 private static final byte B_CR = (byte)0x0d ; // '\r' 070 private static final byte B_LF = (byte)0x0a ; // '\n' 071 private static final int BUFSIZE = 8192 ; // 5.1.6.0 (2010/05/01) 072 073 /** 074 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。 075 * 076 */ 077 private FileUtil() {} 078 079 /** 080 * Fileオブジェクトとエンコードより PrintWriterオブジェクトを作成します。 081 * 082 * @param file 出力するファイルオブジェクト 083 * @param encode ファイルのエンコード 084 * 085 * @return PrintWriterオブジェクト 086 * @throws RuntimeException 何らかのエラーが発生した場合 087 * @og.rtnNotNull 088 */ 089 public static PrintWriter getPrintWriter( final File file,final String encode ) { 090 return getPrintWriter( file,encode,false ); 091 } 092 093 /** 094 * Fileオブジェクトとエンコードより PrintWriterオブジェクトを作成します。 095 * 096 * @param file 出力するファイルオブジェクト 097 * @param encode ファイルのエンコード 098 * @param append ファイルを追加モード(true)にするかどうか 099 * 100 * @return PrintWriterオブジェクト 101 * @throws RuntimeException 何らかのエラーが発生した場合 102 * @og.rtnNotNull 103 */ 104 public static PrintWriter getPrintWriter( final File file,final String encode,final boolean append ) { 105 final PrintWriter writer ; 106 107 try { 108 writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter( 109 new FileOutputStream(file,append) ,encode ))); 110 } 111 catch( final UnsupportedEncodingException ex ) { 112 final String errMsg = "指定されたエンコーディングがサポートされていません。" + CR 113 + ex.getMessage() + CR 114 + "File=[" + file + " , encode=[" + encode + "]" ; 115 throw new OgRuntimeException( errMsg,ex ); 116 } 117 catch( final FileNotFoundException ex ) { // 3.6.1.0 (2005/01/05) 118 final String errMsg = "ファイル名がオープン出来ませんでした。" + CR 119 + ex.getMessage() + CR 120 + "File=[" + file + " , encode=[" + encode + "]" ; 121 throw new OgRuntimeException( errMsg,ex ); 122 } 123 124 return writer ; 125 } 126 127 /** 128 * ファイル名より、PrintWriterオブジェクトを作成する簡易メソッドです。 129 * 130 * これは、ファイル名は、フルパスで、追加モードで、UTF-8 エンコードの 131 * ログファイルを出力する場合に使用します。 132 * また、ファイル名に、"System.out" と、"System.err" を指定できます。 133 * その場合は、標準出力、または、標準エラー出力に出力されます。 134 * "System.out" と、"System.err" を指定した場合は、NonClosePrintWriter 135 * オブジェクトが返されます。これは、close() 処理が呼ばれても、何もしない 136 * クラスです。また、常に内部キャッシュの同じオブジェクトが返されます。 137 * 138 * @param file 出力するファイル名 139 * 140 * @return PrintWriterオブジェクト 141 * @throws RuntimeException 何らかのエラーが発生した場合 142 * @throws IllegalArgumentException ファイル名が null の場合 143 */ 144 public static PrintWriter getLogWriter( final String file ) { 145 if( file == null ) { 146 final String errMsg = "ファイル名に、null は指定できません。"; 147 throw new IllegalArgumentException( errMsg ); 148 } 149 150 final PrintWriter writer ; 151 if( "System.out".equalsIgnoreCase( file ) ) { 152 writer = OUT_WRITER ; 153 } 154 else if( "System.err".equalsIgnoreCase( file ) ) { 155 writer = ERR_WRITER ; 156 } 157 else { 158 writer = getPrintWriter( new File( file ),"UTF-8",true ); 159 } 160 161 return writer ; 162 } 163 164 /** 165 * OutputStreamとエンコードより PrintWriterオブジェクトを作成します。 166 * 167 * @og.rev 5.5.2.0 (2012/05/01) 新規追加 168 * 169 * @param os 利用するOutputStream 170 * @param encode ファイルのエンコード 171 * 172 * @return PrintWriterオブジェクト 173 * @throws RuntimeException 何らかのエラーが発生した場合 174 */ 175 public static PrintWriter getPrintWriter( final OutputStream os,final String encode ) { 176 final PrintWriter writer ; 177 178 try { 179 writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter( os ,encode ))); 180 } 181 catch( final UnsupportedEncodingException ex ) { 182 final String errMsg = "指定されたエンコーディングがサポートされていません。" + CR 183 + ex.getMessage() + CR 184 + "encode=[" + encode + "]" ; 185 throw new OgRuntimeException( errMsg,ex ); 186 } 187 return writer ; 188 } 189 190 /** 191 * PrintWriter を継承した、JspWriterなどの Writer 用のクラスを定義します。 192 * 193 * 例えば、JspWriterなどの JSP/Servlet等のフレームワークで使用される 194 * Writer では、flush や close 処理は、フレームワーク内で行われます。 195 * その場合、通常のファイルと同じ用に、flush や close をアプリケーション側で 196 * 行うと、内部処理的に不整合が発生したり、最悪の場合エラーになります。 197 * このクラスは、NonFlushPrintWriter クラスのオブジェクトを返します。 198 * これは、通常の、new PrintWriter( Writer ) で、求めるのと、ほとんど同様の 199 * 処理を行いますが、close() と flush() メソッドが呼ばれても、何もしません。 200 * 201 * @param writer 出力するWriteオブジェクト(NonFlushPrintWriterクラス) 202 * 203 * @return PrintWriterオブジェクト 204 * @og.rtnNotNull 205 */ 206 public static PrintWriter getNonFlushPrintWriter( final Writer writer ) { 207 return new NonFlushPrintWriter( writer ); 208 } 209 210 /** 211 * Fileオブジェクトとエンコードより BufferedReaderオブジェクトを作成します。 212 * 213 * これは、java 1.7 以降でしか使えませんが、FilesとPaths を使用した BufferedReader 214 * オブジェクトを返します。 215 * encode は、java.nio.charset.Charset になる為、従来のコードと異なるかも知れませんが、 216 * 日本語関係の判定をより正確に行う事が可能になります。(Windows-31J と UTF-8の判別など) 217 * 218 * @og.rev 6.2.0.0 (2015/02/27) java.nio.file.Files と、Paths を使用するように変更 219 * 220 * @param file 入力するファイルオブジェクト 221 * @param encode ファイルのエンコード(java.nio.charset.Charset) 222 * 223 * @return BufferedReaderオブジェクト 224 * @throws RuntimeException 何らかのエラーが発生した場合 225 * @og.rtnNotNull 226 */ 227 public static BufferedReader getBufferedReader( final File file,final String encode ) { 228 final BufferedReader reader ; 229 230 try { 231 reader = Files.newBufferedReader( file.toPath() , Charset.forName( encode ) ); 232 } 233 catch( final IOException ex ) { 234 final String errMsg = "ファイルのオープン中に入出力エラーが発生しました。" + CR 235 + ex.getMessage() + CR 236 + "File=[" + file + "] , encode=[" + encode + "]" ; 237 throw new OgRuntimeException( errMsg,ex ); 238 } 239 catch( final RuntimeException ex ) { 240 final String errMsg = "指定された文字セットが不正か、現在のJava仮想マシンでは利用できません。" + CR 241 + ex.getMessage() + CR 242 + "File=[" + file + "] , encode=[" + encode + "]" ; 243 throw new OgRuntimeException( errMsg,ex ); 244 } 245 246 return reader ; 247 } 248 249 /** 250 * 指定のファイル名が、実際に存在しているかどうかをチェックします。 251 * 存在しない場合は、2秒毎に、3回確認します。 252 * それでも存在しない場合は、エラーを返します。 253 * return されるFileオブジェクトは、正規の形式(CanonicalFile)です。 254 * 255 * @param dir フォルダ名 256 * @param filename ファイル名 257 * 258 * @return 存在チェック(なければ null/あれば、CanonicalFile) 259 */ 260 public static File checkFile( final String dir, final String filename ) { 261 return checkFile( dir,filename,3 ); 262 } 263 264 /** 265 * 指定のファイル名が、実際に存在しているかどうかをチェックします。 266 * 存在しない場合は、2秒毎に、指定の回数分確認します。 267 * それでも存在しない場合は、エラーを返します。 268 * return されるFileオブジェクトは、正規の形式(CanonicalFile)です。 269 * 270 * @param dir フォルダ名 271 * @param filename ファイル名 272 * @param count 回数指定 273 * 274 * @return 存在チェック(なければ null/あれば、CanonicalFile) 275 */ 276 public static File checkFile( final String dir, final String filename,final int count ) { 277 File file = null; 278 279 int cnt = count; 280 while( cnt > 0 ) { 281 file = new File( dir,filename ); 282 if( file.exists() ) { break; } 283 else { 284 if( cnt == 1 ) { return null; } // 残り1回の場合は、2秒待機せずに即抜ける。 285 try { Thread.sleep( 2000 ); } // 2秒待機 286 catch( final InterruptedException ex ) { 287 System.out.println( "InterruptedException" ); 288 } 289 System.out.println(); 290 System.out.print( "CHECK File Error! CNT=" + cnt ); 291 System.out.print( " File=" + file.getAbsolutePath() ); 292 } 293 cnt--; 294 } 295 296 // ファイルの正式パス名の取得 297 try { 298 return file.getCanonicalFile() ; 299 } 300 catch( final IOException ex ) { 301 final String errMsg = "ファイルの正式パス名が取得できません。[" + file.getAbsolutePath() + "]"; 302 throw new OgRuntimeException( errMsg,ex ); 303 } 304 } 305 306 /** 307 * ファイルのバイナリコピーを行います。 308 * 309 * copy( File,File,false ) を呼び出します。 310 * 311 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。 312 * 313 * @param fromFile コピー元ファイル名 314 * @param toFile コピー先ファイル名 315 * 316 * @return バイナリコピーが正常に終了したかどうか[true:成功/false:失敗] 317 * @see #copy( File,File,boolean ) 318 */ 319 public static boolean copy( final String fromFile,final String toFile ) { 320 return copy( new File( fromFile ), new File( toFile ), false ); 321 } 322 323 /** 324 * ファイルのバイナリコピーを行います。 325 * 326 * copy( File,File,boolean ) を呼び出します。 327 * 第3引数の、keepTimeStamp=true で、コピー元のファイルのタイムスタンプを、 328 * コピー先にもセットします。 329 * 330 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。 331 * 332 * @param fromFile コピー元ファイル名 333 * @param toFile コピー先ファイル名 334 * @param keepTimeStamp タイムスタンプ維持[true/false] 335 * 336 * @return バイナリコピーが正常に終了したかどうか[true:成功/false:失敗] 337 * @see #copy( File,File,boolean ) 338 */ 339 public static boolean copy( final String fromFile,final String toFile,final boolean keepTimeStamp ) { 340 return copy( new File( fromFile ), new File( toFile ), keepTimeStamp ); 341 } 342 343 /** 344 * ファイルのバイナリコピーを行います。 345 * 346 * copy( File,File,false ) を呼び出します。 347 * 348 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。 349 * 350 * @param fromFile コピー元ファイル 351 * @param toFile コピー先ファイル 352 * 353 * @return バイナリコピーが正常に終了したかどうか[true:成功/false:失敗] 354 * @see #copy( File,File,boolean ) 355 */ 356 public static boolean copy( final File fromFile,final File toFile ) { 357 return copy( fromFile, toFile, false ); 358 } 359 360 /** 361 * ファイルのバイナリコピーを行います。 362 * 363 * 第3引数の、keepTimeStamp=true で、コピー元のファイルのタイムスタンプを、 364 * コピー先にもセットします。 365 * toFile が、ディレクトリの場合は、fromFile のファイル名をそのままコピーします。 366 * fromFile がディレクトリの場合は、copyDirectry( File,Fileboolean )を call します。 367 * 368 * @og.rev 5.1.6.0 (2010/05/01) 新規追加 369 * @og.rev 5.6.5.2 (2013/06/21) ByteBufferを利用した方式から、transferTo を使用する方式に変更 370 * @og.rev 5.7.1.2 (2013/12/20) copy先(toFile)のフォルダが存在しなければ、作成します。 371 * @og.rev 6.3.6.1 (2015/08/28) copy元(fromFile)がフォルダがディレクトリの場合は、#copyDirectry( File,File,boolean ) を呼ぶ。 372 * @og.rev 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。 373 * @og.rev 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 374 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。 375 * 376 * @param fromFile コピー元ファイル 377 * @param toFile コピー先ファイル 378 * @param keepTimeStamp タイムスタンプ維持[true/false] 379 * 380 * @return バイナリコピーが正常に終了したかどうか[true:成功/false:失敗] 381 * @see #copyDirectry( File,File,boolean ) 382 */ 383 public static boolean copy( final File fromFile,final File toFile,final boolean keepTimeStamp ) { 384 FileInputStream inFile = null; 385 FileOutputStream outFile = null; 386 FileChannel fin = null; 387 FileChannel fout = null; 388 389 File tempToFile = toFile ; 390 try { 391 // fromFileが、ディレクトリの場合は、エラー 392 if( fromFile.isDirectory() ) { 393 // 6.3.6.1 (2015/08/28) 394 return copyDirectry( fromFile,toFile,keepTimeStamp ); 395 } 396 // toFileが、ディレクトリの場合は、そのパスでファイル名をfromFileから取り出す。 397 if( toFile.isDirectory() ) { 398 tempToFile = new File( toFile,fromFile.getName() ); 399 } 400 401 // 5.7.1.2 (2013/12/20) copy先(toFile)のフォルダが存在しなければ、作成します。 402 final File parent = tempToFile.getParentFile(); 403 if( !parent.exists() && !parent.mkdirs() ) { 404 // ディレクトリを作成する 405 System.err.println( parent + " の ディレクトリ作成に失敗しました。" ); 406 return false; 407 } 408 409 inFile = new FileInputStream( fromFile ); 410 outFile = new FileOutputStream( tempToFile ); 411 412 fin = inFile.getChannel(); 413 fout = outFile.getChannel(); 414 415 // 5.6.5.2 (2013/06/21) ByteBufferを利用した方式から、transferTo を使用する方式に変更 416 417 fin.transferTo(0, fin.size(), fout ); 418 } 419 catch( final IOException ex ) { 420 // 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。 421 final String errMsg = "バイナリコピーで、エラーが発生しました。" + CR 422 + "fromFile=[" + fromFile + "]" + CR 423 + "toFile =[" + toFile + "]" + CR ; 424 // 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 425 System.out.println( ThrowUtil.ogThrowMsg( errMsg , ex ) ); // 6.4.2.0 (2016/01/29) 426 return false; 427 } 428 finally { 429 Closer.ioClose( inFile ) ; 430 Closer.ioClose( outFile ); 431 Closer.ioClose( fin ) ; 432 Closer.ioClose( fout ); 433 } 434 435 if( keepTimeStamp ) { 436 return tempToFile.setLastModified( fromFile.lastModified() ); 437 } 438 439 return true; 440 } 441 442 /** 443 * ファイルのバイナリコピーを行います。 444 * 445 * このファイルコピーは、バイナリファイルの 改行コードを 446 * CR+LF に統一します。また、UTF-8 の BOM(0xef,0xbb,0xbf) があれば、 447 * 取り除きます。 448 * 449 * @og.rev 5.1.6.0 (2010/05/01) 新規追加 450 * @og.rev 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。 451 * @og.rev 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 452 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。 453 * 454 * @param fromFile コピー元ファイル 455 * @param toFile コピー先ファイル 456 * 457 * @return バイナリコピーが正常に終了したかどうか[true:成功/false:失敗] 458 */ 459 public static boolean changeCrLfcopy( final File fromFile,final File toFile ) { 460 BufferedInputStream fromStream = null; 461 BufferedOutputStream toStream = null; 462 File tempToFile = toFile ; 463 try { 464 // ディレクトリの場合は、そのパスでファイル名をfromFileから取り出す。 465 if( toFile.isDirectory() ) { 466 tempToFile = new File( toFile,fromFile.getName() ); 467 } 468 fromStream = new BufferedInputStream( new FileInputStream( fromFile ) ); 469 toStream = new BufferedOutputStream( new FileOutputStream( tempToFile ) ); 470 471 final byte[] buf = new byte[BUFSIZE]; 472 int len ; 473 // 4.2.3.0 (2008/05/26) changeCrLf 属性対応 474 475 boolean bomCheck = true; // 最初の一回だけ、BOMチェックを行う。 476 byte bt = (byte)0x00; // バッファの最後と最初の比較時に使用 477 while( (len = fromStream.read(buf,0,BUFSIZE)) != -1 ) { 478 int st = 0; 479 if( bomCheck && len >= 3 && 480 buf[0] == (byte)0xef && 481 buf[1] == (byte)0xbb && 482 buf[2] == (byte)0xbf ) { 483 st = 3; 484 } 485 else { 486 // バッファの最後が CR で、先頭が LF の場合、LF をパスします。 487 if( bt == B_CR && buf[0] == B_LF ) { 488 st = 1 ; 489 } 490 } 491 bomCheck = false; 492 493 for( int i=st;i<len;i++ ) { 494 bt = buf[i] ; 495 if( bt == B_CR || bt == B_LF ) { 496 toStream.write( (int)B_CR ); // CR 497 toStream.write( (int)B_LF ); // LF 498 // CR+LF の場合 499 if( bt == B_CR && i+1 < len && buf[i+1] == B_LF ) { 500 i++; 501 bt = buf[i] ; 502 } 503 } 504 else { 505 toStream.write( (int)bt ); 506 } 507 } 508 } 509 // 最後が改行コードでなければ、改行コードを追加します。 510 // テキストコピーとの互換性のため 511 if( bt != B_CR && bt != B_LF ) { 512 toStream.write( (int)B_CR ); // CR 513 toStream.write( (int)B_LF ); // LF 514 } 515 } 516 catch( final IOException ex ) { 517 // 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。 518 final String errMsg = "バイナリコピー(CrLf)で、エラーが発生しました。" + CR 519 + "fromFile=[" + fromFile + "]" + CR 520 + "toFile =[" + toFile + "]" + CR ; 521 // 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 522 System.out.println( ThrowUtil.ogThrowMsg( errMsg , ex ) ); // 6.4.2.0 (2016/01/29) 523 return false; 524 } 525 finally { 526 Closer.ioClose( fromStream ) ; 527 Closer.ioClose( toStream ) ; 528 } 529 530 return true; 531 } 532 533 /** 534 * ファイルのバイナリコピーを行います。 535 * 536 * コピー元のファイルは、InputStream で指定します。 537 * 元は、jsp/common 以下を圧縮、jar化したため、物理ファイルの取得が 538 * できなくなったため、ServletContext#getServletContext() で、ローカルリソースを 539 * 取得するのが目的です。汎用的に、入力は、InputStream にしました。 540 * URLConnection 等で、取得する場合は、BASIC認証も考慮する必要がありますので、 541 * ご注意ください。 542 * タイムスタンプのコピーは行いません。 543 * 544 * @og.rev 6.3.6.1 (2015/08/28) InputStreamで指定されたファイルのコピー 545 * @og.rev 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 546 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。 547 * 548 * @param inStrm コピー元のInputStream(この中でcloseします) 549 * @param toFile コピー先ファイル 550 * 551 * @return バイナリコピーが正常に終了したかどうか[true:成功/false:失敗] 552 * @see #copy( File,File ) 553 */ 554 public static boolean copy( final InputStream inStrm,final File toFile ) { 555 FileOutputStream foStrm = null; 556 try { 557 // copy先(toFile)のフォルダが存在しなければ、作成します。 558 final File parent = toFile.getParentFile(); 559 if( !parent.exists() && !parent.mkdirs() ) { 560 // ディレクトリを作成する 561 System.err.println( parent + " の ディレクトリ作成に失敗しました。" ); 562 return false; 563 } 564 565 foStrm = new FileOutputStream( toFile, false ); 566 return copy( inStrm , foStrm ); 567 } 568 catch( final IOException ex ) { 569 // 6.3.6.1 (2015/08/28) Exception発生時に return ではなく、StringUtil.ogErrMsg に変更。 570 final String errMsg = "入力ストリームのコピーでエラーが発生しました。" + CR 571 + "toFile =[" + toFile + "]" + CR ; 572 // 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 573 System.out.println( ThrowUtil.ogThrowMsg( errMsg , ex ) ); // 6.4.2.0 (2016/01/29) 574 } 575 finally { 576 Closer.ioClose( inStrm ); 577 Closer.ioClose( foStrm ); 578 } 579 580 return false ; 581 } 582 583 /** 584 * 入出力ストリーム間でデータの転送を行います。 585 * 586 * ここでは、すでに作成されたストリームに基づき、データの入出力を行います。 587 * よって、先にフォルダ作成や、存在チェック、ファイルの削除などの必要な処理は 588 * 済まして置いてください。 589 * また、このメソッド内で、ストリームのクロース処理は行っていません。 590 * 591 * @og.rev 5.1.6.0 (2010/05/01) 新規追加 592 * @og.rev 6.3.6.1 (2015/08/28) エラー時のメッセージ情報を増やします。 593 * @og.rev 6.3.8.5 (2015/10/16) StringUtil#ogErrMsgPrint 使用。 594 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogErrMsgPrint(String,Throwable) を、ThrowUtilt#ogThrowMsg(String,Throwable) に変更。 595 * 596 * @param input 入力ストリーム 597 * @param output 出力ストリーム 598 * 599 * @return データ転送が正常に終了したかどうか[true:成功/false:失敗] 600 */ 601 public static boolean copy( final InputStream input,final OutputStream output ) { 602 if( input == null ) { 603 final String errMsg = "入力ストリームが 作成されていません。" ; 604 // 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 605 System.out.println( ThrowUtil.ogThrowMsg( errMsg ) ); // 6.4.2.0 (2016/01/29) 606 return false; 607 } 608 609 if( output == null ) { 610 final String errMsg = "出力ストリームが 作成されていません。" ; 611 // 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 612 System.out.println( ThrowUtil.ogThrowMsg( errMsg ) ); // 6.4.2.0 (2016/01/29) 613 return false; 614 } 615 616 try { 617 final byte[] buf = new byte[BUFSIZE]; 618 int len; 619 while((len = input.read(buf)) != -1) { 620 output.write(buf, 0, len); 621 } 622 } 623 catch( final IOException ex ) { 624 final String errMsg = "ストリームデータの入出力処理に失敗しました。"; 625 // 6.3.8.5 (2015/10/16) StringUtil.ogErrMsgPrint 使用。 626 System.out.println( ThrowUtil.ogThrowMsg( errMsg,ex ) ); // 6.4.2.0 (2016/01/29) 627 return false; 628 } 629 // finally { 630 // Closer.ioClose( input ); 631 // Closer.ioClose( output ); 632 // } 633 return true ; 634 } 635 636 /** 637 * 再帰処理でディレクトリのコピーを行います。 638 * 639 * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは falseを返します。 640 * 641 * @og.rev 4.3.0.0 (2008/07/24) 追加 642 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。 643 * 644 * @param fromDir コピー元ディレクトリ名 645 * @param toDir コピー先ディレクトリ名 646 * 647 * @return ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗] 648 */ 649 public static boolean copyDirectry( final String fromDir, final String toDir ) { 650 return copyDirectry( new File( fromDir ), new File( toDir ),false ); 651 } 652 653 /** 654 * 再帰処理でディレクトリをコピーします。 655 * 656 * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは falseを返します。 657 * 658 * @og.rev 4.3.0.0 (2008/07/24) 追加 659 * @og.rev 5.1.6.0 (2010/05/01) 内部処理を若干変更します。 660 * 661 * @param fromDir コピー元ディレクトリ 662 * @param toDir コピー先ディレクトリ 663 * 664 * @return ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗] 665 */ 666 public static boolean copyDirectry( final File fromDir, final File toDir ) { 667 return copyDirectry( fromDir, toDir, false ); 668 } 669 670 /** 671 * 再帰処理でディレクトリをコピーします。 672 * 673 * 指定されたコピー元ディレクトリがディレクトリでなかったり存在しないときは falseを返します。 674 * 675 * @og.rev 4.3.0.0 (2008/07/24) 追加 676 * @og.rev 5.1.6.0 (2010/05/01) 内部処理を若干変更します。 677 * @og.rev 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。 678 * 679 * @param fromDir コピー元ディレクトリ 680 * @param toDir コピー先ディレクトリ 681 * @param keepTimeStamp タイムスタンプ維持[true/false] 682 * 683 * @return ディレクトリのコピーが正常に終了したかどうか[true:成功/false:失敗] 684 */ 685 public static boolean copyDirectry( final File fromDir, final File toDir, final boolean keepTimeStamp ) { 686 // コピー元がディレクトリでない場合はfalseを返す 687 // 4.3.4.4 (2009/01/01) 688 if( !fromDir.exists() || !fromDir.isDirectory() ) { 689 System.err.println( fromDir + " が ディレクトリでないか、存在しません。" ); 690 return false; 691 } 692 693 // 4.3.4.4 (2009/01/01) ディレクトリを作成する 694 // 6.0.0.1 (2014/04/25) These nested if statements could be combined 695 if( !toDir.exists() && !toDir.mkdirs() ) { 696 System.err.println( toDir + " の ディレクトリ作成に失敗しました。" ); 697 return false; 698 } 699 700 // ディレクトリ内のファイルをすべて取得する 701 final File[] files = fromDir.listFiles(); 702 703 // 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラー 704 if( files == null ) { 705 System.err.println( fromDir + " はアクセスできません。" ); 706 return false; 707 } 708 709 // ディレクトリ内のファイルに対しコピー処理を行う 710 boolean flag = true; 711 for( int i=0; files.length>i; i++ ){ 712 if( files[i].isDirectory() ){ // ディレクトリだった場合は再帰呼び出しを行う 713 flag = copyDirectry( files[i], new File( toDir, files[i].getName()),keepTimeStamp ); 714 } 715 else{ // ファイルだった場合はファイルコピー処理を行う 716 flag = copy( files[i], new File( toDir, files[i].getName()),keepTimeStamp ); 717 } 718 if( !flag ) { return false; } 719 } 720 return true; 721 } 722 723 /** 724 * 指定されたファイル及びディレクトを削除します。 725 * ディレクトリの場合はサブフォルダ及びファイルも削除します。 726 * 1つでもファイルの削除に失敗した場合、その時点で処理を中断しfalseを返します。 727 * 728 * @og.rev 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラーを返します。 729 * 730 * @param file 削除ファイル/ディレクトリ 731 * 732 * @return ファイル/ディレクトリの削除に終了したかどうか[true:成功/false:失敗] 733 */ 734 public static boolean deleteFiles( final File file ) { 735 if( file.exists() ) { 736 if( file.isDirectory() ) { 737 final File[] list = file.listFiles(); 738 739 // 5.3.7.0 (2011/07/01) フォルダにアクセスできない場合は、エラー 740 if( list == null ) { 741 System.err.println( file + " はアクセスできません。" ); 742 return false; 743 } 744 745 for( int i=0; i<list.length; i++ ) { 746 deleteFiles( list[i] ); 747 } 748 } 749 if( !file.delete() ) { return false; } 750 } 751 return true; 752 } 753 754 /** 755 * 指定されたディレクトリを基点としたファイル名(パスを含む)の一覧を返します。 756 * 757 * @og.rev 4.3.6.6 (2009/05/15) 新規作成 758 * @og.rev 5.4.3.2 (2012/01/06) 引数isCopy追加 759 * @og.rev 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。 760 * 761 * @param dir 基点となるディレクトリ 762 * @param sort ファイル名でソートするか 763 * @param list ファイル名一覧を格納するList 764 * @param isCopy コピー中ファイルを除外するか [true:含む/false:除外] 765 */ 766 public static void getFileList( final File dir, final boolean sort, final List<String> list, final boolean isCopy ) { 767 if( list == null ) { return; } 768 if( dir.isFile() ) { 769 // コピー中判定はrenameで行う 770 // 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..; 771 if( isCopy || dir.renameTo( dir ) ) { 772 list.add( dir.getAbsolutePath() ); 773 } 774 else{ 775 return; 776 } 777 } 778 else if( dir.isDirectory() ) { 779 final File[] files = dir.listFiles(); 780 // 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値のnullチェックを追加。 781 if( files != null ) { 782 for( int i=0; i<files.length; i++ ) { 783 getFileList( files[i], sort, list, isCopy ); 784 } 785 } 786 } 787 if( sort ) { 788 Collections.sort( list ); 789 } 790 } 791 792 /** 793 * 指定されたディレクトリを基点としたファイル名(パスを含む)の一覧を返します。 794 * 互換性のため、コピー中ファイルも含みます。 795 * 796 * @og.rev 5.4.3.2 (2012/01/06) コピー中対応のため引数4つを作成する 797 * 798 * @param dir 基点となるディレクトリ 799 * @param sort ファイル名でソートするか 800 * @param list ファイル名一覧を格納するList 801 */ 802 public static void getFileList( final File dir, final boolean sort, final List<String> list ) { 803 getFileList( dir, sort, list, true ); 804 } 805 806 /** 807 * ファイルをリネームを行います。 808 * 引数のuseBackup属性を true にすると、toFile が存在した場合、toFile の直下に "_backup" フォルダを 809 * 作成して、toFile + "_" + (現在時刻のLONG値) + "." + (toFileの拡張子) に名前変更します。 810 * useBackup属性を rename にすると、toFile が存在した場合、toFile に、"_001" からなる 811 * 連番を付与し、重複しなくなるまで、名前を変更します。 812 * useBackup属性を false(またはnull) にすると、toFile が存在した場合、toFile を削除します。 813 * 814 * 戻り値は、変更後のファイルオブジェクトです。 815 * 816 * @og.rev 5.7.1.2 (2013/12/20) 新規追加 817 * @og.rev 6.0.2.4 (2014/10/17) useBackup の機能追加 818 * @og.rev 6.2.0.0 (2015/02/27) FileInfoクラスを使用。 (#getFileSplit(File)の結果配列は廃止) 819 * 820 * @param fromFile 名前変更する元のファイル 821 * @param toFile 名前変更後のファイル 822 * @param useBackup 置き換えファイルの処理方法(true:backupフォルダ/false:しない/rename:重複しない連番) 823 * @return 名前変更後のファイル 824 * @throws RuntimeException 名称変更処理ができなかった場合。 825 */ 826 public static File renameTo( final File fromFile , final File toFile , final String useBackup ) { 827 if( fromFile == null || toFile == null ) { 828 final String errMsg = "入力ファイルが null です。from=[" + fromFile + "] , to=[" + toFile + "]" ; 829 throw new OgRuntimeException( errMsg ); 830 } 831 832 final File parent = toFile.getParentFile(); // 6.0.2.4 (2014/10/17) toFile のフォルダがなければ作成 833 if( !parent.exists() && !parent.mkdirs() ) { 834 final String errMsg = "toファイルのフォルダが作成できません。from=[" + fromFile + "] , to=[" + toFile + "]" ; 835 throw new OgRuntimeException( errMsg ); 836 } 837 838 // 変更先のファイルが存在した場合の処理。 839 File newFile = toFile; // useBackup = "rename" の時のみ書き換えたいので。 840 if( toFile.exists() ) { 841 final FileInfo info = new FileInfo( toFile ); // 6.2.0.0 (2015/02/27) 842 // バックアップ作成する場合 843 // 6.0.2.4 (2014/10/17) useBackup は、文字列で、true/false,(null)/rename がある。 844 if( "true".equalsIgnoreCase( useBackup ) ) { 845 final File backup = new File( parent , "_backup" ); // その直下に、"_backup" フォルダを作成 846 if( !backup.exists() && !backup.mkdirs() ) { 847 final String errMsg = "バックアップ処理でbackupフォルダの作成に失敗しました。[" + backup + "]"; 848 throw new OgRuntimeException( errMsg ); 849 } 850 // バックアップファイル名は、元のファイル名(拡張子含む) + "_" + 現在時刻のlong値 + "." + 元のファイルの拡張子 851 final String toName = toFile.getName(); 852 final File toFile2 = new File( parent,toName ); // オリジナルの toFile をrename するとまずいので、同名のFileオブジェクトを作成 853 854 final String bkupName = info.NAME + "_" + System.currentTimeMillis() + "." + info.SUFIX ; // 6.2.0.0 (2015/02/27) 855 final File bkupFile = new File( backup,bkupName ); 856 857 if( !toFile2.renameTo( bkupFile ) ) { 858 final String errMsg = "バックアップ処理でバックアップファイルをリネームできませんでした。" +CR 859 + " [" + toFile + "] ⇒ [" + bkupFile + "]" ; 860 throw new OgRuntimeException( errMsg ); 861 } 862 } 863 // 他と違い、toFile を変更する必要がある。 864 else if( "rename".equalsIgnoreCase( useBackup ) ) { 865 for( int i=1000; i<2000; i++ ) { // 000 の3桁を取得したいため。 866 final String no = String.valueOf( i ).substring(1); 867 // 6.2.0.0 (2015/02/27) 配列ではなく、FileInfoクラスを使用 868 final File toFile2 = new File( info.DIR , info.NAME + "_" + no + "." + info.SUFIX ); 869 if( !toFile2.exists() ) { 870 newFile = toFile2; 871 break; 872 } 873 } 874 } 875 // バックアップ作成しない場合は、削除します。 876 else if( !toFile.delete() ) { 877 final String errMsg = "既存のファイル[" + toFile + "]が削除できませんでした。"; 878 throw new OgRuntimeException( errMsg ); 879 } 880 } 881 882 if( !fromFile.renameTo( newFile ) ) { 883 final String errMsg = "所定のファイルをリネームできませんでした。" + CR 884 + " [" + fromFile + "] ⇒ [" + newFile + "]" ; 885 throw new OgRuntimeException( errMsg ); 886 } 887 return newFile; 888 } 889 890 /** 891 * ファイルを読み取って、文字列を作成します。 892 * 893 * データの読取が完全に出来なかったときには、途中までのデータを返します。 894 * 指定のエンコードが存在しない場合や、ファイルが存在しない場合は、 895 * OgRuntimeException を throw します。 896 * encode が null の場合は、UTF-8 で読み込みます。 897 * 898 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更 899 * @og.rev 6.4.5.1 (2016/04/28) encode は初期化しているため、null はセットされません。 900 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。 901 * 902 * @param filename ファイル名 903 * @param encode エンコード名 904 * @return ファイルを読み取った文字列 905 * @throws RuntimeException 指定のエンコードが存在しなかったとき。 906 */ 907 public static String getValue( final String filename , final String encode ) { 908 if( filename == null ) { 909 final String errMsg = "ファイル名が指定されていません。" ; 910 throw new OgRuntimeException( errMsg ); 911 } 912 913 final String enc = encode == null ? HybsConst.UTF_8 : encode ; 914 915 try { 916 return new String( Files.readAllBytes( new File( filename ).toPath() ),enc ); 917 } 918 catch( final IOException ex ) { 919 final String errMsg = "ファイル名がオープン出来ませんでした。[" + filename + "]" ; 920 throw new OgRuntimeException( errMsg,ex ); 921 } 922 } 923 924 /** 925 * 改行コードで分割して、Listオブジェクトを返します。 926 * 927 * encode が null の場合は、UTF-8 で読み込みます。 928 * 929 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。 930 * 931 * @param filename ファイル名 932 * @param encode エンコード名 933 * @return ファイルを読み取った文字列を分割したList 934 */ 935 public static List<String> getLineList( final String filename , final String encode ) { 936 try { 937 final String enc = encode == null ? HybsConst.UTF_8 : encode ; 938 939 return Files.readAllLines( new File( filename ).toPath() , Charset.forName( enc ) ); 940 } 941 catch( final IOException ex ) { 942 final String errMsg = "ファイル名がオープン出来ませんでした。[" + filename + "]" ; 943 throw new OgRuntimeException( errMsg,ex ); 944 } 945 } 946 947 /** 948 * Fileオブジェクトのサイズを返します。 949 * 950 * オブジェクトが通常のファイルの場合は、そのファイルサイズを返します。 951 * フォルダの場合は、再帰的に、ファイルサイズを加算した結果を返します。 952 * 953 * @og.rev 6.7.4.1 (2017/02/17) Fileオブジェクトのサイズを返します。 954 * 955 * @param file Fileオブジェクト 956 * @return ファイルまたはフォルダののサイズ 957 */ 958 public static long length( final File file ) { 959 if( file.isDirectory() ) { 960 final File[] children = file.listFiles(); 961 962 long sum = 0; 963 if( children != null ) { 964 for( final File child : children ) { 965 sum += length( child ); 966 } 967 } 968 return sum; 969 } 970 return file.length(); 971 } 972 973 /** 974 * PrintWriter を継承した、System.out/System.err 用のクラスを定義します。 975 * 976 * 通常の、new PrintWriter( OutputStream ) で、求めるのと、ほとんど同様の 977 * 処理を行います。 978 * ただ、close() メソッドが呼ばれても、何もしません。 979 * 980 */ 981 private static final class NonClosePrintWriter extends PrintWriter { 982 /** 983 * コンストラクター 984 * 985 * new PrintWriter( OutputStream ) を行います。 986 * 987 * @param out OutputStreamオブジェクト 988 */ 989 public NonClosePrintWriter( final OutputStream out ) { 990 super( out ); 991 } 992 993 /** 994 * close() メソッドをオーバーライドします。 995 * 996 * 何もしません。 997 */ 998 @Override 999 public void close() { 1000 // ここでは処理を行いません。 1001 } 1002 } 1003 1004 /** 1005 * PrintWriter を継承した、JspWriterなどの Writer 用のクラスを定義します。 1006 * 1007 * 例えば、JspWriterなどの JSP/Servlet等のフレームワークで使用される 1008 * Writer では、flush や close 処理は、フレームワーク内で行われます。 1009 * その場合、通常のファイルと同じ用に、flush や close をアプリケーション側で 1010 * 行うと、内部処理的に不整合が発生したり、最悪の場合エラーになります。 1011 * このクラスは、単に、通常の、new PrintWriter( Writer ) で、求めるのと、 1012 * ほとんど同様の処理を行います。 1013 * ただ、close() と flush() メソッドが呼ばれても、何もしません。 1014 * 1015 */ 1016 private static final class NonFlushPrintWriter extends PrintWriter { 1017 /** 1018 * コンストラクター 1019 * 1020 * new PrintWriter( Writer ) を行います。 1021 * 1022 * @param writer Writerオブジェクト 1023 */ 1024 public NonFlushPrintWriter( final Writer writer ) { 1025 super( writer ); 1026 } 1027 1028 /** 1029 * close() メソッドをオーバーライドします。 1030 * 1031 * 何もしません。 1032 */ 1033 @Override 1034 public void close() { 1035 // ここでは処理を行いません。 1036 } 1037 1038 /** 1039 * flush() メソッドをオーバーライドします。 1040 * 1041 * 何もしません。 1042 */ 1043 @Override 1044 public void flush() { 1045 // ここでは処理を行いません。 1046 } 1047 } 1048 1049 /** 1050 * ファイルのエンコードを変換するコピーを行います。 1051 * 1052 * copy( File,File,false ) を呼び出します。 1053 * 1054 * @og.rev 5.1.6.0 (2010/05/01) 戻り値に、true/false 指定します。 1055 * 1056 * @param file1 コピー元ファイル名 1057 * @param file2 コピー先ファイル名 1058 * @param encode1 コピー元ファイルのエンコード 1059 * @param encode2 コピー先ファイルのエンコード 1060 * 1061 * @see #copy( File,File,boolean ) 1062 */ 1063 public static void copy( final File file1,final File file2,final String encode1,final String encode2 ) { 1064 // final File tempFile = new File( file2.getName() + "_backup" ); 1065 1066 // FileUtil.copy( file2,tempFile ); 1067 1068 final BufferedReader reader = FileUtil.getBufferedReader( file1 ,encode1 ); 1069 final PrintWriter writer = FileUtil.getPrintWriter( file2 ,encode2 ); 1070 1071 try { 1072 String line1; 1073 while((line1 = reader.readLine()) != null) { 1074 writer.println( line1 ); 1075 } 1076 } 1077 catch( final Throwable th ) { 1078 th.printStackTrace(); 1079 } 1080 finally { 1081 Closer.ioClose( reader ) ; 1082 Closer.ioClose( writer ) ; 1083 } 1084 1085 // 6.9.8.0 (2018/05/28) FindBugs:例外的戻り値を無視しているメソッド 1086// file2.setLastModified( file1.lastModified() ); 1087 if( !file2.setLastModified( file1.lastModified() ) ) { 1088 final String errMsg = "FileUtil.copy において、タイムスタンプの更新が出来ませんでした。" + CR 1089 + " file2= [" + file2 + "]" + CR ; 1090 System.err.println( errMsg ); 1091 } 1092 } 1093 1094 /** 1095 * ファイルをコピーします。 1096 * 1097 * 引数に <file1> <file2> [<encode1> <encode2>] を指定します。 1098 * file1 を読み込み、file2 にコピーします。コピー前に、file2 は、file2_backup にコピーします。 1099 * file1 が、ディレクトリの場合は、ディレクトリごとコピーします。 1100 * encode1、encode2 を指定すると、エンコード変換しながらコピーになります。 1101 * この場合は、ファイル同士のコピーのみになります。 1102 * 1103 * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。 1104 * @og.rev 5.1.6.0 (2010/05/01) 引数の並び順、処理を変更します。 1105 * 1106 * @param args 引数配列 file1 file2 [encode1 encode2] 1107 * @throws Throwable なんらかのエラーが発生した場合。 1108 */ 1109 public static void main( final String[] args ) throws Throwable { 1110 if( args.length != 2 && args.length != 4 ) { 1111 LogWriter.log("Usage: java org.opengion.fukurou.util.FileUtil <file1> <file2> [<encode1> <encode2>]" ); 1112 return ; 1113 } 1114 1115 final File file1 = new File( args[0] ); 1116 final File file2 = new File( args[1] ); 1117 1118 if( args.length < 3 ) { 1119 if( file1.isDirectory() ) { 1120 FileUtil.copyDirectry( file1, file2, true ); 1121 } 1122 else { 1123 final File tempFile = new File( args[1] + "_backup" ); 1124 FileUtil.copy( file2,tempFile ); 1125 FileUtil.copy( file1,file2, true ); 1126 } 1127 } 1128 else { 1129 final String encode1 = args[2]; 1130 final String encode2 = args[3]; 1131 1132 if( file1.isDirectory() ) { 1133 final File[] children = file1.listFiles(); 1134 1135 if( children != null ) { 1136 for( final File child : children ) { 1137 copy( child , new File( file2 , child.getName() ),encode1,encode2 ); 1138 } 1139 } 1140 } 1141 else { 1142 copy( file1,file2,encode1,encode2 ); 1143 } 1144 } 1145 } 1146}