Refactor FileUtils to create new APIs surfacing CopyOption.
copyDirectory(File, File, FileFilter, boolean, CopyOption...)
copyFile(File, File, boolean, CopyOption...)
Sort test methods.
MODIFY
src/main/java/org/apache/commons/io/FileUtils.java
src/main/java/org/apache/commons/io/FileUtils.java
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@@ -652,8 +653,62 @@ public static void copyDirectory(final File srcDir, final File destDir,
* @throws IOException if an IO error occurs during copying
* @since 1.4
*/
- public static void copyDirectory(final File srcDir, final File destDir,
- final FileFilter filter, final boolean preserveFileDate) throws IOException {
+ public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter,
+ final boolean preserveFileDate) throws IOException {
+ copyDirectory(srcDir, destDir, filter, preserveFileDate, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ /**
+ * Copies a filtered directory to a new location.
+ * <p>
+ * This method copies the contents of the specified source directory
+ * to within the specified destination directory.
+ * </p>
+ * <p>
+ * The destination directory is created if it does not exist.
+ * If the destination directory did exist, then this method merges
+ * the source with the destination, with the source taking precedence.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
+ * {@code true} tries to preserve the files' last modified
+ * date/times using {@link File#setLastModified(long)}, however it is
+ * not guaranteed that those operations will succeed.
+ * If the modification operation fails, no indication is provided.
+ * </p>
+ * <b>Example: Copy directories only</b>
+ * <pre>
+ * // only copy the directory structure
+ * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
+ * </pre>
+ *
+ * <b>Example: Copy directories and txt files</b>
+ * <pre>
+ * // Create a filter for ".txt" files
+ * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
+ * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
+ *
+ * // Create a filter for either directories or ".txt" files
+ * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
+ *
+ * // Copy using the filter
+ * FileUtils.copyDirectory(srcDir, destDir, filter, false);
+ * </pre>
+ *
+ * @param srcDir an existing directory to copy, must not be {@code null}
+ * @param destDir the new directory, must not be {@code null}
+ * @param filter the filter to apply, null means copy all directories and files
+ * @param preserveFileDate true if the file date of the copy
+ * should be the same as the original
+ * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
+ *
+ * @throws NullPointerException if source or destination is {@code null}
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @since 2.8.0
+ */
+ public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter,
+ final boolean preserveFileDate, final CopyOption... copyOptions) throws IOException {
checkFileRequirements(srcDir, destDir);
if (!srcDir.isDirectory()) {
throw new IOException("Source '" + srcDir + "' exists but is not a directory");
@@ -674,7 +729,7 @@ public static void copyDirectory(final File srcDir, final File destDir,
}
}
}
- doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList);
+ doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList, copyOptions);
}
//-----------------------------------------------------------------------
@@ -774,10 +829,43 @@ public static void copyFile(final File srcFile, final File destFile) throws IOEx
* @throws IOException if the output file length is not the same as the input file length after the copy
* completes
* @see #copyFileToDirectory(File, File, boolean)
- * @see #doCopyFile(File, File, boolean)
*/
- public static void copyFile(final File srcFile, final File destFile,
- final boolean preserveFileDate) throws IOException {
+ public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate)
+ throws IOException {
+ copyFile(srcFile, destFile, preserveFileDate, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ /**
+ * Copies a file to a new location.
+ * <p>
+ * This method copies the contents of the specified source file
+ * to the specified destination file.
+ * The directory holding the destination file is created if it does not exist.
+ * If the destination file exists, then this method will overwrite it.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Setting <code>preserveFileDate</code> to
+ * {@code true} tries to preserve the file's last modified
+ * date/times using {@link File#setLastModified(long)}, however it is
+ * not guaranteed that the operation will succeed.
+ * If the modification operation fails, no indication is provided.
+ * </p>
+ *
+ * @param srcFile an existing file to copy, must not be {@code null}
+ * @param destFile the new file, must not be {@code null}
+ * @param preserveFileDate true if the file date of the copy
+ * should be the same as the original
+ * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
+ * @throws NullPointerException if source or destination is {@code null}
+ * @throws IOException if source or destination is invalid
+ * @throws IOException if an IO error occurs during copying
+ * @throws IOException if the output file length is not the same as the input file length after the copy
+ * completes
+ * @see #copyFileToDirectory(File, File, boolean)
+ * @since 2.8.0
+ */
+ public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate, final CopyOption... copyOptions)
+ throws IOException {
checkFileRequirements(srcFile, destFile);
if (srcFile.isDirectory()) {
throw new IOException("Source '" + srcFile + "' exists but is a directory");
@@ -794,7 +882,7 @@ public static void copyFile(final File srcFile, final File destFile,
if (destFile.exists() && destFile.canWrite() == false) {
throw new IOException("Destination '" + destFile + "' exists but is read-only");
}
- doCopyFile(srcFile, destFile, preserveFileDate);
+ doCopyFile(srcFile, destFile, preserveFileDate, copyOptions);
}
/**
@@ -1237,12 +1325,12 @@ public static boolean directoryContains(final File directory, final File child)
* @param filter the filter to apply, null means copy all directories and files
* @param preserveFileDate whether to preserve the file date
* @param exclusionList List of files and directories to exclude from the copy, may be null
+ * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
* @return whether the operation succeeded
* @throws IOException if an error occurs
- * @since 1.1
*/
private static boolean doCopyDirectory(final File srcDir, final File destDir, final FileFilter filter,
- final boolean preserveFileDate, final List<String> exclusionList)
+ final boolean preserveFileDate, final List<String> exclusionList, final CopyOption... copyOptions)
throws IOException {
// recurse
final File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
@@ -1265,9 +1353,9 @@ private static boolean doCopyDirectory(final File srcDir, final File destDir, fi
final File dstFile = new File(destDir, srcFile.getName());
if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) {
if (srcFile.isDirectory()) {
- doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList);
+ doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList, copyOptions);
} else {
- doCopyFile(srcFile, dstFile, preserveFileDate);
+ doCopyFile(srcFile, dstFile, preserveFileDate, copyOptions);
}
}
}
@@ -1290,6 +1378,7 @@ private static boolean doCopyDirectory(final File srcDir, final File destDir, fi
* @param srcFile the validated source file, must not be {@code null}
* @param destFile the validated destination file, must not be {@code null}
* @param preserveFileDate whether to preserve the file date
+ * @param copyOptions options specifying how the copy should be done, for example {@link StandardCopyOption}.
* @return whether the operation succeeded
* @throws IOException if an error occurs
* @throws IOException if the output file length is not the same as the input file length after the
@@ -1297,7 +1386,7 @@ private static boolean doCopyDirectory(final File srcDir, final File destDir, fi
* @throws IllegalArgumentException "Negative size" if the file is truncated so that the size is less than the
* position
*/
- private static boolean doCopyFile(final File srcFile, final File destFile, final boolean preserveFileDate)
+ private static boolean doCopyFile(final File srcFile, final File destFile, final boolean preserveFileDate, CopyOption... copyOptions)
throws IOException {
if (destFile.exists() && destFile.isDirectory()) {
throw new IOException("Destination '" + destFile + "' exists but is a directory");
@@ -1306,7 +1395,7 @@ private static boolean doCopyFile(final File srcFile, final File destFile, final
final Path srcPath = srcFile.toPath();
final Path destPath = destFile.toPath();
final long newLastModifed = preserveFileDate ? srcFile.lastModified() : destFile.lastModified();
- Files.copy(srcPath, destPath, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(srcPath, destPath, copyOptions);
// TODO IO-386: Do we still need this check?
checkEqualSizes(srcFile, destFile, Files.size(srcPath), Files.size(destPath));

MODIFY
src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTestCase.java
src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTestCase.java
import java.io.File;
import java.io.IOException;
-
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.AclFileAttributeView;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
*/
public class FileUtilsCopyDirectoryToDirectoryTestCase {
+ private static void assertExceptionTypeAndMessage(final File srcDir, final File destDir,
+ final Class<? extends Exception> expectedExceptionType, final String expectedMessage) {
+ try {
+ FileUtils.copyDirectoryToDirectory(srcDir, destDir);
+ } catch (final Exception e) {
+ final String msg = e.getMessage();
+ assertEquals(expectedExceptionType, e.getClass());
+ assertEquals(expectedMessage, msg);
+ return;
+ }
+ fail();
+ }
+
+ /** Temporary folder managed by JUnit. */
@TempDir
public File temporaryFolder;
- @Test
- public void copyDirectoryToDirectoryThrowsIllegalExceptionWithCorrectMessageWhenSrcDirIsNotDirectory()
- throws IOException {
- final File srcDir = File.createTempFile("notadireotry", null, temporaryFolder);
- final File destDir = new File(temporaryFolder, "destinationDirectory");
- destDir.mkdirs();
- final String expectedMessage = String.format("Source '%s' is not a directory", srcDir);
- assertExceptionTypeAndMessage(srcDir, destDir, IllegalArgumentException.class, expectedMessage);
+ private void assertAcl(final Path sourcePath, final Path destPath) throws IOException {
+ assertEquals(Files.getFileAttributeView(sourcePath, AclFileAttributeView.class).getAcl(),
+ Files.getFileAttributeView(destPath, AclFileAttributeView.class).getAcl());
}
@Test
@@ -53,16 +66,18 @@ public void copyDirectoryToDirectoryThrowsIllegalArgumentExceptionWithCorrectMes
srcDir.mkdir();
final File destDir = new File(temporaryFolder, "notadirectory");
destDir.createNewFile();
- String expectedMessage = String.format("Destination '%s' is not a directory", destDir);
+ final String expectedMessage = String.format("Destination '%s' is not a directory", destDir);
assertExceptionTypeAndMessage(srcDir, destDir, IllegalArgumentException.class, expectedMessage);
}
@Test
- public void copyDirectoryToDirectoryThrowsNullPointerExceptionWithCorrectMessageWhenSrcDirIsNull() {
- final File srcDir = null;
- final File destinationDirectory = new File(temporaryFolder, "destinationDirectory");
- destinationDirectory.mkdir();
- assertExceptionTypeAndMessage(srcDir, destinationDirectory, NullPointerException.class, "sourceDir");
+ public void copyDirectoryToDirectoryThrowsIllegalExceptionWithCorrectMessageWhenSrcDirIsNotDirectory()
+ throws IOException {
+ final File srcDir = File.createTempFile("notadireotry", null, temporaryFolder);
+ final File destDir = new File(temporaryFolder, "destinationDirectory");
+ destDir.mkdirs();
+ final String expectedMessage = String.format("Source '%s' is not a directory", srcDir);
+ assertExceptionTypeAndMessage(srcDir, destDir, IllegalArgumentException.class, expectedMessage);
}
@Test
@@ -73,16 +88,29 @@ public void copyDirectoryToDirectoryThrowsNullPointerExceptionWithCorrectMessage
assertExceptionTypeAndMessage(srcDir, destDir, NullPointerException.class, "destinationDir");
}
- private static void assertExceptionTypeAndMessage(final File srcDir, final File destDir,
- final Class<? extends Exception> expectedExceptionType, final String expectedMessage) {
- try {
- FileUtils.copyDirectoryToDirectory(srcDir, destDir);
- } catch (final Exception e) {
- final String msg = e.getMessage();
- assertEquals(expectedExceptionType, e.getClass());
- assertEquals(expectedMessage, msg);
- return;
+ @Test
+ public void copyDirectoryToDirectoryThrowsNullPointerExceptionWithCorrectMessageWhenSrcDirIsNull() {
+ final File srcDir = null;
+ final File destinationDirectory = new File(temporaryFolder, "destinationDirectory");
+ destinationDirectory.mkdir();
+ assertExceptionTypeAndMessage(srcDir, destinationDirectory, NullPointerException.class, "sourceDir");
}
- fail();
+
+ @Test
+ public void copyFileAndCheckAcl() throws IOException {
+ final Path sourcePath = Files.createTempFile("TempOutput", ".bin");
+ final Path destPath = Paths.get(temporaryFolder.getAbsolutePath(), "SomeFile.bin");
+ // Test copy attributes without replace FIRST.
+ FileUtils.copyFile(sourcePath.toFile(), destPath.toFile(), true, StandardCopyOption.COPY_ATTRIBUTES);
+ assertAcl(sourcePath, destPath);
+ //
+ FileUtils.copyFile(sourcePath.toFile(), destPath.toFile());
+ assertAcl(sourcePath, destPath);
+ //
+ FileUtils.copyFile(sourcePath.toFile(), destPath.toFile(), true, StandardCopyOption.REPLACE_EXISTING);
+ assertAcl(sourcePath, destPath);
+ //
+ FileUtils.copyFile(sourcePath.toFile(), destPath.toFile(), true, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
+ assertAcl(sourcePath, destPath);
}
}

Refactor FileUtils to create new APIs surfacing CopyOption.

- copyDirectory(File, File, FileFilter, boolean, CopyOption...)
- copyFile(File, File, boolean, CopyOption...)
- Sort test methods.