6565 * @author Matthew Hawthorne
6666 * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
6767 * @author Stephen Colebourne
68+ * @author Ian Springer
6869 * @version $Id$
6970 */
7071public class FileUtils {
@@ -391,111 +392,113 @@ public static URL[] toURLs(File[] files) throws IOException {
391392
392393 //-----------------------------------------------------------------------
393394 /**
394- * Copy file from source to destination. If
395- * <code>destinationDirectory</code> does not exist, it (and any parent
396- * directories) will be created. If a file <code>source</code> in
397- * <code>destinationDirectory</code> exists, it will be overwritten.
398- * The copy will have the same file date as the original.
395+ * Copies a file to a directory preserving the file date.
396+ * <p>
397+ * This method copies the contents of the specified source file
398+ * to a file of the same name in the specified destination directory.
399+ * The destination directory is created if it does not exist.
400+ * If the destination file exists, then this method will overwrite it.
399401 *
400- * @param source An existing <code>File</code> to copy.
401- * @param destinationDirectory A directory to copy <code>source</code> into.
402+ * @param srcFile an existing file to copy, must not be null
403+ * @param destDir the directory to place the copy in, must not be null
402404 *
403- * @throws FileNotFoundException if <code>source</code> isn't a normal file.
404- * @throws IllegalArgumentException if <code>destinationDirectory</code>
405- * isn't a directory.
406- * @throws IOException if <code>source</code> does not exist, the file in
407- * <code>destinationDirectory</code> cannot be written to, or an IO error
408- * occurs during copying.
409- */
410- public static void copyFileToDirectory (
411- File source ,
412- File destinationDirectory )
413- throws IOException {
414- if (destinationDirectory .exists ()
415- && !destinationDirectory .isDirectory ()) {
416- throw new IllegalArgumentException (
417- "Destination is not a directory" );
405+ * @throws NullPointerException if source or destination is null
406+ * @throws IOException if source or destination is invalid
407+ * @throws IOException if an IO error occurs during copying
408+ * @see #copyFile
409+ */
410+ public static void copyFileToDirectory (File srcFile , File destDir ) throws IOException {
411+ if (destDir == null ) {
412+ throw new NullPointerException ("Destination must not be null" );
418413 }
419-
420- copyFile (source ,
421- new File (destinationDirectory , source .getName ()), true );
414+ if (destDir .exists () && destDir .isDirectory () == false ) {
415+ throw new IllegalArgumentException ("Destination '" + destDir + "' is not a directory" );
416+ }
417+ copyFile (srcFile , new File (destDir , srcFile .getName ()), true );
422418 }
423419
424420 /**
425- * Copy file from source to destination. The directories up to
426- * <code>destination</code> will be created if they don't already exist.
427- * <code>destination</code> will be overwritten if it already exists.
428- * The copy will have the same file date as the original.
429- *
430- * @param source An existing non-directory <code>File</code> to copy
431- * bytes from.
432- * @param destination A non-directory <code>File</code> to write bytes to
433- * (possibly overwriting).
434- *
435- * @throws IOException if <code>source</code> does not exist,
436- * <code>destination</code> cannot be written to, or an IO error occurs
437- * during copying.
438- *
439- * @throws FileNotFoundException if <code>destination</code> is a directory
440- * (use {@link #copyFileToDirectory}).
421+ * Copies a file to a new location preserving the file date.
422+ * <p>
423+ * This method copies the contents of the specified source file to the
424+ * specified destination file. The directory holding the destination file is
425+ * created if it does not exist. If the destination file exists, then this
426+ * method will overwrite it.
427+ *
428+ * @param srcFile an existing file to copy, must not be null
429+ * @param destFile the new file, must not be null
430+ *
431+ * @throws NullPointerException if source or destination is null
432+ * @throws IOException if source or destination is invalid
433+ * @throws IOException if an IO error occurs during copying
434+ * @see #copyFileToDirectory
441435 */
442- public static void copyFile (File source , File destination )
443- throws IOException {
444- copyFile (source , destination , true );
436+ public static void copyFile (File srcFile , File destFile ) throws IOException {
437+ copyFile (srcFile , destFile , true );
445438 }
446439
447-
448440 /**
449- * Copy file from source to destination. The directories up to
450- * <code>destination</code> will be created if they don't already exist.
451- * <code>destination</code> will be overwritten if it already exists.
452- *
453- * @param source An existing non-directory <code>File</code> to copy
454- * bytes from.
455- * @param destination A non-directory <code>File</code> to write bytes to
456- * (possibly overwriting).
457- * @param preserveFileDate True if the file date of the copy should be the
458- * same as the original.
441+ * Copies a file to a new location.
442+ * <p>
443+ * This method copies the contents of the specified source file
444+ * to the specified destination file.
445+ * The directory holding the destination file is created if it does not exist.
446+ * If the destination file exists, then this method will overwrite it.
459447 *
460- * @throws IOException if <code>source</code> does not exist,
461- * <code>destination</code> cannot be written to, or an IO error occurs
462- * during copying.
448+ * @param srcFile an existing file to copy, must not be null
449+ * @param destFile the new file, must not be null
450+ * @param preserveFileDate true if the file date of the copy
451+ * should be the same as the original
463452 *
464- * @throws FileNotFoundException if <code>destination</code> is a directory
465- * (use {@link #copyFileToDirectory}).
453+ * @throws NullPointerException if source or destination is null
454+ * @throws IOException if source or destination is invalid
455+ * @throws IOException if an IO error occurs during copying
456+ * @see #copyFileToDirectory
466457 */
467- public static void copyFile (File source , File destination ,
468- boolean preserveFileDate )
469- throws IOException {
470- //check source exists
471- if (!source .exists ()) {
472- String message = "File " + source + " does not exist" ;
473- throw new FileNotFoundException (message );
458+ public static void copyFile (File srcFile , File destFile ,
459+ boolean preserveFileDate ) throws IOException {
460+ if (srcFile == null ) {
461+ throw new NullPointerException ("Source must not be null" );
474462 }
475-
476- //does destinations directory exist ?
477- if (destination .getParentFile () != null
478- && !destination .getParentFile ().exists ()) {
479- destination .getParentFile ().mkdirs ();
463+ if (destFile == null ) {
464+ throw new NullPointerException ("Destination must not be null" );
480465 }
481-
482- //make sure we can write to destination
483- if (destination .exists () && !destination .canWrite ()) {
484- String message =
485- "Unable to open file " + destination + " for writing." ;
486- throw new IOException (message );
466+ if (srcFile .exists () == false ) {
467+ throw new FileNotFoundException ("Source '" + srcFile + "' does not exist" );
468+ }
469+ if (srcFile .isDirectory ()) {
470+ throw new IOException ("Source '" + srcFile + "' exists but is a directory" );
487471 }
472+ if (srcFile .getCanonicalPath ().equals (destFile .getCanonicalPath ())) {
473+ throw new IOException ("Source '" + srcFile + "' and destination '" + destFile + "' are the same" );
474+ }
475+ if (destFile .getParentFile () != null && destFile .getParentFile ().exists () == false ) {
476+ if (destFile .getParentFile ().mkdirs () == false ) {
477+ throw new IOException ("Destination '" + destFile + "' directory cannot be created" );
478+ }
479+ }
480+ if (destFile .exists () && destFile .canWrite () == false ) {
481+ throw new IOException ("Destination '" + destFile + "' exists but is read-only" );
482+ }
483+ doCopyFile (srcFile , destFile , preserveFileDate );
484+ }
488485
489- //makes sure it is not the same file
490- if (source .getCanonicalPath ().equals (destination .getCanonicalPath ())) {
491- String message =
492- "Unable to write file " + source + " on itself." ;
493- throw new IOException (message );
486+ /**
487+ * Internal copy file method.
488+ *
489+ * @param srcFile the validated source file, not null
490+ * @param destFile the validated destination file, not null
491+ * @param preserveFileDate whether to preserve the file date
492+ * @throws IOException if an error occurs
493+ */
494+ private static void doCopyFile (File srcFile , File destFile , boolean preserveFileDate ) throws IOException {
495+ if (destFile .exists () && destFile .isDirectory ()) {
496+ throw new IOException ("Destination '" + destFile + "' exists but is a directory" );
494497 }
495498
496- FileInputStream input = new FileInputStream (source );
499+ FileInputStream input = new FileInputStream (srcFile );
497500 try {
498- FileOutputStream output = new FileOutputStream (destination );
501+ FileOutputStream output = new FileOutputStream (destFile );
499502 try {
500503 IOUtils .copy (input , output );
501504 } finally {
@@ -505,21 +508,110 @@ public static void copyFile(File source, File destination,
505508 IOUtils .closeQuietly (input );
506509 }
507510
508- if (source .length () != destination .length ()) {
509- String message =
510- "Failed to copy full contents from "
511- + source
512- + " to "
513- + destination ;
514- throw new IOException (message );
511+ if (srcFile .length () != destFile .length ()) {
512+ throw new IOException ("Failed to copy full contents from '" +
513+ srcFile + "' to '" + destFile + "'" );
515514 }
516-
517515 if (preserveFileDate ) {
518- //file copy should preserve file date
519- destination .setLastModified (source .lastModified ());
516+ destFile .setLastModified (srcFile .lastModified ());
517+ }
518+ }
519+
520+ //-----------------------------------------------------------------------
521+ /**
522+ * Copies a whole directory to a new location preserving the file dates.
523+ * <p>
524+ * This method copies the specified directory and all its child
525+ * directories and files to the specified destination.
526+ * The destination is the new location and name of the directory.
527+ * If it already exists, the contents will be overwritten.
528+ *
529+ * @param srcDir an existing directory to copy, must not be null
530+ * @param destDir the new directory, must not be null
531+ *
532+ * @throws NullPointerException if source or destination is null
533+ * @throws IOException if source or destination is invalid
534+ * @throws IOException if an IO error occurs during copying
535+ */
536+ public static void copyDirectory (File srcDir , File destDir ) throws IOException {
537+ copyDirectory (srcDir , destDir , true );
538+ }
539+
540+ /**
541+ * Copies a whole directory to a new location.
542+ * <p>
543+ * This method copies the contents of the specified source directory
544+ * to within the specified destination directory.
545+ * The destination directory is created if it does not exist.
546+ * If the destination directory did exist, then this method merges
547+ * the source with the destination, with the source taking precedence.
548+ *
549+ * @param srcDir an existing directory to copy, must not be null
550+ * @param destDir the new directory, must not be null
551+ * @param preserveFileDate true if the file date of the copy
552+ * should be the same as the original
553+ *
554+ * @throws NullPointerException if source or destination is null
555+ * @throws IOException if source or destination is invalid
556+ * @throws IOException if an IO error occurs during copying
557+ */
558+ public static void copyDirectory (File srcDir , File destDir ,
559+ boolean preserveFileDate ) throws IOException {
560+ if (srcDir == null ) {
561+ throw new NullPointerException ("Source must not be null" );
562+ }
563+ if (destDir == null ) {
564+ throw new NullPointerException ("Destination must not be null" );
565+ }
566+ if (srcDir .exists () == false ) {
567+ throw new FileNotFoundException ("Source '" + srcDir + "' does not exist" );
568+ }
569+ if (srcDir .isDirectory () == false ) {
570+ throw new IOException ("Source '" + srcDir + "' exists but is not a directory" );
571+ }
572+ if (srcDir .getCanonicalPath ().equals (destDir .getCanonicalPath ())) {
573+ throw new IOException ("Source '" + srcDir + "' and destination '" + destDir + "' are the same" );
574+ }
575+ doCopyDirectory (srcDir , destDir , preserveFileDate );
576+ }
577+
578+ /**
579+ * Internal copy directory method.
580+ *
581+ * @param srcDir the validated source directory, not null
582+ * @param destDir the validated destination directory, not null
583+ * @param preserveFileDate whether to preserve the file date
584+ * @throws IOException if an error occurs
585+ */
586+ private static void doCopyDirectory (File srcDir , File destDir , boolean preserveFileDate ) throws IOException {
587+ if (destDir .exists ()) {
588+ if (destDir .isDirectory () == false ) {
589+ throw new IOException ("Destination '" + destDir + "' exists but is not a directory" );
590+ }
591+ } else {
592+ if (destDir .mkdirs () == false ) {
593+ throw new IOException ("Destination '" + destDir + "' directory cannot be created" );
594+ }
595+ if (preserveFileDate ) {
596+ destDir .setLastModified (srcDir .lastModified ());
597+ }
598+ }
599+ if (destDir .canWrite () == false ) {
600+ throw new IOException ("Destination '" + destDir + "' cannot be written to" );
601+ }
602+ // recurse
603+ File [] files = srcDir .listFiles ();
604+ for (int i = 0 ; i < files .length ; i ++) {
605+ File copiedFile = new File (destDir , files [i ].getName ());
606+ if (files [i ].isDirectory ()) {
607+ doCopyDirectory (files [i ], copiedFile , preserveFileDate );
608+ } else {
609+ doCopyFile (files [i ], copiedFile , preserveFileDate );
610+ }
520611 }
521612 }
522613
614+ //-----------------------------------------------------------------------
523615 /**
524616 * Copies bytes from the URL <code>source</code> to a file
525617 * <code>destination</code>. The directories up to <code>destination</code>
@@ -565,7 +657,7 @@ public static void copyURLToFile(URL source, File destination)
565657 }
566658 }
567659
568-
660+ //-----------------------------------------------------------------------
569661 /**
570662 * Recursively delete a directory.
571663 * @param directory directory to delete
0 commit comments