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