Skip to content

Commit 64320f1

Browse files
author
Stephen Colebourne
committed
Add copyDirectory, and refactor copyFile
bug 32944, from Ian Springer git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk@154553 13f79535-47bb-0310-9956-ffa450edef68
1 parent d7ab0e8 commit 64320f1

3 files changed

Lines changed: 259 additions & 96 deletions

File tree

project.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@
165165
<name>Alban Peignier</name>
166166
<email>alban.peignier at free.fr</email>
167167
</contributor>
168+
<contributor>
169+
<name>Ian Springer</name>
170+
</contributor>
168171
<contributor>
169172
<name>Masato Tezuka</name>
170173
</contributor>

src/java/org/apache/commons/io/FileUtils.java

Lines changed: 188 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
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
*/
7071
public 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

Comments
 (0)