001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 021package org.apache.directory.api.util; 022 023 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.FileNotFoundException; 027import java.io.FileOutputStream; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.OutputStream; 031import java.nio.charset.Charset; 032import java.nio.file.Files; 033import java.nio.file.Paths; 034import java.nio.file.StandardOpenOption; 035import java.util.List; 036 037import org.apache.directory.api.i18n.I18n; 038 039 040/** 041 * This code comes from Apache commons.io library. 042 * 043 * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils. 044 * 045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 046 */ 047public final class FileUtils 048{ 049 /** 050 * The Windows separator character. 051 */ 052 private static final char WINDOWS_SEPARATOR = '\\'; 053 054 /** 055 * The system separator character. 056 */ 057 private static final char SYSTEM_SEPARATOR = File.separatorChar; 058 059 /** 060 * The number of bytes in a kilobyte. 061 */ 062 public static final long ONE_KB = 1024; 063 064 /** 065 * The number of bytes in a megabyte. 066 */ 067 public static final long ONE_MB = ONE_KB * ONE_KB; 068 069 /** 070 * The file copy buffer size (30 MB) 071 */ 072 private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; 073 074 075 /** 076 * Creates a new instance of FileUtils. 077 */ 078 private FileUtils() 079 { 080 // Nothing to do. 081 } 082 083 084 /** 085 * Deletes a directory recursively. 086 * 087 * @param directory directory to delete 088 * @throws IOException in case deletion is unsuccessful 089 */ 090 public static void deleteDirectory( File directory ) throws IOException 091 { 092 if ( !directory.exists() ) 093 { 094 return; 095 } 096 097 if ( !isSymlink( directory ) ) 098 { 099 cleanDirectory( directory ); 100 } 101 102 if ( !directory.delete() ) 103 { 104 throw new IOException( I18n.err( I18n.ERR_17004_UNABLE_DELETE_DIR, directory ) ); 105 } 106 } 107 108 109 /** 110 * Determines whether the specified file is a Symbolic Link rather than an actual file. 111 * <p> 112 * Will not return true if there is a Symbolic Link anywhere in the path, 113 * only if the specific file is. 114 * <p> 115 * <b>Note:</b> the current implementation always returns {@code false} if the system 116 * is detected as Windows 117 * <p> 118 * For code that runs on Java 1.7 or later, use the following method instead: 119 * <br> 120 * {@code boolean java.nio.file.Files.isSymbolicLink(Path path)} 121 * @param file the file to check 122 * @return true if the file is a Symbolic Link 123 * @throws IOException if an IO error occurs while checking the file 124 * @since 2.0 125 */ 126 public static boolean isSymlink( File file ) throws IOException 127 { 128 if ( file == null ) 129 { 130 throw new NullPointerException( I18n.err( I18n.ERR_17005_FILE_MUST_NOT_BE_NULL ) ); 131 } 132 133 if ( SYSTEM_SEPARATOR == WINDOWS_SEPARATOR ) 134 { 135 return false; 136 } 137 138 File fileInCanonicalDir; 139 140 if ( file.getParent() == null ) 141 { 142 fileInCanonicalDir = file; 143 } 144 else 145 { 146 File canonicalDir = file.getParentFile().getCanonicalFile(); 147 fileInCanonicalDir = new File( canonicalDir, file.getName() ); 148 } 149 150 return !fileInCanonicalDir.getCanonicalFile().equals( fileInCanonicalDir.getAbsoluteFile() ); 151 } 152 153 154 /** 155 * Deletes a directory recursively. 156 * 157 * @param directory directory to delete 158 * @throws IOException in case deletion is unsuccessful 159 */ 160 public static void cleanDirectory( File directory ) throws IOException 161 { 162 if ( !directory.exists() ) 163 { 164 throw new IllegalArgumentException( I18n.err( I18n.ERR_17006_DOES_NOT_EXIST, directory ) ); 165 } 166 167 if ( !directory.isDirectory() ) 168 { 169 throw new IllegalArgumentException( I18n.err( I18n.ERR_17007_IS_NOT_DIRECTORY, directory ) ); 170 } 171 172 File[] files = directory.listFiles(); 173 174 if ( files == null ) 175 { 176 // null if security restricted 177 throw new IOException( I18n.err( I18n.ERR_17008_FAIL_LIST_DIR, directory ) ); 178 } 179 180 IOException exception = null; 181 182 for ( File file : files ) 183 { 184 try 185 { 186 forceDelete( file ); 187 } 188 catch ( IOException ioe ) 189 { 190 exception = ioe; 191 } 192 } 193 194 if ( null != exception ) 195 { 196 throw exception; 197 } 198 } 199 200 201 /** 202 * Deletes a file. If file is a directory, delete it and all sub-directories. 203 * <p> 204 * The difference between File.delete() and this method are: 205 * <ul> 206 * <li>A directory to be deleted does not have to be empty.</li> 207 * <li>You get exceptions when a file or directory cannot be deleted. 208 * (java.io.File methods returns a boolean)</li> 209 * </ul> 210 * 211 * @param file file or directory to delete, must not be {@code null} 212 * @throws NullPointerException if the directory is {@code null} 213 * @throws FileNotFoundException if the file was not found 214 * @throws IOException in case deletion is unsuccessful 215 */ 216 public static void forceDelete( File file ) throws IOException 217 { 218 if ( file.isDirectory() ) 219 { 220 deleteDirectory( file ); 221 } 222 else 223 { 224 boolean filePresent = file.exists(); 225 226 if ( !file.delete() ) 227 { 228 if ( !filePresent ) 229 { 230 throw new FileNotFoundException( I18n.err( I18n.ERR_17009_FILE_DOES_NOT_EXIST, file ) ); 231 } 232 233 throw new IOException( I18n.err( I18n.ERR_17010_UNABLE_DELETE_FILE, file ) ); 234 } 235 } 236 } 237 238 239 /** 240 * Returns the path to the system temporary directory. 241 * 242 * @return the path to the system temporary directory. 243 * 244 * @since 2.0 245 */ 246 public static String getTempDirectoryPath() 247 { 248 return System.getProperty( "java.io.tmpdir" ); 249 } 250 251 252 /** 253 * Reads the contents of a file into a String using the default encoding for the VM. 254 * The file is always closed. 255 * 256 * @param file the file to read, must not be {@code null} 257 * @return the file contents, never {@code null} 258 * @throws IOException in case of an I/O error 259 * @since 1.3.1 260 * @deprecated 2.5 use {@link #readFileToString(File, Charset)} instead 261 */ 262 @Deprecated 263 public static String readFileToString( File file ) throws IOException 264 { 265 return readFileToString( file, Charset.defaultCharset() ); 266 } 267 268 269 /** 270 * Reads the contents of a file into a String. 271 * The file is always closed. 272 * 273 * @param file the file to read, must not be {@code null} 274 * @param encoding the encoding to use, {@code null} means platform default 275 * @return the file contents, never {@code null} 276 * @throws IOException in case of an I/O error 277 * @since 2.3 278 */ 279 public static String readFileToString( File file, Charset encoding ) throws IOException 280 { 281 InputStream in = null; 282 283 try 284 { 285 in = openInputStream( file ); 286 return IOUtils.toString( in, IOUtils.toCharset( encoding ) ); 287 } 288 finally 289 { 290 IOUtils.closeQuietly( in ); 291 } 292 } 293 294 295 /** 296 * Reads the contents of a file into a String. The file is always closed. 297 * 298 * @param file the file to read, must not be {@code null} 299 * @param encoding the encoding to use, {@code null} means platform default 300 * @return the file contents, never {@code null} 301 * @throws IOException in case of an I/O error 302 * @since 2.3 303 */ 304 public static String readFileToString( File file, String encoding ) throws IOException 305 { 306 InputStream in = null; 307 308 try 309 { 310 in = openInputStream( file ); 311 return IOUtils.toString( in, IOUtils.toCharset( encoding ) ); 312 } 313 finally 314 { 315 IOUtils.closeQuietly( in ); 316 } 317 } 318 319 320 /** 321 * Opens a {@link FileInputStream} for the specified file, providing better 322 * error messages than simply calling <code>new FileInputStream(file)</code>. 323 * <p> 324 * At the end of the method either the stream will be successfully opened, 325 * or an exception will have been thrown. 326 * <p> 327 * An exception is thrown if the file does not exist. 328 * An exception is thrown if the file object exists but is a directory. 329 * An exception is thrown if the file exists but cannot be read. 330 * 331 * @param file the file to open for input, must not be {@code null} 332 * @return a new {@link InputStream} for the specified file 333 * @throws FileNotFoundException if the file does not exist 334 * @throws IOException if the file object is a directory 335 * @throws IOException if the file cannot be read 336 * @since 1.3 337 */ 338 public static InputStream openInputStream( File file ) throws IOException 339 { 340 if ( file.exists() ) 341 { 342 if ( file.isDirectory() ) 343 { 344 throw new IOException( I18n.err( I18n.ERR_17011_FILE_IS_DIR, file ) ); 345 } 346 347 if ( !file.canRead() ) 348 { 349 throw new IOException( I18n.err( I18n.ERR_17012_CANNOT_READ_FILE, file ) ); 350 } 351 } 352 else 353 { 354 throw new FileNotFoundException( I18n.err( I18n.ERR_17013_FILE_DOES_NOT_EXIST, file ) ); 355 } 356 357 return Files.newInputStream( Paths.get( file.getPath() ) ); 358 } 359 360 361 /** 362 * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. 363 * 364 * @param file the file to write 365 * @param data the content to write to the file 366 * @throws IOException in case of an I/O error 367 * @deprecated 2.5 use {@link #writeStringToFile(File, String, Charset, boolean)} instead 368 */ 369 @Deprecated 370 public static void writeStringToFile( File file, String data ) throws IOException 371 { 372 writeStringToFile( file, data, Charset.defaultCharset(), false ); 373 } 374 375 376 /** 377 * Writes a String to a file creating the file if it does not exist. 378 * 379 * NOTE: As from v1.3, the parent directories of the file will be created 380 * if they do not exist. 381 * 382 * @param file the file to write 383 * @param data the content to write to the file 384 * @param encoding the encoding to use, {@code null} means platform default 385 * @throws IOException in case of an I/O error 386 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 387 */ 388 public static void writeStringToFile( File file, String data, String encoding ) throws IOException 389 { 390 writeStringToFile( file, data, IOUtils.toCharset( encoding ), false ); 391 } 392 393 394 /** 395 * Writes a String to a file creating the file if it does not exist. 396 * 397 * @param file the file to write 398 * @param data the content to write to the file 399 * @param encoding the encoding to use, {@code null} means platform default 400 * @param append if {@code true}, then the String will be added to the 401 * end of the file rather than overwriting 402 * @throws IOException in case of an I/O error 403 * @since 2.3 404 */ 405 public static void writeStringToFile( File file, String data, Charset encoding, boolean append ) throws IOException 406 { 407 OutputStream out = null; 408 409 try 410 { 411 out = openOutputStream( file, append ); 412 IOUtils.write( data, out, encoding ); 413 out.close(); // don't swallow close Exception if copy completes normally 414 } 415 finally 416 { 417 IOUtils.closeQuietly( out ); 418 } 419 } 420 421 422 /** 423 * Opens a {@link FileOutputStream} for the specified file, checking and 424 * creating the parent directory if it does not exist. 425 * <p> 426 * At the end of the method either the stream will be successfully opened, 427 * or an exception will have been thrown. 428 * <p> 429 * The parent directory will be created if it does not exist. 430 * The file will be created if it does not exist. 431 * An exception is thrown if the file object exists but is a directory. 432 * An exception is thrown if the file exists but cannot be written to. 433 * An exception is thrown if the parent directory cannot be created. 434 * 435 * @param file the file to open for output, must not be {@code null} 436 * @param append if {@code true}, then bytes will be added to the 437 * end of the file rather than overwriting 438 * @return a new {@link OutputStream} for the specified file 439 * @throws IOException if the file object is a directory 440 * @throws IOException if the file cannot be written to 441 * @throws IOException if a parent directory needs creating but that fails 442 * @since 2.1 443 */ 444 public static OutputStream openOutputStream( File file, boolean append ) throws IOException 445 { 446 if ( file.exists() ) 447 { 448 if ( file.isDirectory() ) 449 { 450 throw new IOException( I18n.err( I18n.ERR_17011_FILE_IS_DIR, file ) ); 451 } 452 453 if ( !file.canWrite() ) 454 { 455 throw new IOException( I18n.err( I18n.ERR_17014_CANNOT_WRITE_FILE, file ) ); 456 } 457 } 458 else 459 { 460 File parent = file.getParentFile(); 461 462 if ( ( parent != null ) && ( !parent.mkdirs() && !parent.isDirectory() ) ) 463 { 464 throw new IOException( I18n.err( I18n.ERR_17015_CANNOT_CREATE_DIR, parent ) ); 465 } 466 } 467 468 if ( append ) 469 { 470 return Files.newOutputStream( Paths.get( file.getPath() ), StandardOpenOption.CREATE, StandardOpenOption.APPEND ); 471 } 472 else 473 { 474 return Files.newOutputStream( Paths.get( file.getPath() ) ); 475 } 476 } 477 478 479 /** 480 * Returns a {@link File} representing the system temporary directory. 481 * 482 * @return the system temporary directory. 483 * 484 * @since 2.0 485 */ 486 public static File getTempDirectory() 487 { 488 return new File( getTempDirectoryPath() ); 489 } 490 491 492 /** 493 * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. 494 * <p> 495 * The difference between File.delete() and this method are: 496 * <ul> 497 * <li>A directory to be deleted does not have to be empty.</li> 498 * <li>No exceptions are thrown when a file or directory cannot be deleted.</li> 499 * </ul> 500 * 501 * @param file file or directory to delete, can be {@code null} 502 * @return {@code true} if the file or directory was deleted, otherwise 503 * {@code false} 504 * 505 * @since 1.4 506 */ 507 public static boolean deleteQuietly( File file ) 508 { 509 if ( file == null ) 510 { 511 return false; 512 } 513 514 try 515 { 516 if ( file.isDirectory() ) 517 { 518 cleanDirectory( file ); 519 } 520 } 521 catch ( Exception ignored ) 522 { 523 } 524 525 try 526 { 527 return file.delete(); 528 } 529 catch ( Exception ignored ) 530 { 531 return false; 532 } 533 } 534 535 536 /** 537 * Writes a byte array to a file creating the file if it does not exist. 538 * <p> 539 * NOTE: As from v1.3, the parent directories of the file will be created 540 * if they do not exist. 541 * 542 * @param file the file to write to 543 * @param data the content to write to the file 544 * @throws IOException in case of an I/O erroe 545 * @since 1.1 546 */ 547 public static void writeByteArrayToFile( final File file, final byte[] data ) throws IOException 548 { 549 writeByteArrayToFile( file, data, false ); 550 } 551 552 553 /** 554 * Writes a byte array to a file creating the file if it does not exist. 555 * 556 * @param file the file to write to 557 * @param data the content to write to the file 558 * @param append if {@code true}, then bytes will be added to the 559 * end of the file rather than overwriting 560 * @throws IOException in case of an I/O error 561 * @since 2.1 562 */ 563 public static void writeByteArrayToFile( File file, byte[] data, boolean append ) throws IOException 564 { 565 writeByteArrayToFile( file, data, 0, data.length, append ); 566 } 567 568 569 /** 570 * Writes {@code len} bytes from the specified byte array starting 571 * at offset {@code off} to a file, creating the file if it does 572 * not exist. 573 * 574 * @param file the file to write to 575 * @param data the content to write to the file 576 * @param off the start offset in the data 577 * @param len the number of bytes to write 578 * @param append if {@code true}, then bytes will be added to the 579 * end of the file rather than overwriting 580 * @throws IOException in case of an I/O error 581 * @since 2.5 582 */ 583 public static void writeByteArrayToFile( File file, byte[] data, int off, int len, boolean append ) throws IOException 584 { 585 OutputStream out = null; 586 587 try 588 { 589 out = openOutputStream( file, append ); 590 out.write( data, off, len ); 591 out.close(); // don't swallow close Exception if copy completes normally 592 } 593 finally 594 { 595 IOUtils.closeQuietly( out ); 596 } 597 } 598 599 600 /** 601 * Reads the contents of a file into a byte array. 602 * The file is always closed. 603 * 604 * @param file the file to read, must not be {@code null} 605 * @return the file contents, never {@code null} 606 * @throws IOException in case of an I/O error 607 * @since 1.1 608 */ 609 public static byte[] readFileToByteArray( File file ) throws IOException 610 { 611 InputStream in = null; 612 613 try 614 { 615 in = openInputStream( file ); 616 return IOUtils.toByteArray( in, file.length() ); 617 } 618 finally 619 { 620 IOUtils.closeQuietly( in ); 621 } 622 } 623 624 625 /** 626 * Opens a {@link FileOutputStream} for the specified file, checking and 627 * creating the parent directory if it does not exist. 628 * <p> 629 * At the end of the method either the stream will be successfully opened, 630 * or an exception will have been thrown. 631 * <p> 632 * The parent directory will be created if it does not exist. 633 * The file will be created if it does not exist. 634 * An exception is thrown if the file object exists but is a directory. 635 * An exception is thrown if the file exists but cannot be written to. 636 * An exception is thrown if the parent directory cannot be created. 637 * 638 * @param file the file to open for output, must not be {@code null} 639 * @return a new {@link OutputStream} for the specified file 640 * @throws IOException if the file object is a directory 641 * @throws IOException if the file cannot be written to 642 * @throws IOException if a parent directory needs creating but that fails 643 * @since 1.3 644 */ 645 public static OutputStream openOutputStream( File file ) throws IOException 646 { 647 return openOutputStream( file, false ); 648 } 649 650 651 /** 652 * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM. 653 * The file is always closed. 654 * 655 * @param file the file to read, must not be {@code null} 656 * @return the list of Strings representing each line in the file, never {@code null} 657 * @throws IOException in case of an I/O error 658 * @since 1.3 659 * @deprecated 2.5 use {@link #readLines(File, Charset)} instead 660 */ 661 @Deprecated 662 public static List<String> readLines( File file ) throws IOException 663 { 664 return readLines( file, Charset.defaultCharset() ); 665 } 666 667 668 /** 669 * Reads the contents of a file line by line to a List of Strings. 670 * The file is always closed. 671 * 672 * @param file the file to read, must not be {@code null} 673 * @param encoding the encoding to use, {@code null} means platform default 674 * @return the list of Strings representing each line in the file, never {@code null} 675 * @throws IOException in case of an I/O error 676 * @since 2.3 677 */ 678 public static List<String> readLines( File file, Charset encoding ) throws IOException 679 { 680 InputStream in = null; 681 682 try 683 { 684 in = openInputStream( file ); 685 return IOUtils.readLines( in, IOUtils.toCharset( encoding ) ); 686 } 687 finally 688 { 689 IOUtils.closeQuietly( in ); 690 } 691 } 692}