001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.io; 018 019 import java.io.File; 020 import java.io.FileFilter; 021 import java.io.FileInputStream; 022 import java.io.FileNotFoundException; 023 import java.io.FileOutputStream; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.InputStreamReader; 027 import java.io.OutputStream; 028 import java.io.Reader; 029 import java.math.BigInteger; 030 import java.net.URL; 031 import java.net.URLConnection; 032 import java.nio.ByteBuffer; 033 import java.nio.channels.FileChannel; 034 import java.nio.charset.Charset; 035 import java.util.ArrayList; 036 import java.util.Collection; 037 import java.util.Date; 038 import java.util.Iterator; 039 import java.util.List; 040 import java.util.zip.CRC32; 041 import java.util.zip.CheckedInputStream; 042 import java.util.zip.Checksum; 043 044 import org.apache.commons.io.filefilter.DirectoryFileFilter; 045 import org.apache.commons.io.filefilter.FalseFileFilter; 046 import org.apache.commons.io.filefilter.FileFilterUtils; 047 import org.apache.commons.io.filefilter.IOFileFilter; 048 import org.apache.commons.io.filefilter.SuffixFileFilter; 049 import org.apache.commons.io.filefilter.TrueFileFilter; 050 import org.apache.commons.io.output.NullOutputStream; 051 052 /** 053 * General file manipulation utilities. 054 * <p> 055 * Facilities are provided in the following areas: 056 * <ul> 057 * <li>writing to a file 058 * <li>reading from a file 059 * <li>make a directory including parent directories 060 * <li>copying files and directories 061 * <li>deleting files and directories 062 * <li>converting to and from a URL 063 * <li>listing files and directories by filter and extension 064 * <li>comparing file content 065 * <li>file last changed date 066 * <li>calculating a checksum 067 * </ul> 068 * <p> 069 * Origin of code: Excalibur, Alexandria, Commons-Utils 070 * 071 * @version $Id: FileUtils.java 1304052 2012-03-22 20:55:29Z ggregory $ 072 */ 073 public class FileUtils { 074 075 /** 076 * Instances should NOT be constructed in standard programming. 077 */ 078 public FileUtils() { 079 super(); 080 } 081 082 /** 083 * The number of bytes in a kilobyte. 084 */ 085 public static final long ONE_KB = 1024; 086 087 /** 088 * The number of bytes in a megabyte. 089 */ 090 public static final long ONE_MB = ONE_KB * ONE_KB; 091 092 /** 093 * The file copy buffer size (30 MB) 094 */ 095 private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; 096 097 /** 098 * The number of bytes in a gigabyte. 099 */ 100 public static final long ONE_GB = ONE_KB * ONE_MB; 101 102 /** 103 * The number of bytes in a terabyte. 104 */ 105 public static final long ONE_TB = ONE_KB * ONE_GB; 106 107 /** 108 * The number of bytes in a petabyte. 109 */ 110 public static final long ONE_PB = ONE_KB * ONE_TB; 111 112 /** 113 * The number of bytes in an exabyte. 114 */ 115 public static final long ONE_EB = ONE_KB * ONE_PB; 116 117 /** 118 * The number of bytes in a zettabyte. 119 */ 120 public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB)); 121 122 /** 123 * The number of bytes in a yottabyte. 124 */ 125 public static final BigInteger ONE_YB = ONE_ZB.multiply(BigInteger.valueOf(ONE_EB)); 126 127 /** 128 * An empty array of type <code>File</code>. 129 */ 130 public static final File[] EMPTY_FILE_ARRAY = new File[0]; 131 132 /** 133 * The UTF-8 character set, used to decode octets in URLs. 134 */ 135 private static final Charset UTF8 = Charset.forName("UTF-8"); 136 137 //----------------------------------------------------------------------- 138 /** 139 * Construct a file from the set of name elements. 140 * 141 * @param directory the parent directory 142 * @param names the name elements 143 * @return the file 144 * @since 2.1 145 */ 146 public static File getFile(File directory, String... names) { 147 if (directory == null) { 148 throw new NullPointerException("directorydirectory must not be null"); 149 } 150 if (names == null) { 151 throw new NullPointerException("names must not be null"); 152 } 153 File file = directory; 154 for (String name : names) { 155 file = new File(file, name); 156 } 157 return file; 158 } 159 160 /** 161 * Construct a file from the set of name elements. 162 * 163 * @param names the name elements 164 * @return the file 165 * @since 2.1 166 */ 167 public static File getFile(String... names) { 168 if (names == null) { 169 throw new NullPointerException("names must not be null"); 170 } 171 File file = null; 172 for (String name : names) { 173 if (file == null) { 174 file = new File(name); 175 } else { 176 file = new File(file, name); 177 } 178 } 179 return file; 180 } 181 182 /** 183 * Returns the path to the system temporary directory. 184 * 185 * @return the path to the system temporary directory. 186 * 187 * @since 2.0 188 */ 189 public static String getTempDirectoryPath() { 190 return System.getProperty("java.io.tmpdir"); 191 } 192 193 /** 194 * Returns a {@link File} representing the system temporary directory. 195 * 196 * @return the system temporary directory. 197 * 198 * @since 2.0 199 */ 200 public static File getTempDirectory() { 201 return new File(getTempDirectoryPath()); 202 } 203 204 /** 205 * Returns the path to the user's home directory. 206 * 207 * @return the path to the user's home directory. 208 * 209 * @since 2.0 210 */ 211 public static String getUserDirectoryPath() { 212 return System.getProperty("user.home"); 213 } 214 215 /** 216 * Returns a {@link File} representing the user's home directory. 217 * 218 * @return the user's home directory. 219 * 220 * @since 2.0 221 */ 222 public static File getUserDirectory() { 223 return new File(getUserDirectoryPath()); 224 } 225 226 //----------------------------------------------------------------------- 227 /** 228 * Opens a {@link FileInputStream} for the specified file, providing better 229 * error messages than simply calling <code>new FileInputStream(file)</code>. 230 * <p> 231 * At the end of the method either the stream will be successfully opened, 232 * or an exception will have been thrown. 233 * <p> 234 * An exception is thrown if the file does not exist. 235 * An exception is thrown if the file object exists but is a directory. 236 * An exception is thrown if the file exists but cannot be read. 237 * 238 * @param file the file to open for input, must not be <code>null</code> 239 * @return a new {@link FileInputStream} for the specified file 240 * @throws FileNotFoundException if the file does not exist 241 * @throws IOException if the file object is a directory 242 * @throws IOException if the file cannot be read 243 * @since 1.3 244 */ 245 public static FileInputStream openInputStream(File file) throws IOException { 246 if (file.exists()) { 247 if (file.isDirectory()) { 248 throw new IOException("File '" + file + "' exists but is a directory"); 249 } 250 if (file.canRead() == false) { 251 throw new IOException("File '" + file + "' cannot be read"); 252 } 253 } else { 254 throw new FileNotFoundException("File '" + file + "' does not exist"); 255 } 256 return new FileInputStream(file); 257 } 258 259 //----------------------------------------------------------------------- 260 /** 261 * Opens a {@link FileOutputStream} for the specified file, checking and 262 * creating the parent directory if it does not exist. 263 * <p> 264 * At the end of the method either the stream will be successfully opened, 265 * or an exception will have been thrown. 266 * <p> 267 * The parent directory will be created if it does not exist. 268 * The file will be created if it does not exist. 269 * An exception is thrown if the file object exists but is a directory. 270 * An exception is thrown if the file exists but cannot be written to. 271 * An exception is thrown if the parent directory cannot be created. 272 * 273 * @param file the file to open for output, must not be <code>null</code> 274 * @return a new {@link FileOutputStream} for the specified file 275 * @throws IOException if the file object is a directory 276 * @throws IOException if the file cannot be written to 277 * @throws IOException if a parent directory needs creating but that fails 278 * @since 1.3 279 */ 280 public static FileOutputStream openOutputStream(File file) throws IOException { 281 return openOutputStream(file, false); 282 } 283 284 /** 285 * Opens a {@link FileOutputStream} for the specified file, checking and 286 * creating the parent directory if it does not exist. 287 * <p> 288 * At the end of the method either the stream will be successfully opened, 289 * or an exception will have been thrown. 290 * <p> 291 * The parent directory will be created if it does not exist. 292 * The file will be created if it does not exist. 293 * An exception is thrown if the file object exists but is a directory. 294 * An exception is thrown if the file exists but cannot be written to. 295 * An exception is thrown if the parent directory cannot be created. 296 * 297 * @param file the file to open for output, must not be <code>null</code> 298 * @param append if <code>true</code>, then bytes will be added to the 299 * end of the file rather than overwriting 300 * @return a new {@link FileOutputStream} for the specified file 301 * @throws IOException if the file object is a directory 302 * @throws IOException if the file cannot be written to 303 * @throws IOException if a parent directory needs creating but that fails 304 * @since 2.1 305 */ 306 public static FileOutputStream openOutputStream(File file, boolean append) throws IOException { 307 if (file.exists()) { 308 if (file.isDirectory()) { 309 throw new IOException("File '" + file + "' exists but is a directory"); 310 } 311 if (file.canWrite() == false) { 312 throw new IOException("File '" + file + "' cannot be written to"); 313 } 314 } else { 315 File parent = file.getParentFile(); 316 if (parent != null) { 317 if (!parent.mkdirs() && !parent.isDirectory()) { 318 throw new IOException("Directory '" + parent + "' could not be created"); 319 } 320 } 321 } 322 return new FileOutputStream(file, append); 323 } 324 325 //----------------------------------------------------------------------- 326 /** 327 * Returns a human-readable version of the file size, where the input 328 * represents a specific number of bytes. 329 * 330 * If the size is over 1GB, the size is returned as the number of whole GB, 331 * i.e. the size is rounded down to the nearest GB boundary. 332 * 333 * Similarly for the 1MB and 1KB boundaries. 334 * 335 * @param size the number of bytes 336 * @return a human-readable display value (includes units - GB, MB, KB or bytes) 337 */ 338 // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? 339 public static String byteCountToDisplaySize(long size) { 340 String displaySize; 341 342 if (size / ONE_EB > 0) { 343 displaySize = String.valueOf(size / ONE_EB) + " EB"; 344 } else if (size / ONE_PB > 0) { 345 displaySize = String.valueOf(size / ONE_PB) + " PB"; 346 } else if (size / ONE_TB > 0) { 347 displaySize = String.valueOf(size / ONE_TB) + " TB"; 348 } else if (size / ONE_GB > 0) { 349 displaySize = String.valueOf(size / ONE_GB) + " GB"; 350 } else if (size / ONE_MB > 0) { 351 displaySize = String.valueOf(size / ONE_MB) + " MB"; 352 } else if (size / ONE_KB > 0) { 353 displaySize = String.valueOf(size / ONE_KB) + " KB"; 354 } else { 355 displaySize = String.valueOf(size) + " bytes"; 356 } 357 return displaySize; 358 } 359 360 //----------------------------------------------------------------------- 361 /** 362 * Implements the same behaviour as the "touch" utility on Unix. It creates 363 * a new file with size 0 or, if the file exists already, it is opened and 364 * closed without modifying it, but updating the file date and time. 365 * <p> 366 * NOTE: As from v1.3, this method throws an IOException if the last 367 * modified date of the file cannot be set. Also, as from v1.3 this method 368 * creates parent directories if they do not exist. 369 * 370 * @param file the File to touch 371 * @throws IOException If an I/O problem occurs 372 */ 373 public static void touch(File file) throws IOException { 374 if (!file.exists()) { 375 OutputStream out = openOutputStream(file); 376 IOUtils.closeQuietly(out); 377 } 378 boolean success = file.setLastModified(System.currentTimeMillis()); 379 if (!success) { 380 throw new IOException("Unable to set the last modification time for " + file); 381 } 382 } 383 384 //----------------------------------------------------------------------- 385 /** 386 * Converts a Collection containing java.io.File instanced into array 387 * representation. This is to account for the difference between 388 * File.listFiles() and FileUtils.listFiles(). 389 * 390 * @param files a Collection containing java.io.File instances 391 * @return an array of java.io.File 392 */ 393 public static File[] convertFileCollectionToFileArray(Collection<File> files) { 394 return files.toArray(new File[files.size()]); 395 } 396 397 //----------------------------------------------------------------------- 398 /** 399 * Finds files within a given directory (and optionally its 400 * subdirectories). All files found are filtered by an IOFileFilter. 401 * 402 * @param files the collection of files found. 403 * @param directory the directory to search in. 404 * @param filter the filter to apply to files and directories. 405 * @param includeSubDirectories indicates if will include the subdirectories themselves 406 */ 407 private static void innerListFiles(Collection<File> files, File directory, 408 IOFileFilter filter, boolean includeSubDirectories) { 409 File[] found = directory.listFiles((FileFilter) filter); 410 411 if (found != null) { 412 for (File file : found) { 413 if (file.isDirectory()) { 414 if (includeSubDirectories) { 415 files.add(file); 416 } 417 innerListFiles(files, file, filter, includeSubDirectories); 418 } else { 419 files.add(file); 420 } 421 } 422 } 423 } 424 425 /** 426 * Finds files within a given directory (and optionally its 427 * subdirectories). All files found are filtered by an IOFileFilter. 428 * <p> 429 * If your search should recurse into subdirectories you can pass in 430 * an IOFileFilter for directories. You don't need to bind a 431 * DirectoryFileFilter (via logical AND) to this filter. This method does 432 * that for you. 433 * <p> 434 * An example: If you want to search through all directories called 435 * "temp" you pass in <code>FileFilterUtils.NameFileFilter("temp")</code> 436 * <p> 437 * Another common usage of this method is find files in a directory 438 * tree but ignoring the directories generated CVS. You can simply pass 439 * in <code>FileFilterUtils.makeCVSAware(null)</code>. 440 * 441 * @param directory the directory to search in 442 * @param fileFilter filter to apply when finding files. 443 * @param dirFilter optional filter to apply when finding subdirectories. 444 * If this parameter is <code>null</code>, subdirectories will not be included in the 445 * search. Use TrueFileFilter.INSTANCE to match all directories. 446 * @return an collection of java.io.File with the matching files 447 * @see org.apache.commons.io.filefilter.FileFilterUtils 448 * @see org.apache.commons.io.filefilter.NameFileFilter 449 */ 450 public static Collection<File> listFiles( 451 File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) { 452 validateListFilesParameters(directory, fileFilter); 453 454 IOFileFilter effFileFilter = setUpEffectiveFileFilter(fileFilter); 455 IOFileFilter effDirFilter = setUpEffectiveDirFilter(dirFilter); 456 457 //Find files 458 Collection<File> files = new java.util.LinkedList<File>(); 459 innerListFiles(files, directory, 460 FileFilterUtils.or(effFileFilter, effDirFilter), false); 461 return files; 462 } 463 464 /** 465 * Validates the given arguments. 466 * <ul> 467 * <li>Throws {@link IllegalArgumentException} if {@code directory} is not a directory</li> 468 * <li>Throws {@link NullPointerException} if {@code fileFilter} is null</li> 469 * </ul> 470 * 471 * @param directory The File to test 472 * @param fileFilter The IOFileFilter to test 473 */ 474 private static void validateListFilesParameters(File directory, IOFileFilter fileFilter) { 475 if (!directory.isDirectory()) { 476 throw new IllegalArgumentException("Parameter 'directory' is not a directory"); 477 } 478 if (fileFilter == null) { 479 throw new NullPointerException("Parameter 'fileFilter' is null"); 480 } 481 } 482 483 /** 484 * Returns a filter that accepts files in addition to the {@link File} objects accepted by the given filter. 485 * 486 * @param fileFilter a base filter to add to 487 * @return a filter that accepts files 488 */ 489 private static IOFileFilter setUpEffectiveFileFilter(IOFileFilter fileFilter) { 490 return FileFilterUtils.and(fileFilter, FileFilterUtils.notFileFilter(DirectoryFileFilter.INSTANCE)); 491 } 492 493 /** 494 * Returns a filter that accepts directories in addition to the {@link File} objects accepted by the given filter. 495 * 496 * @param dirFilter a base filter to add to 497 * @return a filter that accepts directories 498 */ 499 private static IOFileFilter setUpEffectiveDirFilter(IOFileFilter dirFilter) { 500 return dirFilter == null ? FalseFileFilter.INSTANCE : FileFilterUtils.and(dirFilter, 501 DirectoryFileFilter.INSTANCE); 502 } 503 504 /** 505 * Finds files within a given directory (and optionally its 506 * subdirectories). All files found are filtered by an IOFileFilter. 507 * <p> 508 * The resulting collection includes the subdirectories themselves. 509 * <p> 510 * @see org.apache.commons.io.FileUtils#listFiles 511 * 512 * @param directory the directory to search in 513 * @param fileFilter filter to apply when finding files. 514 * @param dirFilter optional filter to apply when finding subdirectories. 515 * If this parameter is <code>null</code>, subdirectories will not be included in the 516 * search. Use TrueFileFilter.INSTANCE to match all directories. 517 * @return an collection of java.io.File with the matching files 518 * @see org.apache.commons.io.filefilter.FileFilterUtils 519 * @see org.apache.commons.io.filefilter.NameFileFilter 520 * @since 2.2 521 */ 522 public static Collection<File> listFilesAndDirs( 523 File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) { 524 validateListFilesParameters(directory, fileFilter); 525 526 IOFileFilter effFileFilter = setUpEffectiveFileFilter(fileFilter); 527 IOFileFilter effDirFilter = setUpEffectiveDirFilter(dirFilter); 528 529 //Find files 530 Collection<File> files = new java.util.LinkedList<File>(); 531 if (directory.isDirectory()) { 532 files.add(directory); 533 } 534 innerListFiles(files, directory, 535 FileFilterUtils.or(effFileFilter, effDirFilter), true); 536 return files; 537 } 538 539 /** 540 * Allows iteration over the files in given directory (and optionally 541 * its subdirectories). 542 * <p> 543 * All files found are filtered by an IOFileFilter. This method is 544 * based on {@link #listFiles(File, IOFileFilter, IOFileFilter)}, 545 * which supports Iterable ('foreach' loop). 546 * <p> 547 * @param directory the directory to search in 548 * @param fileFilter filter to apply when finding files. 549 * @param dirFilter optional filter to apply when finding subdirectories. 550 * If this parameter is <code>null</code>, subdirectories will not be included in the 551 * search. Use TrueFileFilter.INSTANCE to match all directories. 552 * @return an iterator of java.io.File for the matching files 553 * @see org.apache.commons.io.filefilter.FileFilterUtils 554 * @see org.apache.commons.io.filefilter.NameFileFilter 555 * @since 1.2 556 */ 557 public static Iterator<File> iterateFiles( 558 File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) { 559 return listFiles(directory, fileFilter, dirFilter).iterator(); 560 } 561 562 /** 563 * Allows iteration over the files in given directory (and optionally 564 * its subdirectories). 565 * <p> 566 * All files found are filtered by an IOFileFilter. This method is 567 * based on {@link #listFilesAndDirs(File, IOFileFilter, IOFileFilter)}, 568 * which supports Iterable ('foreach' loop). 569 * <p> 570 * The resulting iterator includes the subdirectories themselves. 571 * 572 * @param directory the directory to search in 573 * @param fileFilter filter to apply when finding files. 574 * @param dirFilter optional filter to apply when finding subdirectories. 575 * If this parameter is <code>null</code>, subdirectories will not be included in the 576 * search. Use TrueFileFilter.INSTANCE to match all directories. 577 * @return an iterator of java.io.File for the matching files 578 * @see org.apache.commons.io.filefilter.FileFilterUtils 579 * @see org.apache.commons.io.filefilter.NameFileFilter 580 * @since 2.2 581 */ 582 public static Iterator<File> iterateFilesAndDirs(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) { 583 return listFilesAndDirs(directory, fileFilter, dirFilter).iterator(); 584 } 585 586 //----------------------------------------------------------------------- 587 /** 588 * Converts an array of file extensions to suffixes for use 589 * with IOFileFilters. 590 * 591 * @param extensions an array of extensions. Format: {"java", "xml"} 592 * @return an array of suffixes. Format: {".java", ".xml"} 593 */ 594 private static String[] toSuffixes(String[] extensions) { 595 String[] suffixes = new String[extensions.length]; 596 for (int i = 0; i < extensions.length; i++) { 597 suffixes[i] = "." + extensions[i]; 598 } 599 return suffixes; 600 } 601 602 603 /** 604 * Finds files within a given directory (and optionally its subdirectories) 605 * which match an array of extensions. 606 * 607 * @param directory the directory to search in 608 * @param extensions an array of extensions, ex. {"java","xml"}. If this 609 * parameter is <code>null</code>, all files are returned. 610 * @param recursive if true all subdirectories are searched as well 611 * @return an collection of java.io.File with the matching files 612 */ 613 public static Collection<File> listFiles( 614 File directory, String[] extensions, boolean recursive) { 615 IOFileFilter filter; 616 if (extensions == null) { 617 filter = TrueFileFilter.INSTANCE; 618 } else { 619 String[] suffixes = toSuffixes(extensions); 620 filter = new SuffixFileFilter(suffixes); 621 } 622 return listFiles(directory, filter, 623 recursive ? TrueFileFilter.INSTANCE : FalseFileFilter.INSTANCE); 624 } 625 626 /** 627 * Allows iteration over the files in a given directory (and optionally 628 * its subdirectories) which match an array of extensions. This method 629 * is based on {@link #listFiles(File, String[], boolean)}, 630 * which supports Iterable ('foreach' loop). 631 * 632 * @param directory the directory to search in 633 * @param extensions an array of extensions, ex. {"java","xml"}. If this 634 * parameter is <code>null</code>, all files are returned. 635 * @param recursive if true all subdirectories are searched as well 636 * @return an iterator of java.io.File with the matching files 637 * @since 1.2 638 */ 639 public static Iterator<File> iterateFiles( 640 File directory, String[] extensions, boolean recursive) { 641 return listFiles(directory, extensions, recursive).iterator(); 642 } 643 644 //----------------------------------------------------------------------- 645 /** 646 * Compares the contents of two files to determine if they are equal or not. 647 * <p> 648 * This method checks to see if the two files are different lengths 649 * or if they point to the same file, before resorting to byte-by-byte 650 * comparison of the contents. 651 * <p> 652 * Code origin: Avalon 653 * 654 * @param file1 the first file 655 * @param file2 the second file 656 * @return true if the content of the files are equal or they both don't 657 * exist, false otherwise 658 * @throws IOException in case of an I/O error 659 */ 660 public static boolean contentEquals(File file1, File file2) throws IOException { 661 boolean file1Exists = file1.exists(); 662 if (file1Exists != file2.exists()) { 663 return false; 664 } 665 666 if (!file1Exists) { 667 // two not existing files are equal 668 return true; 669 } 670 671 if (file1.isDirectory() || file2.isDirectory()) { 672 // don't want to compare directory contents 673 throw new IOException("Can't compare directories, only files"); 674 } 675 676 if (file1.length() != file2.length()) { 677 // lengths differ, cannot be equal 678 return false; 679 } 680 681 if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { 682 // same file 683 return true; 684 } 685 686 InputStream input1 = null; 687 InputStream input2 = null; 688 try { 689 input1 = new FileInputStream(file1); 690 input2 = new FileInputStream(file2); 691 return IOUtils.contentEquals(input1, input2); 692 693 } finally { 694 IOUtils.closeQuietly(input1); 695 IOUtils.closeQuietly(input2); 696 } 697 } 698 699 //----------------------------------------------------------------------- 700 /** 701 * Compares the contents of two files to determine if they are equal or not. 702 * <p> 703 * This method checks to see if the two files point to the same file, 704 * before resorting to line-by-line comparison of the contents. 705 * <p> 706 * 707 * @param file1 the first file 708 * @param file2 the second file 709 * @param charsetName the character encoding to be used. 710 * May be null, in which case the platform default is used 711 * @return true if the content of the files are equal or neither exists, 712 * false otherwise 713 * @throws IOException in case of an I/O error 714 * @since 2.2 715 * @see IOUtils#contentEqualsIgnoreEOL(Reader, Reader) 716 */ 717 public static boolean contentEqualsIgnoreEOL(File file1, File file2, String charsetName) throws IOException { 718 boolean file1Exists = file1.exists(); 719 if (file1Exists != file2.exists()) { 720 return false; 721 } 722 723 if (!file1Exists) { 724 // two not existing files are equal 725 return true; 726 } 727 728 if (file1.isDirectory() || file2.isDirectory()) { 729 // don't want to compare directory contents 730 throw new IOException("Can't compare directories, only files"); 731 } 732 733 if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { 734 // same file 735 return true; 736 } 737 738 Reader input1 = null; 739 Reader input2 = null; 740 try { 741 if (charsetName == null) { 742 input1 = new InputStreamReader(new FileInputStream(file1)); 743 input2 = new InputStreamReader(new FileInputStream(file2)); 744 } else { 745 input1 = new InputStreamReader(new FileInputStream(file1), charsetName); 746 input2 = new InputStreamReader(new FileInputStream(file2), charsetName); 747 } 748 return IOUtils.contentEqualsIgnoreEOL(input1, input2); 749 750 } finally { 751 IOUtils.closeQuietly(input1); 752 IOUtils.closeQuietly(input2); 753 } 754 } 755 756 //----------------------------------------------------------------------- 757 /** 758 * Convert from a <code>URL</code> to a <code>File</code>. 759 * <p> 760 * From version 1.1 this method will decode the URL. 761 * Syntax such as <code>file:///my%20docs/file.txt</code> will be 762 * correctly decoded to <code>/my docs/file.txt</code>. Starting with version 763 * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters. 764 * Additionally, malformed percent-encoded octets are handled leniently by 765 * passing them through literally. 766 * 767 * @param url the file URL to convert, <code>null</code> returns <code>null</code> 768 * @return the equivalent <code>File</code> object, or <code>null</code> 769 * if the URL's protocol is not <code>file</code> 770 */ 771 public static File toFile(URL url) { 772 if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) { 773 return null; 774 } else { 775 String filename = url.getFile().replace('/', File.separatorChar); 776 filename = decodeUrl(filename); 777 return new File(filename); 778 } 779 } 780 781 /** 782 * Decodes the specified URL as per RFC 3986, i.e. transforms 783 * percent-encoded octets to characters by decoding with the UTF-8 character 784 * set. This function is primarily intended for usage with 785 * {@link java.net.URL} which unfortunately does not enforce proper URLs. As 786 * such, this method will leniently accept invalid characters or malformed 787 * percent-encoded octets and simply pass them literally through to the 788 * result string. Except for rare edge cases, this will make unencoded URLs 789 * pass through unaltered. 790 * 791 * @param url The URL to decode, may be <code>null</code>. 792 * @return The decoded URL or <code>null</code> if the input was 793 * <code>null</code>. 794 */ 795 static String decodeUrl(String url) { 796 String decoded = url; 797 if (url != null && url.indexOf('%') >= 0) { 798 int n = url.length(); 799 StringBuffer buffer = new StringBuffer(); 800 ByteBuffer bytes = ByteBuffer.allocate(n); 801 for (int i = 0; i < n;) { 802 if (url.charAt(i) == '%') { 803 try { 804 do { 805 byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16); 806 bytes.put(octet); 807 i += 3; 808 } while (i < n && url.charAt(i) == '%'); 809 continue; 810 } catch (RuntimeException e) { 811 // malformed percent-encoded octet, fall through and 812 // append characters literally 813 } finally { 814 if (bytes.position() > 0) { 815 bytes.flip(); 816 buffer.append(UTF8.decode(bytes).toString()); 817 bytes.clear(); 818 } 819 } 820 } 821 buffer.append(url.charAt(i++)); 822 } 823 decoded = buffer.toString(); 824 } 825 return decoded; 826 } 827 828 /** 829 * Converts each of an array of <code>URL</code> to a <code>File</code>. 830 * <p> 831 * Returns an array of the same size as the input. 832 * If the input is <code>null</code>, an empty array is returned. 833 * If the input contains <code>null</code>, the output array contains <code>null</code> at the same 834 * index. 835 * <p> 836 * This method will decode the URL. 837 * Syntax such as <code>file:///my%20docs/file.txt</code> will be 838 * correctly decoded to <code>/my docs/file.txt</code>. 839 * 840 * @param urls the file URLs to convert, <code>null</code> returns empty array 841 * @return a non-<code>null</code> array of Files matching the input, with a <code>null</code> item 842 * if there was a <code>null</code> at that index in the input array 843 * @throws IllegalArgumentException if any file is not a URL file 844 * @throws IllegalArgumentException if any file is incorrectly encoded 845 * @since 1.1 846 */ 847 public static File[] toFiles(URL[] urls) { 848 if (urls == null || urls.length == 0) { 849 return EMPTY_FILE_ARRAY; 850 } 851 File[] files = new File[urls.length]; 852 for (int i = 0; i < urls.length; i++) { 853 URL url = urls[i]; 854 if (url != null) { 855 if (url.getProtocol().equals("file") == false) { 856 throw new IllegalArgumentException( 857 "URL could not be converted to a File: " + url); 858 } 859 files[i] = toFile(url); 860 } 861 } 862 return files; 863 } 864 865 /** 866 * Converts each of an array of <code>File</code> to a <code>URL</code>. 867 * <p> 868 * Returns an array of the same size as the input. 869 * 870 * @param files the files to convert 871 * @return an array of URLs matching the input 872 * @throws IOException if a file cannot be converted 873 */ 874 public static URL[] toURLs(File[] files) throws IOException { 875 URL[] urls = new URL[files.length]; 876 877 for (int i = 0; i < urls.length; i++) { 878 urls[i] = files[i].toURI().toURL(); 879 } 880 881 return urls; 882 } 883 884 //----------------------------------------------------------------------- 885 /** 886 * Copies a file to a directory preserving the file date. 887 * <p> 888 * This method copies the contents of the specified source file 889 * to a file of the same name in the specified destination directory. 890 * The destination directory is created if it does not exist. 891 * If the destination file exists, then this method will overwrite it. 892 * <p> 893 * <strong>Note:</strong> This method tries to preserve the file's last 894 * modified date/times using {@link File#setLastModified(long)}, however 895 * it is not guaranteed that the operation will succeed. 896 * If the modification operation fails, no indication is provided. 897 * 898 * @param srcFile an existing file to copy, must not be <code>null</code> 899 * @param destDir the directory to place the copy in, must not be <code>null</code> 900 * 901 * @throws NullPointerException if source or destination is null 902 * @throws IOException if source or destination is invalid 903 * @throws IOException if an IO error occurs during copying 904 * @see #copyFile(File, File, boolean) 905 */ 906 public static void copyFileToDirectory(File srcFile, File destDir) throws IOException { 907 copyFileToDirectory(srcFile, destDir, true); 908 } 909 910 /** 911 * Copies a file to a directory optionally preserving the file date. 912 * <p> 913 * This method copies the contents of the specified source file 914 * to a file of the same name in the specified destination directory. 915 * The destination directory is created if it does not exist. 916 * If the destination file exists, then this method will overwrite it. 917 * <p> 918 * <strong>Note:</strong> Setting <code>preserveFileDate</code> to 919 * <code>true</code> tries to preserve the file's last modified 920 * date/times using {@link File#setLastModified(long)}, however it is 921 * not guaranteed that the operation will succeed. 922 * If the modification operation fails, no indication is provided. 923 * 924 * @param srcFile an existing file to copy, must not be <code>null</code> 925 * @param destDir the directory to place the copy in, must not be <code>null</code> 926 * @param preserveFileDate true if the file date of the copy 927 * should be the same as the original 928 * 929 * @throws NullPointerException if source or destination is <code>null</code> 930 * @throws IOException if source or destination is invalid 931 * @throws IOException if an IO error occurs during copying 932 * @see #copyFile(File, File, boolean) 933 * @since 1.3 934 */ 935 public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException { 936 if (destDir == null) { 937 throw new NullPointerException("Destination must not be null"); 938 } 939 if (destDir.exists() && destDir.isDirectory() == false) { 940 throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); 941 } 942 File destFile = new File(destDir, srcFile.getName()); 943 copyFile(srcFile, destFile, preserveFileDate); 944 } 945 946 /** 947 * Copies a file to a new location preserving the file date. 948 * <p> 949 * This method copies the contents of the specified source file to the 950 * specified destination file. The directory holding the destination file is 951 * created if it does not exist. If the destination file exists, then this 952 * method will overwrite it. 953 * <p> 954 * <strong>Note:</strong> This method tries to preserve the file's last 955 * modified date/times using {@link File#setLastModified(long)}, however 956 * it is not guaranteed that the operation will succeed. 957 * If the modification operation fails, no indication is provided. 958 * 959 * @param srcFile an existing file to copy, must not be <code>null</code> 960 * @param destFile the new file, must not be <code>null</code> 961 * 962 * @throws NullPointerException if source or destination is <code>null</code> 963 * @throws IOException if source or destination is invalid 964 * @throws IOException if an IO error occurs during copying 965 * @see #copyFileToDirectory(File, File) 966 */ 967 public static void copyFile(File srcFile, File destFile) throws IOException { 968 copyFile(srcFile, destFile, true); 969 } 970 971 /** 972 * Copies a file to a new location. 973 * <p> 974 * This method copies the contents of the specified source file 975 * to the specified destination file. 976 * The directory holding the destination file is created if it does not exist. 977 * If the destination file exists, then this method will overwrite it. 978 * <p> 979 * <strong>Note:</strong> Setting <code>preserveFileDate</code> to 980 * <code>true</code> tries to preserve the file's last modified 981 * date/times using {@link File#setLastModified(long)}, however it is 982 * not guaranteed that the operation will succeed. 983 * If the modification operation fails, no indication is provided. 984 * 985 * @param srcFile an existing file to copy, must not be <code>null</code> 986 * @param destFile the new file, must not be <code>null</code> 987 * @param preserveFileDate true if the file date of the copy 988 * should be the same as the original 989 * 990 * @throws NullPointerException if source or destination is <code>null</code> 991 * @throws IOException if source or destination is invalid 992 * @throws IOException if an IO error occurs during copying 993 * @see #copyFileToDirectory(File, File, boolean) 994 */ 995 public static void copyFile(File srcFile, File destFile, 996 boolean preserveFileDate) throws IOException { 997 if (srcFile == null) { 998 throw new NullPointerException("Source must not be null"); 999 } 1000 if (destFile == null) { 1001 throw new NullPointerException("Destination must not be null"); 1002 } 1003 if (srcFile.exists() == false) { 1004 throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); 1005 } 1006 if (srcFile.isDirectory()) { 1007 throw new IOException("Source '" + srcFile + "' exists but is a directory"); 1008 } 1009 if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { 1010 throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); 1011 } 1012 File parentFile = destFile.getParentFile(); 1013 if (parentFile != null) { 1014 if (!parentFile.mkdirs() && !parentFile.isDirectory()) { 1015 throw new IOException("Destination '" + parentFile + "' directory cannot be created"); 1016 } 1017 } 1018 if (destFile.exists() && destFile.canWrite() == false) { 1019 throw new IOException("Destination '" + destFile + "' exists but is read-only"); 1020 } 1021 doCopyFile(srcFile, destFile, preserveFileDate); 1022 } 1023 1024 /** 1025 * Copy bytes from a <code>File</code> to an <code>OutputStream</code>. 1026 * <p> 1027 * This method buffers the input internally, so there is no need to use a <code>BufferedInputStream</code>. 1028 * </p> 1029 * 1030 * @param input 1031 * the <code>File</code> to read from 1032 * @param output 1033 * the <code>OutputStream</code> to write to 1034 * @return the number of bytes copied 1035 * @throws NullPointerException 1036 * if the input or output is null 1037 * @throws IOException 1038 * if an I/O error occurs 1039 * @since 2.1 1040 */ 1041 public static long copyFile(File input, OutputStream output) throws IOException { 1042 final FileInputStream fis = new FileInputStream(input); 1043 try { 1044 return IOUtils.copyLarge(fis, output); 1045 } finally { 1046 fis.close(); 1047 } 1048 } 1049 1050 /** 1051 * Internal copy file method. 1052 * 1053 * @param srcFile the validated source file, must not be <code>null</code> 1054 * @param destFile the validated destination file, must not be <code>null</code> 1055 * @param preserveFileDate whether to preserve the file date 1056 * @throws IOException if an error occurs 1057 */ 1058 private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { 1059 if (destFile.exists() && destFile.isDirectory()) { 1060 throw new IOException("Destination '" + destFile + "' exists but is a directory"); 1061 } 1062 1063 FileInputStream fis = null; 1064 FileOutputStream fos = null; 1065 FileChannel input = null; 1066 FileChannel output = null; 1067 try { 1068 fis = new FileInputStream(srcFile); 1069 fos = new FileOutputStream(destFile); 1070 input = fis.getChannel(); 1071 output = fos.getChannel(); 1072 long size = input.size(); 1073 long pos = 0; 1074 long count = 0; 1075 while (pos < size) { 1076 count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos; 1077 pos += output.transferFrom(input, pos, count); 1078 } 1079 } finally { 1080 IOUtils.closeQuietly(output); 1081 IOUtils.closeQuietly(fos); 1082 IOUtils.closeQuietly(input); 1083 IOUtils.closeQuietly(fis); 1084 } 1085 1086 if (srcFile.length() != destFile.length()) { 1087 throw new IOException("Failed to copy full contents from '" + 1088 srcFile + "' to '" + destFile + "'"); 1089 } 1090 if (preserveFileDate) { 1091 destFile.setLastModified(srcFile.lastModified()); 1092 } 1093 } 1094 1095 //----------------------------------------------------------------------- 1096 /** 1097 * Copies a directory to within another directory preserving the file dates. 1098 * <p> 1099 * This method copies the source directory and all its contents to a 1100 * directory of the same name in the specified destination directory. 1101 * <p> 1102 * The destination directory is created if it does not exist. 1103 * If the destination directory did exist, then this method merges 1104 * the source with the destination, with the source taking precedence. 1105 * <p> 1106 * <strong>Note:</strong> This method tries to preserve the files' last 1107 * modified date/times using {@link File#setLastModified(long)}, however 1108 * it is not guaranteed that those operations will succeed. 1109 * If the modification operation fails, no indication is provided. 1110 * 1111 * @param srcDir an existing directory to copy, must not be <code>null</code> 1112 * @param destDir the directory to place the copy in, must not be <code>null</code> 1113 * 1114 * @throws NullPointerException if source or destination is <code>null</code> 1115 * @throws IOException if source or destination is invalid 1116 * @throws IOException if an IO error occurs during copying 1117 * @since 1.2 1118 */ 1119 public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException { 1120 if (srcDir == null) { 1121 throw new NullPointerException("Source must not be null"); 1122 } 1123 if (srcDir.exists() && srcDir.isDirectory() == false) { 1124 throw new IllegalArgumentException("Source '" + destDir + "' is not a directory"); 1125 } 1126 if (destDir == null) { 1127 throw new NullPointerException("Destination must not be null"); 1128 } 1129 if (destDir.exists() && destDir.isDirectory() == false) { 1130 throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); 1131 } 1132 copyDirectory(srcDir, new File(destDir, srcDir.getName()), true); 1133 } 1134 1135 /** 1136 * Copies a whole directory to a new location preserving the file dates. 1137 * <p> 1138 * This method copies the specified directory and all its child 1139 * directories and files to the specified destination. 1140 * The destination is the new location and name of the directory. 1141 * <p> 1142 * The destination directory is created if it does not exist. 1143 * If the destination directory did exist, then this method merges 1144 * the source with the destination, with the source taking precedence. 1145 * <p> 1146 * <strong>Note:</strong> This method tries to preserve the files' last 1147 * modified date/times using {@link File#setLastModified(long)}, however 1148 * it is not guaranteed that those operations will succeed. 1149 * If the modification operation fails, no indication is provided. 1150 * 1151 * @param srcDir an existing directory to copy, must not be <code>null</code> 1152 * @param destDir the new directory, must not be <code>null</code> 1153 * 1154 * @throws NullPointerException if source or destination is <code>null</code> 1155 * @throws IOException if source or destination is invalid 1156 * @throws IOException if an IO error occurs during copying 1157 * @since 1.1 1158 */ 1159 public static void copyDirectory(File srcDir, File destDir) throws IOException { 1160 copyDirectory(srcDir, destDir, true); 1161 } 1162 1163 /** 1164 * Copies a whole directory to a new location. 1165 * <p> 1166 * This method copies the contents of the specified source directory 1167 * to within the specified destination directory. 1168 * <p> 1169 * The destination directory is created if it does not exist. 1170 * If the destination directory did exist, then this method merges 1171 * the source with the destination, with the source taking precedence. 1172 * <p> 1173 * <strong>Note:</strong> Setting <code>preserveFileDate</code> to 1174 * <code>true</code> tries to preserve the files' last modified 1175 * date/times using {@link File#setLastModified(long)}, however it is 1176 * not guaranteed that those operations will succeed. 1177 * If the modification operation fails, no indication is provided. 1178 * 1179 * @param srcDir an existing directory to copy, must not be <code>null</code> 1180 * @param destDir the new directory, must not be <code>null</code> 1181 * @param preserveFileDate true if the file date of the copy 1182 * should be the same as the original 1183 * 1184 * @throws NullPointerException if source or destination is <code>null</code> 1185 * @throws IOException if source or destination is invalid 1186 * @throws IOException if an IO error occurs during copying 1187 * @since 1.1 1188 */ 1189 public static void copyDirectory(File srcDir, File destDir, 1190 boolean preserveFileDate) throws IOException { 1191 copyDirectory(srcDir, destDir, null, preserveFileDate); 1192 } 1193 1194 /** 1195 * Copies a filtered directory to a new location preserving the file dates. 1196 * <p> 1197 * This method copies the contents of the specified source directory 1198 * to within the specified destination directory. 1199 * <p> 1200 * The destination directory is created if it does not exist. 1201 * If the destination directory did exist, then this method merges 1202 * the source with the destination, with the source taking precedence. 1203 * <p> 1204 * <strong>Note:</strong> This method tries to preserve the files' last 1205 * modified date/times using {@link File#setLastModified(long)}, however 1206 * it is not guaranteed that those operations will succeed. 1207 * If the modification operation fails, no indication is provided. 1208 * 1209 * <h4>Example: Copy directories only</h4> 1210 * <pre> 1211 * // only copy the directory structure 1212 * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY); 1213 * </pre> 1214 * 1215 * <h4>Example: Copy directories and txt files</h4> 1216 * <pre> 1217 * // Create a filter for ".txt" files 1218 * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt"); 1219 * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter); 1220 * 1221 * // Create a filter for either directories or ".txt" files 1222 * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles); 1223 * 1224 * // Copy using the filter 1225 * FileUtils.copyDirectory(srcDir, destDir, filter); 1226 * </pre> 1227 * 1228 * @param srcDir an existing directory to copy, must not be <code>null</code> 1229 * @param destDir the new directory, must not be <code>null</code> 1230 * @param filter the filter to apply, null means copy all directories and files 1231 * should be the same as the original 1232 * 1233 * @throws NullPointerException if source or destination is <code>null</code> 1234 * @throws IOException if source or destination is invalid 1235 * @throws IOException if an IO error occurs during copying 1236 * @since 1.4 1237 */ 1238 public static void copyDirectory(File srcDir, File destDir, 1239 FileFilter filter) throws IOException { 1240 copyDirectory(srcDir, destDir, filter, true); 1241 } 1242 1243 /** 1244 * Copies a filtered directory to a new location. 1245 * <p> 1246 * This method copies the contents of the specified source directory 1247 * to within the specified destination directory. 1248 * <p> 1249 * The destination directory is created if it does not exist. 1250 * If the destination directory did exist, then this method merges 1251 * the source with the destination, with the source taking precedence. 1252 * <p> 1253 * <strong>Note:</strong> Setting <code>preserveFileDate</code> to 1254 * <code>true</code> tries to preserve the files' last modified 1255 * date/times using {@link File#setLastModified(long)}, however it is 1256 * not guaranteed that those operations will succeed. 1257 * If the modification operation fails, no indication is provided. 1258 * 1259 * <h4>Example: Copy directories only</h4> 1260 * <pre> 1261 * // only copy the directory structure 1262 * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false); 1263 * </pre> 1264 * 1265 * <h4>Example: Copy directories and txt files</h4> 1266 * <pre> 1267 * // Create a filter for ".txt" files 1268 * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt"); 1269 * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter); 1270 * 1271 * // Create a filter for either directories or ".txt" files 1272 * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles); 1273 * 1274 * // Copy using the filter 1275 * FileUtils.copyDirectory(srcDir, destDir, filter, false); 1276 * </pre> 1277 * 1278 * @param srcDir an existing directory to copy, must not be <code>null</code> 1279 * @param destDir the new directory, must not be <code>null</code> 1280 * @param filter the filter to apply, null means copy all directories and files 1281 * @param preserveFileDate true if the file date of the copy 1282 * should be the same as the original 1283 * 1284 * @throws NullPointerException if source or destination is <code>null</code> 1285 * @throws IOException if source or destination is invalid 1286 * @throws IOException if an IO error occurs during copying 1287 * @since 1.4 1288 */ 1289 public static void copyDirectory(File srcDir, File destDir, 1290 FileFilter filter, boolean preserveFileDate) throws IOException { 1291 if (srcDir == null) { 1292 throw new NullPointerException("Source must not be null"); 1293 } 1294 if (destDir == null) { 1295 throw new NullPointerException("Destination must not be null"); 1296 } 1297 if (srcDir.exists() == false) { 1298 throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); 1299 } 1300 if (srcDir.isDirectory() == false) { 1301 throw new IOException("Source '" + srcDir + "' exists but is not a directory"); 1302 } 1303 if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) { 1304 throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same"); 1305 } 1306 1307 // Cater for destination being directory within the source directory (see IO-141) 1308 List<String> exclusionList = null; 1309 if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { 1310 File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); 1311 if (srcFiles != null && srcFiles.length > 0) { 1312 exclusionList = new ArrayList<String>(srcFiles.length); 1313 for (File srcFile : srcFiles) { 1314 File copiedFile = new File(destDir, srcFile.getName()); 1315 exclusionList.add(copiedFile.getCanonicalPath()); 1316 } 1317 } 1318 } 1319 doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList); 1320 } 1321 1322 /** 1323 * Internal copy directory method. 1324 * 1325 * @param srcDir the validated source directory, must not be <code>null</code> 1326 * @param destDir the validated destination directory, must not be <code>null</code> 1327 * @param filter the filter to apply, null means copy all directories and files 1328 * @param preserveFileDate whether to preserve the file date 1329 * @param exclusionList List of files and directories to exclude from the copy, may be null 1330 * @throws IOException if an error occurs 1331 * @since 1.1 1332 */ 1333 private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter, 1334 boolean preserveFileDate, List<String> exclusionList) throws IOException { 1335 // recurse 1336 File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); 1337 if (srcFiles == null) { // null if abstract pathname does not denote a directory, or if an I/O error occurs 1338 throw new IOException("Failed to list contents of " + srcDir); 1339 } 1340 if (destDir.exists()) { 1341 if (destDir.isDirectory() == false) { 1342 throw new IOException("Destination '" + destDir + "' exists but is not a directory"); 1343 } 1344 } else { 1345 if (!destDir.mkdirs() && !destDir.isDirectory()) { 1346 throw new IOException("Destination '" + destDir + "' directory cannot be created"); 1347 } 1348 } 1349 if (destDir.canWrite() == false) { 1350 throw new IOException("Destination '" + destDir + "' cannot be written to"); 1351 } 1352 for (File srcFile : srcFiles) { 1353 File dstFile = new File(destDir, srcFile.getName()); 1354 if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) { 1355 if (srcFile.isDirectory()) { 1356 doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList); 1357 } else { 1358 doCopyFile(srcFile, dstFile, preserveFileDate); 1359 } 1360 } 1361 } 1362 1363 // Do this last, as the above has probably affected directory metadata 1364 if (preserveFileDate) { 1365 destDir.setLastModified(srcDir.lastModified()); 1366 } 1367 } 1368 1369 //----------------------------------------------------------------------- 1370 /** 1371 * Copies bytes from the URL <code>source</code> to a file 1372 * <code>destination</code>. The directories up to <code>destination</code> 1373 * will be created if they don't already exist. <code>destination</code> 1374 * will be overwritten if it already exists. 1375 * <p> 1376 * Warning: this method does not set a connection or read timeout and thus 1377 * might block forever. Use {@link #copyURLToFile(URL, File, int, int)} 1378 * with reasonable timeouts to prevent this. 1379 * 1380 * @param source the <code>URL</code> to copy bytes from, must not be <code>null</code> 1381 * @param destination the non-directory <code>File</code> to write bytes to 1382 * (possibly overwriting), must not be <code>null</code> 1383 * @throws IOException if <code>source</code> URL cannot be opened 1384 * @throws IOException if <code>destination</code> is a directory 1385 * @throws IOException if <code>destination</code> cannot be written 1386 * @throws IOException if <code>destination</code> needs creating but can't be 1387 * @throws IOException if an IO error occurs during copying 1388 */ 1389 public static void copyURLToFile(URL source, File destination) throws IOException { 1390 InputStream input = source.openStream(); 1391 copyInputStreamToFile(input, destination); 1392 } 1393 1394 /** 1395 * Copies bytes from the URL <code>source</code> to a file 1396 * <code>destination</code>. The directories up to <code>destination</code> 1397 * will be created if they don't already exist. <code>destination</code> 1398 * will be overwritten if it already exists. 1399 * 1400 * @param source the <code>URL</code> to copy bytes from, must not be <code>null</code> 1401 * @param destination the non-directory <code>File</code> to write bytes to 1402 * (possibly overwriting), must not be <code>null</code> 1403 * @param connectionTimeout the number of milliseconds until this method 1404 * will timeout if no connection could be established to the <code>source</code> 1405 * @param readTimeout the number of milliseconds until this method will 1406 * timeout if no data could be read from the <code>source</code> 1407 * @throws IOException if <code>source</code> URL cannot be opened 1408 * @throws IOException if <code>destination</code> is a directory 1409 * @throws IOException if <code>destination</code> cannot be written 1410 * @throws IOException if <code>destination</code> needs creating but can't be 1411 * @throws IOException if an IO error occurs during copying 1412 * @since 2.0 1413 */ 1414 public static void copyURLToFile(URL source, File destination, 1415 int connectionTimeout, int readTimeout) throws IOException { 1416 URLConnection connection = source.openConnection(); 1417 connection.setConnectTimeout(connectionTimeout); 1418 connection.setReadTimeout(readTimeout); 1419 InputStream input = connection.getInputStream(); 1420 copyInputStreamToFile(input, destination); 1421 } 1422 1423 /** 1424 * Copies bytes from an {@link InputStream} <code>source</code> to a file 1425 * <code>destination</code>. The directories up to <code>destination</code> 1426 * will be created if they don't already exist. <code>destination</code> 1427 * will be overwritten if it already exists. 1428 * 1429 * @param source the <code>InputStream</code> to copy bytes from, must not be <code>null</code> 1430 * @param destination the non-directory <code>File</code> to write bytes to 1431 * (possibly overwriting), must not be <code>null</code> 1432 * @throws IOException if <code>destination</code> is a directory 1433 * @throws IOException if <code>destination</code> cannot be written 1434 * @throws IOException if <code>destination</code> needs creating but can't be 1435 * @throws IOException if an IO error occurs during copying 1436 * @since 2.0 1437 */ 1438 public static void copyInputStreamToFile(InputStream source, File destination) throws IOException { 1439 try { 1440 FileOutputStream output = openOutputStream(destination); 1441 try { 1442 IOUtils.copy(source, output); 1443 output.close(); // don't swallow close Exception if copy completes normally 1444 } finally { 1445 IOUtils.closeQuietly(output); 1446 } 1447 } finally { 1448 IOUtils.closeQuietly(source); 1449 } 1450 } 1451 1452 //----------------------------------------------------------------------- 1453 /** 1454 * Deletes a directory recursively. 1455 * 1456 * @param directory directory to delete 1457 * @throws IOException in case deletion is unsuccessful 1458 */ 1459 public static void deleteDirectory(File directory) throws IOException { 1460 if (!directory.exists()) { 1461 return; 1462 } 1463 1464 if (!isSymlink(directory)) { 1465 cleanDirectory(directory); 1466 } 1467 1468 if (!directory.delete()) { 1469 String message = 1470 "Unable to delete directory " + directory + "."; 1471 throw new IOException(message); 1472 } 1473 } 1474 1475 /** 1476 * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. 1477 * <p> 1478 * The difference between File.delete() and this method are: 1479 * <ul> 1480 * <li>A directory to be deleted does not have to be empty.</li> 1481 * <li>No exceptions are thrown when a file or directory cannot be deleted.</li> 1482 * </ul> 1483 * 1484 * @param file file or directory to delete, can be <code>null</code> 1485 * @return <code>true</code> if the file or directory was deleted, otherwise 1486 * <code>false</code> 1487 * 1488 * @since 1.4 1489 */ 1490 public static boolean deleteQuietly(File file) { 1491 if (file == null) { 1492 return false; 1493 } 1494 try { 1495 if (file.isDirectory()) { 1496 cleanDirectory(file); 1497 } 1498 } catch (Exception ignored) { 1499 } 1500 1501 try { 1502 return file.delete(); 1503 } catch (Exception ignored) { 1504 return false; 1505 } 1506 } 1507 1508 /** 1509 * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory). 1510 * <p> 1511 * Files are normalized before comparison. 1512 * </p> 1513 * 1514 * Edge cases: 1515 * <ul> 1516 * <li>A {@code directory} must not be null: if null, throw IllegalArgumentException</li> 1517 * <li>A {@code directory} must be a directory: if not a directory, throw IllegalArgumentException</li> 1518 * <li>A directory does not contain itself: return false</li> 1519 * <li>A null child file is not contained in any parent: return false</li> 1520 * </ul> 1521 * 1522 * @param directory 1523 * the file to consider as the parent. 1524 * @param child 1525 * the file to consider as the child. 1526 * @return true is the candidate leaf is under by the specified composite. False otherwise. 1527 * @throws IOException 1528 * if an IO error occurs while checking the files. 1529 * @since 2.2 1530 * @see FilenameUtils#directoryContains(String, String) 1531 */ 1532 public static boolean directoryContains(final File directory, final File child) throws IOException { 1533 1534 // Fail fast against NullPointerException 1535 if (directory == null) { 1536 throw new IllegalArgumentException("Directory must not be null"); 1537 } 1538 1539 if (!directory.isDirectory()) { 1540 throw new IllegalArgumentException("Not a directory: " + directory); 1541 } 1542 1543 if (child == null) { 1544 return false; 1545 } 1546 1547 if (!directory.exists() || !child.exists()) { 1548 return false; 1549 } 1550 1551 // Canonicalize paths (normalizes relative paths) 1552 String canonicalParent = directory.getCanonicalPath(); 1553 String canonicalChild = child.getCanonicalPath(); 1554 1555 return FilenameUtils.directoryContains(canonicalParent, canonicalChild); 1556 } 1557 1558 /** 1559 * Cleans a directory without deleting it. 1560 * 1561 * @param directory directory to clean 1562 * @throws IOException in case cleaning is unsuccessful 1563 */ 1564 public static void cleanDirectory(File directory) throws IOException { 1565 if (!directory.exists()) { 1566 String message = directory + " does not exist"; 1567 throw new IllegalArgumentException(message); 1568 } 1569 1570 if (!directory.isDirectory()) { 1571 String message = directory + " is not a directory"; 1572 throw new IllegalArgumentException(message); 1573 } 1574 1575 File[] files = directory.listFiles(); 1576 if (files == null) { // null if security restricted 1577 throw new IOException("Failed to list contents of " + directory); 1578 } 1579 1580 IOException exception = null; 1581 for (File file : files) { 1582 try { 1583 forceDelete(file); 1584 } catch (IOException ioe) { 1585 exception = ioe; 1586 } 1587 } 1588 1589 if (null != exception) { 1590 throw exception; 1591 } 1592 } 1593 1594 //----------------------------------------------------------------------- 1595 /** 1596 * Waits for NFS to propagate a file creation, imposing a timeout. 1597 * <p> 1598 * This method repeatedly tests {@link File#exists()} until it returns 1599 * true up to the maximum time specified in seconds. 1600 * 1601 * @param file the file to check, must not be <code>null</code> 1602 * @param seconds the maximum time in seconds to wait 1603 * @return true if file exists 1604 * @throws NullPointerException if the file is <code>null</code> 1605 */ 1606 public static boolean waitFor(File file, int seconds) { 1607 int timeout = 0; 1608 int tick = 0; 1609 while (!file.exists()) { 1610 if (tick++ >= 10) { 1611 tick = 0; 1612 if (timeout++ > seconds) { 1613 return false; 1614 } 1615 } 1616 try { 1617 Thread.sleep(100); 1618 } catch (InterruptedException ignore) { 1619 // ignore exception 1620 } catch (Exception ex) { 1621 break; 1622 } 1623 } 1624 return true; 1625 } 1626 1627 //----------------------------------------------------------------------- 1628 /** 1629 * Reads the contents of a file into a String. 1630 * The file is always closed. 1631 * 1632 * @param file the file to read, must not be <code>null</code> 1633 * @param encoding the encoding to use, <code>null</code> means platform default 1634 * @return the file contents, never <code>null</code> 1635 * @throws IOException in case of an I/O error 1636 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1637 */ 1638 public static String readFileToString(File file, String encoding) throws IOException { 1639 InputStream in = null; 1640 try { 1641 in = openInputStream(file); 1642 return IOUtils.toString(in, encoding); 1643 } finally { 1644 IOUtils.closeQuietly(in); 1645 } 1646 } 1647 1648 1649 /** 1650 * Reads the contents of a file into a String using the default encoding for the VM. 1651 * The file is always closed. 1652 * 1653 * @param file the file to read, must not be <code>null</code> 1654 * @return the file contents, never <code>null</code> 1655 * @throws IOException in case of an I/O error 1656 * @since 1.3.1 1657 */ 1658 public static String readFileToString(File file) throws IOException { 1659 return readFileToString(file, null); 1660 } 1661 1662 /** 1663 * Reads the contents of a file into a byte array. 1664 * The file is always closed. 1665 * 1666 * @param file the file to read, must not be <code>null</code> 1667 * @return the file contents, never <code>null</code> 1668 * @throws IOException in case of an I/O error 1669 * @since 1.1 1670 */ 1671 public static byte[] readFileToByteArray(File file) throws IOException { 1672 InputStream in = null; 1673 try { 1674 in = openInputStream(file); 1675 return IOUtils.toByteArray(in, file.length()); 1676 } finally { 1677 IOUtils.closeQuietly(in); 1678 } 1679 } 1680 1681 /** 1682 * Reads the contents of a file line by line to a List of Strings. 1683 * The file is always closed. 1684 * 1685 * @param file the file to read, must not be <code>null</code> 1686 * @param encoding the encoding to use, <code>null</code> means platform default 1687 * @return the list of Strings representing each line in the file, never <code>null</code> 1688 * @throws IOException in case of an I/O error 1689 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1690 * @since 1.1 1691 */ 1692 public static List<String> readLines(File file, String encoding) throws IOException { 1693 InputStream in = null; 1694 try { 1695 in = openInputStream(file); 1696 return IOUtils.readLines(in, encoding); 1697 } finally { 1698 IOUtils.closeQuietly(in); 1699 } 1700 } 1701 1702 /** 1703 * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM. 1704 * The file is always closed. 1705 * 1706 * @param file the file to read, must not be <code>null</code> 1707 * @return the list of Strings representing each line in the file, never <code>null</code> 1708 * @throws IOException in case of an I/O error 1709 * @since 1.3 1710 */ 1711 public static List<String> readLines(File file) throws IOException { 1712 return readLines(file, null); 1713 } 1714 1715 /** 1716 * Returns an Iterator for the lines in a <code>File</code>. 1717 * <p> 1718 * This method opens an <code>InputStream</code> for the file. 1719 * When you have finished with the iterator you should close the stream 1720 * to free internal resources. This can be done by calling the 1721 * {@link LineIterator#close()} or 1722 * {@link LineIterator#closeQuietly(LineIterator)} method. 1723 * <p> 1724 * The recommended usage pattern is: 1725 * <pre> 1726 * LineIterator it = FileUtils.lineIterator(file, "UTF-8"); 1727 * try { 1728 * while (it.hasNext()) { 1729 * String line = it.nextLine(); 1730 * /// do something with line 1731 * } 1732 * } finally { 1733 * LineIterator.closeQuietly(iterator); 1734 * } 1735 * </pre> 1736 * <p> 1737 * If an exception occurs during the creation of the iterator, the 1738 * underlying stream is closed. 1739 * 1740 * @param file the file to open for input, must not be <code>null</code> 1741 * @param encoding the encoding to use, <code>null</code> means platform default 1742 * @return an Iterator of the lines in the file, never <code>null</code> 1743 * @throws IOException in case of an I/O error (file closed) 1744 * @since 1.2 1745 */ 1746 public static LineIterator lineIterator(File file, String encoding) throws IOException { 1747 InputStream in = null; 1748 try { 1749 in = openInputStream(file); 1750 return IOUtils.lineIterator(in, encoding); 1751 } catch (IOException ex) { 1752 IOUtils.closeQuietly(in); 1753 throw ex; 1754 } catch (RuntimeException ex) { 1755 IOUtils.closeQuietly(in); 1756 throw ex; 1757 } 1758 } 1759 1760 /** 1761 * Returns an Iterator for the lines in a <code>File</code> using the default encoding for the VM. 1762 * 1763 * @param file the file to open for input, must not be <code>null</code> 1764 * @return an Iterator of the lines in the file, never <code>null</code> 1765 * @throws IOException in case of an I/O error (file closed) 1766 * @since 1.3 1767 * @see #lineIterator(File, String) 1768 */ 1769 public static LineIterator lineIterator(File file) throws IOException { 1770 return lineIterator(file, null); 1771 } 1772 1773 //----------------------------------------------------------------------- 1774 /** 1775 * Writes a String to a file creating the file if it does not exist. 1776 * 1777 * NOTE: As from v1.3, the parent directories of the file will be created 1778 * if they do not exist. 1779 * 1780 * @param file the file to write 1781 * @param data the content to write to the file 1782 * @param encoding the encoding to use, <code>null</code> means platform default 1783 * @throws IOException in case of an I/O error 1784 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1785 */ 1786 public static void writeStringToFile(File file, String data, String encoding) throws IOException { 1787 writeStringToFile(file, data, encoding, false); 1788 } 1789 1790 /** 1791 * Writes a String to a file creating the file if it does not exist. 1792 * 1793 * @param file the file to write 1794 * @param data the content to write to the file 1795 * @param encoding the encoding to use, <code>null</code> means platform default 1796 * @param append if <code>true</code>, then the String will be added to the 1797 * end of the file rather than overwriting 1798 * @throws IOException in case of an I/O error 1799 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1800 * @since 2.1 1801 */ 1802 public static void writeStringToFile(File file, String data, String encoding, boolean append) throws IOException { 1803 OutputStream out = null; 1804 try { 1805 out = openOutputStream(file, append); 1806 IOUtils.write(data, out, encoding); 1807 out.close(); // don't swallow close Exception if copy completes normally 1808 } finally { 1809 IOUtils.closeQuietly(out); 1810 } 1811 } 1812 1813 /** 1814 * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. 1815 * 1816 * @param file the file to write 1817 * @param data the content to write to the file 1818 * @throws IOException in case of an I/O error 1819 */ 1820 public static void writeStringToFile(File file, String data) throws IOException { 1821 writeStringToFile(file, data, null, false); 1822 } 1823 1824 /** 1825 * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. 1826 * 1827 * @param file the file to write 1828 * @param data the content to write to the file 1829 * @param append if <code>true</code>, then the String will be added to the 1830 * end of the file rather than overwriting 1831 * @throws IOException in case of an I/O error 1832 * @since 2.1 1833 */ 1834 public static void writeStringToFile(File file, String data, boolean append) throws IOException { 1835 writeStringToFile(file, data, null, append); 1836 } 1837 1838 /** 1839 * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. 1840 * 1841 * @param file the file to write 1842 * @param data the content to write to the file 1843 * @throws IOException in case of an I/O error 1844 * @since 2.0 1845 */ 1846 public static void write(File file, CharSequence data) throws IOException { 1847 write(file, data, null, false); 1848 } 1849 1850 /** 1851 * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. 1852 * 1853 * @param file the file to write 1854 * @param data the content to write to the file 1855 * @param append if <code>true</code>, then the data will be added to the 1856 * end of the file rather than overwriting 1857 * @throws IOException in case of an I/O error 1858 * @since 2.1 1859 */ 1860 public static void write(File file, CharSequence data, boolean append) throws IOException { 1861 write(file, data, null, append); 1862 } 1863 1864 /** 1865 * Writes a CharSequence to a file creating the file if it does not exist. 1866 * 1867 * @param file the file to write 1868 * @param data the content to write to the file 1869 * @param encoding the encoding to use, <code>null</code> means platform default 1870 * @throws IOException in case of an I/O error 1871 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1872 * @since 2.0 1873 */ 1874 public static void write(File file, CharSequence data, String encoding) throws IOException { 1875 write(file, data, encoding, false); 1876 } 1877 1878 /** 1879 * Writes a CharSequence to a file creating the file if it does not exist. 1880 * 1881 * @param file the file to write 1882 * @param data the content to write to the file 1883 * @param encoding the encoding to use, <code>null</code> means platform default 1884 * @param append if <code>true</code>, then the data will be added to the 1885 * end of the file rather than overwriting 1886 * @throws IOException in case of an I/O error 1887 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1888 * @since IO 2.1 1889 */ 1890 public static void write(File file, CharSequence data, String encoding, boolean append) throws IOException { 1891 String str = data == null ? null : data.toString(); 1892 writeStringToFile(file, str, encoding, append); 1893 } 1894 1895 /** 1896 * Writes a byte array to a file creating the file if it does not exist. 1897 * <p> 1898 * NOTE: As from v1.3, the parent directories of the file will be created 1899 * if they do not exist. 1900 * 1901 * @param file the file to write to 1902 * @param data the content to write to the file 1903 * @throws IOException in case of an I/O error 1904 * @since 1.1 1905 */ 1906 public static void writeByteArrayToFile(File file, byte[] data) throws IOException { 1907 writeByteArrayToFile(file, data, false); 1908 } 1909 1910 /** 1911 * Writes a byte array to a file creating the file if it does not exist. 1912 * 1913 * @param file the file to write to 1914 * @param data the content to write to the file 1915 * @param append if <code>true</code>, then bytes will be added to the 1916 * end of the file rather than overwriting 1917 * @throws IOException in case of an I/O error 1918 * @since IO 2.1 1919 */ 1920 public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException { 1921 OutputStream out = null; 1922 try { 1923 out = openOutputStream(file, append); 1924 out.write(data); 1925 out.close(); // don't swallow close Exception if copy completes normally 1926 } finally { 1927 IOUtils.closeQuietly(out); 1928 } 1929 } 1930 1931 /** 1932 * Writes the <code>toString()</code> value of each item in a collection to 1933 * the specified <code>File</code> line by line. 1934 * The specified character encoding and the default line ending will be used. 1935 * <p> 1936 * NOTE: As from v1.3, the parent directories of the file will be created 1937 * if they do not exist. 1938 * 1939 * @param file the file to write to 1940 * @param encoding the encoding to use, <code>null</code> means platform default 1941 * @param lines the lines to write, <code>null</code> entries produce blank lines 1942 * @throws IOException in case of an I/O error 1943 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1944 * @since 1.1 1945 */ 1946 public static void writeLines(File file, String encoding, Collection<?> lines) throws IOException { 1947 writeLines(file, encoding, lines, null, false); 1948 } 1949 1950 /** 1951 * Writes the <code>toString()</code> value of each item in a collection to 1952 * the specified <code>File</code> line by line, optionally appending. 1953 * The specified character encoding and the default line ending will be used. 1954 * 1955 * @param file the file to write to 1956 * @param encoding the encoding to use, <code>null</code> means platform default 1957 * @param lines the lines to write, <code>null</code> entries produce blank lines 1958 * @param append if <code>true</code>, then the lines will be added to the 1959 * end of the file rather than overwriting 1960 * @throws IOException in case of an I/O error 1961 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 1962 * @since 2.1 1963 */ 1964 public static void writeLines(File file, String encoding, Collection<?> lines, boolean append) throws IOException { 1965 writeLines(file, encoding, lines, null, append); 1966 } 1967 1968 /** 1969 * Writes the <code>toString()</code> value of each item in a collection to 1970 * the specified <code>File</code> line by line. 1971 * The default VM encoding and the default line ending will be used. 1972 * 1973 * @param file the file to write to 1974 * @param lines the lines to write, <code>null</code> entries produce blank lines 1975 * @throws IOException in case of an I/O error 1976 * @since 1.3 1977 */ 1978 public static void writeLines(File file, Collection<?> lines) throws IOException { 1979 writeLines(file, null, lines, null, false); 1980 } 1981 1982 /** 1983 * Writes the <code>toString()</code> value of each item in a collection to 1984 * the specified <code>File</code> line by line. 1985 * The default VM encoding and the default line ending will be used. 1986 * 1987 * @param file the file to write to 1988 * @param lines the lines to write, <code>null</code> entries produce blank lines 1989 * @param append if <code>true</code>, then the lines will be added to the 1990 * end of the file rather than overwriting 1991 * @throws IOException in case of an I/O error 1992 * @since 2.1 1993 */ 1994 public static void writeLines(File file, Collection<?> lines, boolean append) throws IOException { 1995 writeLines(file, null, lines, null, append); 1996 } 1997 1998 /** 1999 * Writes the <code>toString()</code> value of each item in a collection to 2000 * the specified <code>File</code> line by line. 2001 * The specified character encoding and the line ending will be used. 2002 * <p> 2003 * NOTE: As from v1.3, the parent directories of the file will be created 2004 * if they do not exist. 2005 * 2006 * @param file the file to write to 2007 * @param encoding the encoding to use, <code>null</code> means platform default 2008 * @param lines the lines to write, <code>null</code> entries produce blank lines 2009 * @param lineEnding the line separator to use, <code>null</code> is system default 2010 * @throws IOException in case of an I/O error 2011 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 2012 * @since 1.1 2013 */ 2014 public static void writeLines(File file, String encoding, Collection<?> lines, String lineEnding) 2015 throws IOException { 2016 writeLines(file, encoding, lines, lineEnding, false); 2017 } 2018 2019 /** 2020 * Writes the <code>toString()</code> value of each item in a collection to 2021 * the specified <code>File</code> line by line. 2022 * The specified character encoding and the line ending will be used. 2023 * 2024 * @param file the file to write to 2025 * @param encoding the encoding to use, <code>null</code> means platform default 2026 * @param lines the lines to write, <code>null</code> entries produce blank lines 2027 * @param lineEnding the line separator to use, <code>null</code> is system default 2028 * @param append if <code>true</code>, then the lines will be added to the 2029 * end of the file rather than overwriting 2030 * @throws IOException in case of an I/O error 2031 * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM 2032 * @since 2.1 2033 */ 2034 public static void writeLines(File file, String encoding, Collection<?> lines, String lineEnding, boolean append) 2035 throws IOException { 2036 OutputStream out = null; 2037 try { 2038 out = openOutputStream(file, append); 2039 IOUtils.writeLines(lines, lineEnding, out, encoding); 2040 out.close(); // don't swallow close Exception if copy completes normally 2041 } finally { 2042 IOUtils.closeQuietly(out); 2043 } 2044 } 2045 2046 /** 2047 * Writes the <code>toString()</code> value of each item in a collection to 2048 * the specified <code>File</code> line by line. 2049 * The default VM encoding and the specified line ending will be used. 2050 * 2051 * @param file the file to write to 2052 * @param lines the lines to write, <code>null</code> entries produce blank lines 2053 * @param lineEnding the line separator to use, <code>null</code> is system default 2054 * @throws IOException in case of an I/O error 2055 * @since 1.3 2056 */ 2057 public static void writeLines(File file, Collection<?> lines, String lineEnding) throws IOException { 2058 writeLines(file, null, lines, lineEnding, false); 2059 } 2060 2061 /** 2062 * Writes the <code>toString()</code> value of each item in a collection to 2063 * the specified <code>File</code> line by line. 2064 * The default VM encoding and the specified line ending will be used. 2065 * 2066 * @param file the file to write to 2067 * @param lines the lines to write, <code>null</code> entries produce blank lines 2068 * @param lineEnding the line separator to use, <code>null</code> is system default 2069 * @param append if <code>true</code>, then the lines will be added to the 2070 * end of the file rather than overwriting 2071 * @throws IOException in case of an I/O error 2072 * @since 2.1 2073 */ 2074 public static void writeLines(File file, Collection<?> lines, String lineEnding, boolean append) 2075 throws IOException { 2076 writeLines(file, null, lines, lineEnding, append); 2077 } 2078 2079 //----------------------------------------------------------------------- 2080 /** 2081 * Deletes a file. If file is a directory, delete it and all sub-directories. 2082 * <p> 2083 * The difference between File.delete() and this method are: 2084 * <ul> 2085 * <li>A directory to be deleted does not have to be empty.</li> 2086 * <li>You get exceptions when a file or directory cannot be deleted. 2087 * (java.io.File methods returns a boolean)</li> 2088 * </ul> 2089 * 2090 * @param file file or directory to delete, must not be <code>null</code> 2091 * @throws NullPointerException if the directory is <code>null</code> 2092 * @throws FileNotFoundException if the file was not found 2093 * @throws IOException in case deletion is unsuccessful 2094 */ 2095 public static void forceDelete(File file) throws IOException { 2096 if (file.isDirectory()) { 2097 deleteDirectory(file); 2098 } else { 2099 boolean filePresent = file.exists(); 2100 if (!file.delete()) { 2101 if (!filePresent){ 2102 throw new FileNotFoundException("File does not exist: " + file); 2103 } 2104 String message = 2105 "Unable to delete file: " + file; 2106 throw new IOException(message); 2107 } 2108 } 2109 } 2110 2111 /** 2112 * Schedules a file to be deleted when JVM exits. 2113 * If file is directory delete it and all sub-directories. 2114 * 2115 * @param file file or directory to delete, must not be <code>null</code> 2116 * @throws NullPointerException if the file is <code>null</code> 2117 * @throws IOException in case deletion is unsuccessful 2118 */ 2119 public static void forceDeleteOnExit(File file) throws IOException { 2120 if (file.isDirectory()) { 2121 deleteDirectoryOnExit(file); 2122 } else { 2123 file.deleteOnExit(); 2124 } 2125 } 2126 2127 /** 2128 * Schedules a directory recursively for deletion on JVM exit. 2129 * 2130 * @param directory directory to delete, must not be <code>null</code> 2131 * @throws NullPointerException if the directory is <code>null</code> 2132 * @throws IOException in case deletion is unsuccessful 2133 */ 2134 private static void deleteDirectoryOnExit(File directory) throws IOException { 2135 if (!directory.exists()) { 2136 return; 2137 } 2138 2139 directory.deleteOnExit(); 2140 if (!isSymlink(directory)) { 2141 cleanDirectoryOnExit(directory); 2142 } 2143 } 2144 2145 /** 2146 * Cleans a directory without deleting it. 2147 * 2148 * @param directory directory to clean, must not be <code>null</code> 2149 * @throws NullPointerException if the directory is <code>null</code> 2150 * @throws IOException in case cleaning is unsuccessful 2151 */ 2152 private static void cleanDirectoryOnExit(File directory) throws IOException { 2153 if (!directory.exists()) { 2154 String message = directory + " does not exist"; 2155 throw new IllegalArgumentException(message); 2156 } 2157 2158 if (!directory.isDirectory()) { 2159 String message = directory + " is not a directory"; 2160 throw new IllegalArgumentException(message); 2161 } 2162 2163 File[] files = directory.listFiles(); 2164 if (files == null) { // null if security restricted 2165 throw new IOException("Failed to list contents of " + directory); 2166 } 2167 2168 IOException exception = null; 2169 for (File file : files) { 2170 try { 2171 forceDeleteOnExit(file); 2172 } catch (IOException ioe) { 2173 exception = ioe; 2174 } 2175 } 2176 2177 if (null != exception) { 2178 throw exception; 2179 } 2180 } 2181 2182 /** 2183 * Makes a directory, including any necessary but nonexistent parent 2184 * directories. If a file already exists with specified name but it is 2185 * not a directory then an IOException is thrown. 2186 * If the directory cannot be created (or does not already exist) 2187 * then an IOException is thrown. 2188 * 2189 * @param directory directory to create, must not be <code>null</code> 2190 * @throws NullPointerException if the directory is <code>null</code> 2191 * @throws IOException if the directory cannot be created or the file already exists but is not a directory 2192 */ 2193 public static void forceMkdir(File directory) throws IOException { 2194 if (directory.exists()) { 2195 if (!directory.isDirectory()) { 2196 String message = 2197 "File " 2198 + directory 2199 + " exists and is " 2200 + "not a directory. Unable to create directory."; 2201 throw new IOException(message); 2202 } 2203 } else { 2204 if (!directory.mkdirs()) { 2205 // Double-check that some other thread or process hasn't made 2206 // the directory in the background 2207 if (!directory.isDirectory()) 2208 { 2209 String message = 2210 "Unable to create directory " + directory; 2211 throw new IOException(message); 2212 } 2213 } 2214 } 2215 } 2216 2217 //----------------------------------------------------------------------- 2218 /** 2219 * Returns the size of the specified file or directory. If the provided 2220 * {@link File} is a regular file, then the file's length is returned. 2221 * If the argument is a directory, then the size of the directory is 2222 * calculated recursively. If a directory or subdirectory is security 2223 * restricted, its size will not be included. 2224 * 2225 * @param file the regular file or directory to return the size 2226 * of (must not be <code>null</code>). 2227 * 2228 * @return the length of the file, or recursive size of the directory, 2229 * provided (in bytes). 2230 * 2231 * @throws NullPointerException if the file is <code>null</code> 2232 * @throws IllegalArgumentException if the file does not exist. 2233 * 2234 * @since 2.0 2235 */ 2236 public static long sizeOf(File file) { 2237 2238 if (!file.exists()) { 2239 String message = file + " does not exist"; 2240 throw new IllegalArgumentException(message); 2241 } 2242 2243 if (file.isDirectory()) { 2244 return sizeOfDirectory(file); 2245 } else { 2246 return file.length(); 2247 } 2248 2249 } 2250 2251 /** 2252 * Counts the size of a directory recursively (sum of the length of all files). 2253 * 2254 * @param directory directory to inspect, must not be <code>null</code> 2255 * @return size of directory in bytes, 0 if directory is security restricted 2256 * @throws NullPointerException if the directory is <code>null</code> 2257 */ 2258 public static long sizeOfDirectory(File directory) { 2259 if (!directory.exists()) { 2260 String message = directory + " does not exist"; 2261 throw new IllegalArgumentException(message); 2262 } 2263 2264 if (!directory.isDirectory()) { 2265 String message = directory + " is not a directory"; 2266 throw new IllegalArgumentException(message); 2267 } 2268 2269 long size = 0; 2270 2271 File[] files = directory.listFiles(); 2272 if (files == null) { // null if security restricted 2273 return 0L; 2274 } 2275 for (File file : files) { 2276 size += sizeOf(file); 2277 } 2278 2279 return size; 2280 } 2281 2282 //----------------------------------------------------------------------- 2283 /** 2284 * Tests if the specified <code>File</code> is newer than the reference 2285 * <code>File</code>. 2286 * 2287 * @param file the <code>File</code> of which the modification date must 2288 * be compared, must not be <code>null</code> 2289 * @param reference the <code>File</code> of which the modification date 2290 * is used, must not be <code>null</code> 2291 * @return true if the <code>File</code> exists and has been modified more 2292 * recently than the reference <code>File</code> 2293 * @throws IllegalArgumentException if the file is <code>null</code> 2294 * @throws IllegalArgumentException if the reference file is <code>null</code> or doesn't exist 2295 */ 2296 public static boolean isFileNewer(File file, File reference) { 2297 if (reference == null) { 2298 throw new IllegalArgumentException("No specified reference file"); 2299 } 2300 if (!reference.exists()) { 2301 throw new IllegalArgumentException("The reference file '" 2302 + reference + "' doesn't exist"); 2303 } 2304 return isFileNewer(file, reference.lastModified()); 2305 } 2306 2307 /** 2308 * Tests if the specified <code>File</code> is newer than the specified 2309 * <code>Date</code>. 2310 * 2311 * @param file the <code>File</code> of which the modification date 2312 * must be compared, must not be <code>null</code> 2313 * @param date the date reference, must not be <code>null</code> 2314 * @return true if the <code>File</code> exists and has been modified 2315 * after the given <code>Date</code>. 2316 * @throws IllegalArgumentException if the file is <code>null</code> 2317 * @throws IllegalArgumentException if the date is <code>null</code> 2318 */ 2319 public static boolean isFileNewer(File file, Date date) { 2320 if (date == null) { 2321 throw new IllegalArgumentException("No specified date"); 2322 } 2323 return isFileNewer(file, date.getTime()); 2324 } 2325 2326 /** 2327 * Tests if the specified <code>File</code> is newer than the specified 2328 * time reference. 2329 * 2330 * @param file the <code>File</code> of which the modification date must 2331 * be compared, must not be <code>null</code> 2332 * @param timeMillis the time reference measured in milliseconds since the 2333 * epoch (00:00:00 GMT, January 1, 1970) 2334 * @return true if the <code>File</code> exists and has been modified after 2335 * the given time reference. 2336 * @throws IllegalArgumentException if the file is <code>null</code> 2337 */ 2338 public static boolean isFileNewer(File file, long timeMillis) { 2339 if (file == null) { 2340 throw new IllegalArgumentException("No specified file"); 2341 } 2342 if (!file.exists()) { 2343 return false; 2344 } 2345 return file.lastModified() > timeMillis; 2346 } 2347 2348 2349 //----------------------------------------------------------------------- 2350 /** 2351 * Tests if the specified <code>File</code> is older than the reference 2352 * <code>File</code>. 2353 * 2354 * @param file the <code>File</code> of which the modification date must 2355 * be compared, must not be <code>null</code> 2356 * @param reference the <code>File</code> of which the modification date 2357 * is used, must not be <code>null</code> 2358 * @return true if the <code>File</code> exists and has been modified before 2359 * the reference <code>File</code> 2360 * @throws IllegalArgumentException if the file is <code>null</code> 2361 * @throws IllegalArgumentException if the reference file is <code>null</code> or doesn't exist 2362 */ 2363 public static boolean isFileOlder(File file, File reference) { 2364 if (reference == null) { 2365 throw new IllegalArgumentException("No specified reference file"); 2366 } 2367 if (!reference.exists()) { 2368 throw new IllegalArgumentException("The reference file '" 2369 + reference + "' doesn't exist"); 2370 } 2371 return isFileOlder(file, reference.lastModified()); 2372 } 2373 2374 /** 2375 * Tests if the specified <code>File</code> is older than the specified 2376 * <code>Date</code>. 2377 * 2378 * @param file the <code>File</code> of which the modification date 2379 * must be compared, must not be <code>null</code> 2380 * @param date the date reference, must not be <code>null</code> 2381 * @return true if the <code>File</code> exists and has been modified 2382 * before the given <code>Date</code>. 2383 * @throws IllegalArgumentException if the file is <code>null</code> 2384 * @throws IllegalArgumentException if the date is <code>null</code> 2385 */ 2386 public static boolean isFileOlder(File file, Date date) { 2387 if (date == null) { 2388 throw new IllegalArgumentException("No specified date"); 2389 } 2390 return isFileOlder(file, date.getTime()); 2391 } 2392 2393 /** 2394 * Tests if the specified <code>File</code> is older than the specified 2395 * time reference. 2396 * 2397 * @param file the <code>File</code> of which the modification date must 2398 * be compared, must not be <code>null</code> 2399 * @param timeMillis the time reference measured in milliseconds since the 2400 * epoch (00:00:00 GMT, January 1, 1970) 2401 * @return true if the <code>File</code> exists and has been modified before 2402 * the given time reference. 2403 * @throws IllegalArgumentException if the file is <code>null</code> 2404 */ 2405 public static boolean isFileOlder(File file, long timeMillis) { 2406 if (file == null) { 2407 throw new IllegalArgumentException("No specified file"); 2408 } 2409 if (!file.exists()) { 2410 return false; 2411 } 2412 return file.lastModified() < timeMillis; 2413 } 2414 2415 //----------------------------------------------------------------------- 2416 /** 2417 * Computes the checksum of a file using the CRC32 checksum routine. 2418 * The value of the checksum is returned. 2419 * 2420 * @param file the file to checksum, must not be <code>null</code> 2421 * @return the checksum value 2422 * @throws NullPointerException if the file or checksum is <code>null</code> 2423 * @throws IllegalArgumentException if the file is a directory 2424 * @throws IOException if an IO error occurs reading the file 2425 * @since 1.3 2426 */ 2427 public static long checksumCRC32(File file) throws IOException { 2428 CRC32 crc = new CRC32(); 2429 checksum(file, crc); 2430 return crc.getValue(); 2431 } 2432 2433 /** 2434 * Computes the checksum of a file using the specified checksum object. 2435 * Multiple files may be checked using one <code>Checksum</code> instance 2436 * if desired simply by reusing the same checksum object. 2437 * For example: 2438 * <pre> 2439 * long csum = FileUtils.checksum(file, new CRC32()).getValue(); 2440 * </pre> 2441 * 2442 * @param file the file to checksum, must not be <code>null</code> 2443 * @param checksum the checksum object to be used, must not be <code>null</code> 2444 * @return the checksum specified, updated with the content of the file 2445 * @throws NullPointerException if the file or checksum is <code>null</code> 2446 * @throws IllegalArgumentException if the file is a directory 2447 * @throws IOException if an IO error occurs reading the file 2448 * @since 1.3 2449 */ 2450 public static Checksum checksum(File file, Checksum checksum) throws IOException { 2451 if (file.isDirectory()) { 2452 throw new IllegalArgumentException("Checksums can't be computed on directories"); 2453 } 2454 InputStream in = null; 2455 try { 2456 in = new CheckedInputStream(new FileInputStream(file), checksum); 2457 IOUtils.copy(in, new NullOutputStream()); 2458 } finally { 2459 IOUtils.closeQuietly(in); 2460 } 2461 return checksum; 2462 } 2463 2464 /** 2465 * Moves a directory. 2466 * <p> 2467 * When the destination directory is on another file system, do a "copy and delete". 2468 * 2469 * @param srcDir the directory to be moved 2470 * @param destDir the destination directory 2471 * @throws NullPointerException if source or destination is <code>null</code> 2472 * @throws FileExistsException if the destination directory exists 2473 * @throws IOException if source or destination is invalid 2474 * @throws IOException if an IO error occurs moving the file 2475 * @since 1.4 2476 */ 2477 public static void moveDirectory(File srcDir, File destDir) throws IOException { 2478 if (srcDir == null) { 2479 throw new NullPointerException("Source must not be null"); 2480 } 2481 if (destDir == null) { 2482 throw new NullPointerException("Destination must not be null"); 2483 } 2484 if (!srcDir.exists()) { 2485 throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); 2486 } 2487 if (!srcDir.isDirectory()) { 2488 throw new IOException("Source '" + srcDir + "' is not a directory"); 2489 } 2490 if (destDir.exists()) { 2491 throw new FileExistsException("Destination '" + destDir + "' already exists"); 2492 } 2493 boolean rename = srcDir.renameTo(destDir); 2494 if (!rename) { 2495 if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { 2496 throw new IOException("Cannot move directory: "+srcDir+" to a subdirectory of itself: "+destDir); 2497 } 2498 copyDirectory( srcDir, destDir ); 2499 deleteDirectory( srcDir ); 2500 if (srcDir.exists()) { 2501 throw new IOException("Failed to delete original directory '" + srcDir + 2502 "' after copy to '" + destDir + "'"); 2503 } 2504 } 2505 } 2506 2507 /** 2508 * Moves a directory to another directory. 2509 * 2510 * @param src the file to be moved 2511 * @param destDir the destination file 2512 * @param createDestDir If <code>true</code> create the destination directory, 2513 * otherwise if <code>false</code> throw an IOException 2514 * @throws NullPointerException if source or destination is <code>null</code> 2515 * @throws FileExistsException if the directory exists in the destination directory 2516 * @throws IOException if source or destination is invalid 2517 * @throws IOException if an IO error occurs moving the file 2518 * @since 1.4 2519 */ 2520 public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException { 2521 if (src == null) { 2522 throw new NullPointerException("Source must not be null"); 2523 } 2524 if (destDir == null) { 2525 throw new NullPointerException("Destination directory must not be null"); 2526 } 2527 if (!destDir.exists() && createDestDir) { 2528 destDir.mkdirs(); 2529 } 2530 if (!destDir.exists()) { 2531 throw new FileNotFoundException("Destination directory '" + destDir + 2532 "' does not exist [createDestDir=" + createDestDir +"]"); 2533 } 2534 if (!destDir.isDirectory()) { 2535 throw new IOException("Destination '" + destDir + "' is not a directory"); 2536 } 2537 moveDirectory(src, new File(destDir, src.getName())); 2538 2539 } 2540 2541 /** 2542 * Moves a file. 2543 * <p> 2544 * When the destination file is on another file system, do a "copy and delete". 2545 * 2546 * @param srcFile the file to be moved 2547 * @param destFile the destination file 2548 * @throws NullPointerException if source or destination is <code>null</code> 2549 * @throws FileExistsException if the destination file exists 2550 * @throws IOException if source or destination is invalid 2551 * @throws IOException if an IO error occurs moving the file 2552 * @since 1.4 2553 */ 2554 public static void moveFile(File srcFile, File destFile) throws IOException { 2555 if (srcFile == null) { 2556 throw new NullPointerException("Source must not be null"); 2557 } 2558 if (destFile == null) { 2559 throw new NullPointerException("Destination must not be null"); 2560 } 2561 if (!srcFile.exists()) { 2562 throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); 2563 } 2564 if (srcFile.isDirectory()) { 2565 throw new IOException("Source '" + srcFile + "' is a directory"); 2566 } 2567 if (destFile.exists()) { 2568 throw new FileExistsException("Destination '" + destFile + "' already exists"); 2569 } 2570 if (destFile.isDirectory()) { 2571 throw new IOException("Destination '" + destFile + "' is a directory"); 2572 } 2573 boolean rename = srcFile.renameTo(destFile); 2574 if (!rename) { 2575 copyFile( srcFile, destFile ); 2576 if (!srcFile.delete()) { 2577 FileUtils.deleteQuietly(destFile); 2578 throw new IOException("Failed to delete original file '" + srcFile + 2579 "' after copy to '" + destFile + "'"); 2580 } 2581 } 2582 } 2583 2584 /** 2585 * Moves a file to a directory. 2586 * 2587 * @param srcFile the file to be moved 2588 * @param destDir the destination file 2589 * @param createDestDir If <code>true</code> create the destination directory, 2590 * otherwise if <code>false</code> throw an IOException 2591 * @throws NullPointerException if source or destination is <code>null</code> 2592 * @throws FileExistsException if the destination file exists 2593 * @throws IOException if source or destination is invalid 2594 * @throws IOException if an IO error occurs moving the file 2595 * @since 1.4 2596 */ 2597 public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException { 2598 if (srcFile == null) { 2599 throw new NullPointerException("Source must not be null"); 2600 } 2601 if (destDir == null) { 2602 throw new NullPointerException("Destination directory must not be null"); 2603 } 2604 if (!destDir.exists() && createDestDir) { 2605 destDir.mkdirs(); 2606 } 2607 if (!destDir.exists()) { 2608 throw new FileNotFoundException("Destination directory '" + destDir + 2609 "' does not exist [createDestDir=" + createDestDir +"]"); 2610 } 2611 if (!destDir.isDirectory()) { 2612 throw new IOException("Destination '" + destDir + "' is not a directory"); 2613 } 2614 moveFile(srcFile, new File(destDir, srcFile.getName())); 2615 } 2616 2617 /** 2618 * Moves a file or directory to the destination directory. 2619 * <p> 2620 * When the destination is on another file system, do a "copy and delete". 2621 * 2622 * @param src the file or directory to be moved 2623 * @param destDir the destination directory 2624 * @param createDestDir If <code>true</code> create the destination directory, 2625 * otherwise if <code>false</code> throw an IOException 2626 * @throws NullPointerException if source or destination is <code>null</code> 2627 * @throws FileExistsException if the directory or file exists in the destination directory 2628 * @throws IOException if source or destination is invalid 2629 * @throws IOException if an IO error occurs moving the file 2630 * @since 1.4 2631 */ 2632 public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException { 2633 if (src == null) { 2634 throw new NullPointerException("Source must not be null"); 2635 } 2636 if (destDir == null) { 2637 throw new NullPointerException("Destination must not be null"); 2638 } 2639 if (!src.exists()) { 2640 throw new FileNotFoundException("Source '" + src + "' does not exist"); 2641 } 2642 if (src.isDirectory()) { 2643 moveDirectoryToDirectory(src, destDir, createDestDir); 2644 } else { 2645 moveFileToDirectory(src, destDir, createDestDir); 2646 } 2647 } 2648 2649 /** 2650 * Determines whether the specified file is a Symbolic Link rather than an actual file. 2651 * <p> 2652 * Will not return true if there is a Symbolic Link anywhere in the path, 2653 * only if the specific file is. 2654 * <p> 2655 * <b>Note:</b> the current implementation always returns {@code false} if the system 2656 * is detected as Windows using {@link FilenameUtils#isSystemWindows()} 2657 * 2658 * @param file the file to check 2659 * @return true if the file is a Symbolic Link 2660 * @throws IOException if an IO error occurs while checking the file 2661 * @since 2.0 2662 */ 2663 public static boolean isSymlink(File file) throws IOException { 2664 if (file == null) { 2665 throw new NullPointerException("File must not be null"); 2666 } 2667 if (FilenameUtils.isSystemWindows()) { 2668 return false; 2669 } 2670 File fileInCanonicalDir = null; 2671 if (file.getParent() == null) { 2672 fileInCanonicalDir = file; 2673 } else { 2674 File canonicalDir = file.getParentFile().getCanonicalFile(); 2675 fileInCanonicalDir = new File(canonicalDir, file.getName()); 2676 } 2677 2678 if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { 2679 return false; 2680 } else { 2681 return true; 2682 } 2683 } 2684 2685 }