You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2021/01/11 06:45:39 UTC

[commons-io] branch master updated: FileUtils throws IllegalArgumentException for (most) illegal input to methods.

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new 0cee29a  FileUtils throws IllegalArgumentException for (most) illegal input to methods.
0cee29a is described below

commit 0cee29aa4c1818963ed1a55058219282e89d7488
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Mon Jan 11 01:45:34 2021 -0500

    FileUtils throws IllegalArgumentException for (most) illegal input to
    methods.
    
    - There are cases where FileNotFoundException is more appropriate and
    cases where we cannot throw FileNotFoundException in order to maintain
    BC.
    - Refactor to reuse private require*() methods.
    - Check for illegal inputs early in methods with
    Objects.requireNonNull() and custom private require() methods.
    - Convert a few unit tests to assertThrows().
    - Fix [IO-699] Wrong logging in FileUtils.setLastModified.
---
 src/changes/changes.xml                            |   3 +
 src/main/java/org/apache/commons/io/FileUtils.java | 891 +++++++++++----------
 .../FileUtilsCopyDirectoryToDirectoryTestCase.java |   6 +-
 .../org/apache/commons/io/FileUtilsTestCase.java   | 258 ++----
 4 files changed, 550 insertions(+), 608 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 2396442..24802d0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -84,6 +84,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action issue="IO-600" dev="ggregory" type="fix" due-to="Abhyankar Chaubey, Gary Gregory">
         Fix getPrefixLength method for Linux filename #179.
       </action>
+      <action issue="IO-699" dev="ggregory" type="fix" due-to="tza, Gary Gregory">
+        Wrong logging in FileUtils.setLastModified.
+      </action>
       <!-- ADD -->
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add FileSystemProviders class.
diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java
index 4a64f2d..df42b84 100644
--- a/src/main/java/org/apache/commons/io/FileUtils.java
+++ b/src/main/java/org/apache/commons/io/FileUtils.java
@@ -33,6 +33,7 @@ import java.net.URLConnection;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.CopyOption;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -85,9 +86,11 @@ import org.apache.commons.io.filefilter.TrueFileFilter;
  * <li>calculating a checksum
  * </ul>
  * <p>
- * Note that a specific charset should be specified whenever possible.
- * Relying on the platform default means that the code is Locale-dependent.
- * Only use the default if the files are known to always use the platform default.
+ * Note that a specific charset should be specified whenever possible. Relying on the platform default means that the
+ * code is Locale-dependent. Only use the default if the files are known to always use the platform default.
+ * </p>
+ * <p>
+ * {@link SecurityException} are not documented in the Javadoc.
  * </p>
  * <p>
  * Origin of code: Excalibur, Alexandria, Commons-Utils
@@ -193,11 +196,13 @@ public class FileUtils {
      *
      * @param size the number of bytes
      * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes)
+     * @throws NullPointerException if the given {@code BigInteger} is {@code null}.
      * @see <a href="https://issues.apache.org/jira/browse/IO-226">IO-226 - should the rounding be changed?</a>
      * @since 2.4
      */
     // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed?
     public static String byteCountToDisplaySize(final BigInteger size) {
+        Objects.requireNonNull(size, "size");
         final String displaySize;
 
         if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) {
@@ -238,24 +243,26 @@ public class FileUtils {
     }
 
     /**
-     * Computes the checksum of a file using the specified checksum object.
-     * Multiple files may be checked using one <code>Checksum</code> instance
-     * if desired simply by reusing the same checksum object.
-     * For example:
+     * Computes the checksum of a file using the specified checksum object. Multiple files may be checked using one
+     * <code>Checksum</code> instance if desired simply by reusing the same checksum object. For example:
+     * 
      * <pre>
-     *   long csum = FileUtils.checksum(file, new CRC32()).getValue();
+     * long checksum = FileUtils.checksum(file, new CRC32()).getValue();
      * </pre>
      *
-     * @param file     the file to checksum, must not be {@code null}
+     * @param file the file to checksum, must not be {@code null}
      * @param checksum the checksum object to be used, must not be {@code null}
      * @return the checksum specified, updated with the content of the file
-     * @throws NullPointerException     if the file or checksum is {@code null}
-     * @throws IllegalArgumentException if the file is a directory
-     * @throws IOException              if an IO error occurs reading the file
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws NullPointerException if the given {@code Checksum} is {@code null}.
+     * @throws IllegalArgumentException if the given {@code File} does not exist or is not a file.
+     * @throws IOException if an IO error occurs reading the file.
      * @since 1.3
      */
     public static Checksum checksum(final File file, final Checksum checksum) throws IOException {
+        requireExistsChecked(file, "file");
         requireFile(file, "file");
+        Objects.requireNonNull(checksum, "checksum");
         try (InputStream in = new CheckedInputStream(new FileInputStream(file), checksum)) {
             IOUtils.consume(in);
         }
@@ -268,9 +275,9 @@ public class FileUtils {
      *
      * @param file the file to checksum, must not be {@code null}
      * @return the checksum value
-     * @throws NullPointerException     if the file or checksum is {@code null}
-     * @throws IllegalArgumentException if the file is a directory
-     * @throws IOException              if an IO error occurs reading the file
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if the given {@code File} does not exist or is not a file.
+     * @throws IOException              if an IO error occurs reading the file.
      * @since 1.3
      */
     public static long checksumCRC32(final File file) throws IOException {
@@ -281,12 +288,13 @@ public class FileUtils {
      * Cleans a directory without deleting it.
      *
      * @param directory directory to clean
-     * @throws IOException              in case cleaning is unsuccessful
-     * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if directory does not exist or is not a directory.
+     * @throws IOException if an I/O error occurs.
      * @see #forceDelete(File)
      */
     public static void cleanDirectory(final File directory) throws IOException {
-        final File[] files = verifiedListFiles(directory);
+        final File[] files = listFiles(directory, null);
 
         final List<Exception> causeList = new ArrayList<>();
         for (final File file : files) {
@@ -306,12 +314,13 @@ public class FileUtils {
      * Cleans a directory without deleting it.
      *
      * @param directory directory to clean, must not be {@code null}
-     * @throws NullPointerException if the directory is {@code null}
-     * @throws IOException          in case cleaning is unsuccessful
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if directory does not exist or is not a directory.
+     * @throws IOException if an I/O error occurs.
      * @see #forceDeleteOnExit(File)
      */
     private static void cleanDirectoryOnExit(final File directory) throws IOException {
-        final File[] files = verifiedListFiles(directory);
+        final File[] files = listFiles(directory, null);
 
         final List<Exception> causeList = new ArrayList<>();
         for (final File file : files) {
@@ -328,11 +337,10 @@ public class FileUtils {
     }
 
     /**
-     * Compares the contents of two files to determine if they are equal or not.
+     * Tests whether the contents of two files are equal.
      * <p>
-     * This method checks to see if the two files are different lengths
-     * or if they point to the same file, before resorting to byte-by-byte
-     * comparison of the contents.
+     * This method checks to see if the two files are different lengths or if they point to the same file, before
+     * resorting to byte-by-byte comparison of the contents.
      * </p>
      * <p>
      * Code origin: Avalon
@@ -340,16 +348,16 @@ public class FileUtils {
      *
      * @param file1 the first file
      * @param file2 the second file
-     * @return true if the content of the files are equal or they both don't
-     * exist, false otherwise
-     * @throws IOException in case of an I/O error
+     * @return true if the content of the files are equal or they both don't exist, false otherwise
+     * @throws IllegalArgumentException when an input is not a file.
+     * @throws IOException If an I/O error occurs.
      * @see org.apache.commons.io.file.PathUtils#fileContentEquals(Path,Path,java.nio.file.LinkOption[],java.nio.file.OpenOption...)
      */
     public static boolean contentEquals(final File file1, final File file2) throws IOException {
         if (file1 == null && file2 == null) {
             return true;
         }
-        if (file1 == null ^ file2 == null) {
+        if (file1 == null || file2 == null) {
             return false;
         }
         final boolean file1Exists = file1.exists();
@@ -362,10 +370,8 @@ public class FileUtils {
             return true;
         }
 
-        if (file1.isDirectory() || file2.isDirectory()) {
-            // don't want to compare directory contents
-            throw new IOException("Can't compare directories, only files");
-        }
+        requireFile(file1, "file1");
+        requireFile(file2, "file2");
 
         if (file1.length() != file2.length()) {
             // lengths differ, cannot be equal
@@ -377,8 +383,7 @@ public class FileUtils {
             return true;
         }
 
-        try (InputStream input1 = new FileInputStream(file1);
-             InputStream input2 = new FileInputStream(file2)) {
+        try (InputStream input1 = new FileInputStream(file1); InputStream input2 = new FileInputStream(file2)) {
             return IOUtils.contentEquals(input1, input2);
         }
     }
@@ -396,7 +401,9 @@ public class FileUtils {
      *                    May be null, in which case the platform default is used
      * @return true if the content of the files are equal or neither exists,
      * false otherwise
-     * @throws IOException in case of an I/O error
+     * @throws IllegalArgumentException when an input is not a file.
+     * @throws IOException in case of an I/O error.
+     * @throws UnsupportedCharsetException If the named charset is unavailable (unchecked exception).
      * @see IOUtils#contentEqualsIgnoreEOL(Reader, Reader)
      * @since 2.2
      */
@@ -405,7 +412,7 @@ public class FileUtils {
         if (file1 == null && file2 == null) {
             return true;
         }
-        if (file1 == null ^ file2 == null) {
+        if (file1 == null || file2 == null) {
             return false;
         }
         final boolean file1Exists = file1.exists();
@@ -418,18 +425,17 @@ public class FileUtils {
             return true;
         }
 
-        if (file1.isDirectory() || file2.isDirectory()) {
-            // don't want to compare directory contents
-            throw new IOException("Can't compare directories, only files");
-        }
+        requireFile(file1, "file1");
+        requireFile(file2, "file2");
 
         if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) {
             // same file
             return true;
         }
 
-        try (Reader input1 = new InputStreamReader(new FileInputStream(file1), Charsets.toCharset(charsetName));
-             Reader input2 = new InputStreamReader(new FileInputStream(file2), Charsets.toCharset(charsetName))) {
+        final Charset charset = Charsets.toCharset(charsetName);
+        try (Reader input1 = new InputStreamReader(new FileInputStream(file1), charset);
+             Reader input2 = new InputStreamReader(new FileInputStream(file2), charset)) {
             return IOUtils.contentEqualsIgnoreEOL(input1, input2);
         }
     }
@@ -443,7 +449,7 @@ public class FileUtils {
      * @return an array of java.io.File
      */
     public static File[] convertFileCollectionToFileArray(final Collection<File> files) {
-        return files.toArray(new File[files.size()]);
+        return files.toArray(EMPTY_FILE_ARRAY);
     }
 
     /**
@@ -464,9 +470,9 @@ public class FileUtils {
      *
      * @param srcDir an existing directory to copy, must not be {@code null}.
      * @param destDir the new directory, must not be {@code null}.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 1.1
      */
@@ -492,9 +498,9 @@ public class FileUtils {
      * @param srcDir an existing directory to copy, must not be {@code null}.
      * @param destDir the new directory, must not be {@code null}.
      * @param preserveFileDate true if the file date of the copy should be the same as the original.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 1.1
      */
@@ -541,14 +547,14 @@ public class FileUtils {
      * @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 should be the same as the original.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 1.4
      */
-    public static void copyDirectory(final File srcDir, final File destDir,
-                                     final FileFilter filter) throws IOException {
+    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter)
+        throws IOException {
         copyDirectory(srcDir, destDir, filter, true);
     }
 
@@ -591,9 +597,9 @@ public class FileUtils {
      * @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.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 1.4
      */
@@ -639,31 +645,27 @@ public class FileUtils {
      *
      * @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 fileFilter 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 NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 2.8.0
      */
-    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter filter,
+    public static void copyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter,
         final boolean preserveFileDate, final CopyOption... copyOptions) throws IOException {
-        requireFileRequirements(srcDir, destDir);
-        if (!srcDir.isDirectory()) {
-            throw new IOException("Source '" + srcDir + "' exists but is not a directory");
-        }
-        final String srcDirCanonicalPath = srcDir.getCanonicalPath();
-        final String destDirCanonicalPath = destDir.getCanonicalPath();
-        if (srcDirCanonicalPath.equals(destDirCanonicalPath)) {
-            throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same");
-        }
+        requireFileCopy(srcDir, destDir);
+        requireDirectory(srcDir, "srcDir");
+        requireCanonicalPathsNotEquals(srcDir, destDir);
 
         // Cater for destination being directory within the source directory (see IO-141)
         List<String> exclusionList = null;
+        final String srcDirCanonicalPath = srcDir.getCanonicalPath();
+        final String destDirCanonicalPath = destDir.getCanonicalPath();
         if (destDirCanonicalPath.startsWith(srcDirCanonicalPath)) {
-            final File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
+            final File[] srcFiles = listFiles(srcDir, fileFilter);
             if (srcFiles != null && srcFiles.length > 0) {
                 exclusionList = new ArrayList<>(srcFiles.length);
                 for (final File srcFile : srcFiles) {
@@ -672,7 +674,7 @@ public class FileUtils {
                 }
             }
         }
-        doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList, copyOptions);
+        doCopyDirectory(srcDir, destDir, fileFilter, preserveFileDate, exclusionList, copyOptions);
     }
 
     /**
@@ -693,22 +695,15 @@ public class FileUtils {
      *
      * @param sourceDir an existing directory to copy, must not be {@code null}.
      * @param destinationDir the directory to place the copy in, must not be {@code null}.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws IllegalArgumentException if {@code srcDir} or {@code destDir} is not a directory.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 1.2
      */
     public static void copyDirectoryToDirectory(final File sourceDir, final File destinationDir) throws IOException {
-        Objects.requireNonNull(sourceDir, "sourceDir");
-        if (sourceDir.exists() && sourceDir.isDirectory() == false) {
-            throw new IllegalArgumentException("Source '" + sourceDir + "' is not a directory");
-        }
-        Objects.requireNonNull(destinationDir, "destinationDir");
-        if (destinationDir.exists() && destinationDir.isDirectory() == false) {
-            throw new IllegalArgumentException("Destination '" + destinationDir + "' is not a directory");
-        }
+        requireDirectoryIfExists(sourceDir, "sourceDir");
+        requireDirectoryIfExists(destinationDir, "destinationDir");
         copyDirectory(sourceDir, new File(destinationDir, sourceDir.getName()), true);
     }
 
@@ -727,8 +722,7 @@ public class FileUtils {
      *
      * @param srcFile an existing file to copy, must not be {@code null}.
      * @param destFile the new file, must not be {@code null}.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
      * @throws IOException if source or destination is invalid.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @throws IOException if the output file length is not the same as the input file length after the copy completes.
@@ -740,7 +734,7 @@ public class FileUtils {
     }
 
     /**
-     * Copies a file to a new location.
+     * Copies an existing file to a new file 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
@@ -755,12 +749,11 @@ public class FileUtils {
      * @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.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
      * @throws IOException if source or destination is invalid.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @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 #copyFile(File, File, boolean, CopyOption...)
      */
     public static void copyFile(final File srcFile, final File destFile, final boolean preserveFileDate)
         throws IOException {
@@ -784,30 +777,23 @@ public class FileUtils {
      * @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 NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws FileNotFoundException if the source does not exist.
+     * @throws IllegalArgumentException if source is not a file.
      * @throws IOException if the output file length is not the same as the input file length after the copy completes.
-     * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
+     * @throws IOException if an I/O error occurs, or setting the last-modified time didn't succeeded.
      * @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 {
-        requireFileRequirements(srcFile, destFile);
-        if (srcFile.isDirectory()) {
-            throw new IOException("Source '" + srcFile + "' exists but is a directory");
-        }
-        if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
-            throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
-        }
-        final File parentFile = destFile.getParentFile();
-        if (parentFile != null) {
-            if (!parentFile.mkdirs() && !parentFile.isDirectory()) {
-                throw new IOException("Destination '" + parentFile + "' directory cannot be created");
-            }
-        }
-        if (destFile.exists() && destFile.canWrite() == false) {
-            throw new IOException("Destination '" + destFile + "' exists but is read-only");
+        requireFileCopy(srcFile, destFile);
+        requireFile(srcFile, "srcFile");
+        requireCanonicalPathsNotEquals(srcFile, destFile);
+        createParentDirectories(destFile);
+
+        if (destFile.exists()) {
+            requireCanWrite(destFile, "destFile");
         }
         doCopyFile(srcFile, destFile, preserveFileDate, copyOptions);
     }
@@ -821,7 +807,8 @@ public class FileUtils {
      * @param input  the <code>File</code> to read from
      * @param output the <code>OutputStream</code> to write to
      * @return the number of bytes copied
-     * @throws NullPointerException if the input or output is null
+     * @throws NullPointerException if the File is {@code null}.
+     * @throws NullPointerException if the OutputStream is {@code null}.
      * @throws IOException          if an I/O error occurs
      * @since 2.1
      */
@@ -846,9 +833,8 @@ public class FileUtils {
      *
      * @param srcFile an existing file to copy, must not be {@code null}.
      * @param destDir the directory to place the copy in, must not be {@code null}.
-     *
-     * @throws NullPointerException if source or destination is null.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if source or destination is invalid.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @see #copyFile(File, File, boolean)
      */
@@ -872,9 +858,7 @@ public class FileUtils {
      * @param sourceFile an existing file to copy, must not be {@code null}.
      * @param destinationDir the directory to place the copy in, must not be {@code null}.
      * @param preserveFileDate true if the file date of the copy should be the same as the original.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @throws IOException if the output file length is not the same as the input file length after the copy completes.
      * @see #copyFile(File, File, boolean)
@@ -882,10 +866,7 @@ public class FileUtils {
      */
     public static void copyFileToDirectory(final File sourceFile, final File destinationDir, final boolean preserveFileDate)
             throws IOException {
-        Objects.requireNonNull(destinationDir, "destinationDir");
-        if (destinationDir.exists() && destinationDir.isDirectory() == false) {
-            throw new IllegalArgumentException("Destination '" + destinationDir + "' is not a directory");
-        }
+        requireDirectoryIfExists(destinationDir, "destinationDir");
         final File destFile = new File(destinationDir, sourceFile.getName());
         copyFile(sourceFile, destFile, preserveFileDate);
     }
@@ -895,8 +876,12 @@ public class FileUtils {
      * <code>destination</code>. The directories up to <code>destination</code>
      * will be created if they don't already exist. <code>destination</code>
      * will be overwritten if it already exists.
-     * The {@code source} stream is closed.
+     * <p>
+     * <em>The {@code source} stream is closed.</em>
+     * </p>
+     * <p>
      * See {@link #copyToFile(InputStream, File)} for a method that does not close the input stream.
+     * </p>
      *
      * @param source      the <code>InputStream</code> to copy bytes from, must not be {@code null}, will be closed
      * @param destination the non-directory <code>File</code> to write bytes to
@@ -931,9 +916,9 @@ public class FileUtils {
      *
      * @param sourceFile an existing file or directory to copy, must not be {@code null}.
      * @param destinationDir the directory to place the copy in, must not be {@code null}.
-     *
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @see #copyDirectoryToDirectory(File, File)
      * @see #copyFileToDirectory(File, File)
@@ -946,7 +931,7 @@ public class FileUtils {
         } else if (sourceFile.isDirectory()) {
             copyDirectoryToDirectory(sourceFile, destinationDir);
         } else {
-            throw new IOException("The source " + sourceFile + " does not exist");
+            throw new FileNotFoundException("The source " + sourceFile + " does not exist");
         }
     }
 
@@ -967,8 +952,7 @@ public class FileUtils {
      *
      * @param sourceIterable     a existing files to copy, must not be {@code null}.
      * @param destinationDir  the directory to place the copy in, must not be {@code null}.
-     *
-     * @throws NullPointerException if source or destination is null.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
      * @throws IOException if source or destination is invalid.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @see #copyFileToDirectory(File, File)
@@ -1030,20 +1014,18 @@ public class FileUtils {
         }
     }
 
-
     /**
-     * Copies bytes from the URL <code>source</code> to a file
-     * <code>destination</code>. The directories up to <code>destination</code>
-     * will be created if they don't already exist. <code>destination</code>
-     * will be overwritten if it already exists.
+     * Copies bytes from the URL <code>source</code> to a file <code>destination</code>. The directories up to
+     * <code>destination</code> will be created if they don't already exist. <code>destination</code> will be
+     * overwritten if it already exists.
      *
-     * @param source            the <code>URL</code> to copy bytes from, must not be {@code null}
-     * @param destination       the non-directory <code>File</code> to write bytes to
-     *                          (possibly overwriting), must not be {@code null}
-     * @param connectionTimeout the number of milliseconds until this method
-     *                          will timeout if no connection could be established to the <code>source</code>
-     * @param readTimeout       the number of milliseconds until this method will
-     *                          timeout if no data could be read from the <code>source</code>
+     * @param source the <code>URL</code> to copy bytes from, must not be {@code null}
+     * @param destination the non-directory <code>File</code> to write bytes to (possibly overwriting), must not be
+     *        {@code null}
+     * @param connectionTimeoutMillis the number of milliseconds until this method will timeout if no connection could
+     *        be established to the <code>source</code>
+     * @param readTimeoutMillis the number of milliseconds until this method will timeout if no data could be read from
+     *        the <code>source</code>
      * @throws IOException if <code>source</code> URL cannot be opened
      * @throws IOException if <code>destination</code> is a directory
      * @throws IOException if <code>destination</code> cannot be written
@@ -1052,15 +1034,29 @@ public class FileUtils {
      * @since 2.0
      */
     public static void copyURLToFile(final URL source, final File destination,
-        final int connectionTimeout, final int readTimeout) throws IOException {
+        final int connectionTimeoutMillis, final int readTimeoutMillis) throws IOException {
         final URLConnection connection = source.openConnection();
-        connection.setConnectTimeout(connectionTimeout);
-        connection.setReadTimeout(readTimeout);
+        connection.setConnectTimeout(connectionTimeoutMillis);
+        connection.setReadTimeout(readTimeoutMillis);
         try (final InputStream stream = connection.getInputStream()) {
             copyInputStreamToFile(stream, destination);
         }
     }
 
+
+    /**
+     * Creates all parent directories for a File object.
+     *
+     * @param file the File that may need parents, may be null.
+     * @return The parent directory, or {@code null} if the given file does not name a parent
+     * @throws IOException if the directory was not created along with all its parent directories.
+     * @throws IOException if the given file object is not null and not a directory.
+     * @since 2.9.0
+     */
+    public static File createParentDirectories(final File file) throws IOException {
+        return mkdirs(getParentFile(file));
+    }
+
     /**
      * Decodes the specified URL as per RFC 3986, i.e. transforms
      * percent-encoded octets to characters by decoding with the UTF-8 character
@@ -1109,7 +1105,8 @@ public class FileUtils {
     }
 
     /**
-     * Deletes the given File but throws IOException if it cannot, unlike {@link File#delete()}.
+     * Deletes the given File but throws an IOException if it cannot, unlike {@link File#delete()} which returns a
+     * boolean.
      *
      * @param file The file to delete.
      * @return the given file.
@@ -1118,6 +1115,7 @@ public class FileUtils {
      * @since 2.9.0
      */
     public static File delete(final File file) throws IOException {
+        Objects.requireNonNull(file, "file");
         if (!file.delete()) {
             throw new IOException("Unable to delete " + file);
         }
@@ -1132,14 +1130,13 @@ public class FileUtils {
      * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory
      */
     public static void deleteDirectory(final File directory) throws IOException {
+        Objects.requireNonNull(directory, "directory");
         if (!directory.exists()) {
             return;
         }
-
         if (!isSymlink(directory)) {
             cleanDirectory(directory);
         }
-
         delete(directory);
     }
 
@@ -1154,7 +1151,6 @@ public class FileUtils {
         if (!directory.exists()) {
             return;
         }
-
         directory.deleteOnExit();
         if (!isSymlink(directory)) {
             cleanDirectoryOnExit(directory);
@@ -1219,7 +1215,7 @@ public class FileUtils {
      * @since 2.2
      */
     public static boolean directoryContains(final File directory, final File child) throws IOException {
-        requireDirectory(directory, "directory");
+        requireDirectoryExists(directory, "directory");
 
         if (child == null) {
             return false;
@@ -1241,37 +1237,26 @@ public class FileUtils {
      *
      * @param srcDir the validated source directory, must not be {@code null}.
      * @param destDir the validated destination directory, must not be {@code null}.
-     * @param filter the filter to apply, null means copy all directories and files.
+     * @param fileFilter 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}.
-     * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
+     * @throws IOException if the directory was not created along with all its parent directories.
+     * @throws IOException if the given file object is not a directory.
      */
-    private static void doCopyDirectory(final File srcDir, final File destDir, final FileFilter filter,
+    private static void doCopyDirectory(final File srcDir, final File destDir, final FileFilter fileFilter,
         final boolean preserveFileDate, final List<String> exclusionList, final CopyOption... copyOptions)
         throws IOException {
         // recurse
-        final File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter);
-        if (srcFiles == null) {  // null if abstract pathname does not denote a directory, or if an I/O error occurs
-            throw new IOException("Failed to list contents of " + srcDir);
-        }
-        if (destDir.exists()) {
-            if (destDir.isDirectory() == false) {
-                throw new IOException("Destination '" + destDir + "' exists but is not a directory");
-            }
-        } else {
-            if (!destDir.mkdirs() && !destDir.isDirectory()) {
-                throw new IOException("Destination '" + destDir + "' directory cannot be created");
-            }
-        }
-        if (destDir.canWrite() == false) {
-            throw new IOException("Destination '" + destDir + "' cannot be written to");
-        }
+        final File[] srcFiles = listFiles(srcDir, fileFilter);
+        requireDirectoryIfExists(destDir, "destDir");
+        mkdirs(destDir);
+        requireCanWrite(destDir, "destDir");
         for (final File srcFile : srcFiles) {
             final File dstFile = new File(destDir, srcFile.getName());
             if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) {
                 if (srcFile.isDirectory()) {
-                    doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList, copyOptions);
+                    doCopyDirectory(srcFile, dstFile, fileFilter, preserveFileDate, exclusionList, copyOptions);
                 } else {
                     doCopyFile(srcFile, dstFile, preserveFileDate, copyOptions);
                 }
@@ -1301,9 +1286,8 @@ public class FileUtils {
      */
     private static void doCopyFile(final File srcFile, final File destFile, final boolean preserveFileDate, final CopyOption... copyOptions)
         throws IOException {
-        if (destFile.exists() && destFile.isDirectory()) {
-            throw new IOException("Destination '" + destFile + "' exists but is a directory");
-        }
+        Objects.requireNonNull(srcFile, "srcFile");
+        requireFileIfExists(destFile, "destFile");
 
         final Path srcPath = srcFile.toPath();
         final Path destPath = destFile.toPath();
@@ -1311,8 +1295,6 @@ public class FileUtils {
         Files.copy(srcPath, destPath, copyOptions);
 
         // TODO IO-386: Do we still need this check?
-        requireEqualSizes(srcFile, destFile, Files.size(srcPath), Files.size(destPath));
-        // TODO IO-386: Do we still need this check?
         requireEqualSizes(srcFile, destFile, srcFile.length(), destFile.length());
 
         if (preserveFileDate) {
@@ -1336,12 +1318,13 @@ public class FileUtils {
      * @throws IOException           in case deletion is unsuccessful
      */
     public static void forceDelete(final File file) throws IOException {
+        Objects.requireNonNull(file, "file");
         final Counters.PathCounters deleteCounters;
         try {
             deleteCounters = PathUtils.delete(file.toPath(), PathUtils.EMPTY_LINK_OPTION_ARRAY,
                 StandardDeleteOption.OVERRIDE_READ_ONLY);
         } catch (final IOException e) {
-            throw new IOException("Unable to delete file: " + file, e);
+            throw new IOException("Cannot delete file: " + file, e);
         }
 
         if (deleteCounters.getFileCounter().get() < 1 && deleteCounters.getDirectoryCounter().get() < 1) {
@@ -1359,6 +1342,7 @@ public class FileUtils {
      * @throws IOException          in case deletion is unsuccessful
      */
     public static void forceDeleteOnExit(final File file) throws IOException {
+        Objects.requireNonNull(file, "file");
         if (file.isDirectory()) {
             deleteDirectoryOnExit(file);
         } else {
@@ -1374,26 +1358,12 @@ public class FileUtils {
      * then an IOException is thrown.
      *
      * @param directory directory to create, must not be {@code null}
-     * @throws NullPointerException if the directory is {@code null}
-     * @throws IOException          if the directory cannot be created or the file already exists but is not a directory
+     * @throws IOException if the directory was not created along with all its parent directories.
+     * @throws IOException if the given file object is not a directory.
+     * @throws SecurityException See {@link File#mkdirs()}.
      */
     public static void forceMkdir(final File directory) throws IOException {
-        if (directory.exists()) {
-            if (!directory.isDirectory()) {
-                throw new IOException("File "
-                        + directory
-                        + " exists and is "
-                        + "not a directory. Unable to create directory.");
-            }
-        } else {
-            if (!directory.mkdirs()) {
-                // Double-check that some other thread or process hasn't made
-                // the directory in the background
-                if (!directory.isDirectory()) {
-                    throw new IOException("Unable to create directory " + directory);
-                }
-            }
-        }
+        mkdirs(directory);
     }
 
     /**
@@ -1406,7 +1376,8 @@ public class FileUtils {
      * @since 2.5
      */
     public static void forceMkdirParent(final File file) throws IOException {
-        final File parent = file.getParentFile();
+        Objects.requireNonNull(file, "file");
+        final File parent = getParentFile(file);
         if (parent == null) {
             return;
         }
@@ -1418,7 +1389,7 @@ public class FileUtils {
      *
      * @param directory the parent directory
      * @param names the name elements
-     * @return the file
+     * @return the new file
      * @since 2.1
      */
     public static File getFile(final File directory, final String... names) {
@@ -1452,6 +1423,16 @@ public class FileUtils {
     }
 
     /**
+     * Gets the parent of the given file. The given file may be bull and a file's parent may as well be null.
+     * 
+     * @param file The file to query.
+     * @return The parent file or {@code null}.
+     */
+    private static File getParentFile(final File file) {
+        return file == null ? null : file.getParentFile();
+    }
+
+    /**
      * Returns a {@link File} representing the system temporary directory.
      *
      * @return the system temporary directory.
@@ -1798,7 +1779,6 @@ public class FileUtils {
      * @param instant the date reference
      * @return true if the {@code File} exists and has been modified before the given {@code Instant}.
      * @throws NullPointerException if the file or instant is {@code null}
-     *
      * @since 2.8.0
      */
     public static boolean isFileOlder(final File file, final Instant instant) {
@@ -1813,7 +1793,7 @@ public class FileUtils {
      * @param timeMillis the time reference measured in milliseconds since the
      *                   epoch (00:00:00 GMT, January 1, 1970)
      * @return true if the {@code File} exists and has been modified before the given time reference.
-     * @throws NullPointerException if the file is {@code null}
+     * @throws NullPointerException if the file is {@code null}.
      */
     public static boolean isFileOlder(final File file, final long timeMillis) {
         Objects.requireNonNull(file, "file");
@@ -1824,31 +1804,18 @@ public class FileUtils {
     }
 
     /**
-     * Determines whether the specified file is a Symbolic Link rather than an actual file.
+     * Tests whether the specified file is a symbolic link rather than an actual file.
      * <p>
-     * Will not return true if there is a Symbolic Link anywhere in the path,
-     * only if the specific file is.
+     * This method delegates to {@link Files#isSymbolicLink(Path path)}
      * </p>
-     * <p>
-     * When using jdk1.7, this method delegates to {@code boolean java.nio.file.Files.isSymbolicLink(Path path)}
-     * </p>
-     *
-     * <p>
-     * <b>Note:</b> the current implementation always returns {@code false} if running on
-     * jkd1.6 and the system is detected as Windows using {@link FilenameUtils#isSystemWindows()}
-     * </p>
-     * <p>
-     * For code that runs on Java 1.7 or later, use the following method instead:
-     * </p>
-     *
-     * {@code boolean java.nio.file.Files.isSymbolicLink(Path path)}
-     * @param file the file to check
-     * @return true if the file is a Symbolic Link
+     * 
+     * @param file the file to test.
+     * @return true if the file is a symbolic link, see {@link Files#isSymbolicLink(Path path)}.
      * @since 2.0
+     * @see Files#isSymbolicLink(Path)
      */
     public static boolean isSymlink(final File file) {
-        Objects.requireNonNull(file, "file");
-        return Files.isSymbolicLink(file.toPath());
+        return file != null ? Files.isSymbolicLink(file.toPath()) : false;
     }
 
     /**
@@ -1933,7 +1900,10 @@ public class FileUtils {
      *
      * @param file the file to open for input, must not be {@code null}
      * @return an Iterator of the lines in the file, never {@code null}
-     * @throws IOException in case of an I/O error (file closed)
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @see #lineIterator(File, String)
      * @since 1.3
      */
@@ -1972,7 +1942,10 @@ public class FileUtils {
      * @param file     the file to open for input, must not be {@code null}
      * @param charsetName the name of the requested charset, {@code null} means platform default
      * @return an Iterator of the lines in the file, never {@code null}
-     * @throws IOException in case of an I/O error (file closed)
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @since 1.2
      */
     public static LineIterator lineIterator(final File file, final String charsetName) throws IOException {
@@ -1998,6 +1971,26 @@ public class FileUtils {
     }
 
     /**
+     * Lists files in a directory, asserting that the supplied directory exists and is a directory.
+     *
+     * @param directory The directory to list
+     * @param fileFilter Optional file filter, may be null.
+     * @return The files in the directory, never {@code null}.
+     * @throws NullPointerException if directory is {@code null}.
+     * @throws IllegalArgumentException if directory does not exist or is not a directory.
+     * @throws IOException if an I/O error occurs.
+     */
+    private static File[] listFiles(final File directory, FileFilter fileFilter) throws IOException {
+        requireDirectoryExists(directory, "directory");
+        final File[] files = fileFilter == null ? directory.listFiles() : directory.listFiles(fileFilter);
+        if (files == null) {
+            // null if the directory does not denote a directory, or if an I/O error occurs.
+            throw new IOException("Unknown I/O error listing contents of directory: " + directory);
+        }
+        return files;
+    }
+
+    /**
      * Finds files within a given directory (and optionally its
      * subdirectories). All files found are filtered by an IOFileFilter.
      * <p>
@@ -2086,6 +2079,25 @@ public class FileUtils {
     }
 
     /**
+     * Calls {@link File#mkdirs()} and throws an exception on failure.
+     *
+     * @param directory the receiver for {@code mkdirs()}, may be null.
+     * @return the given file, may be null.
+     * @throws IOException if the directory was not created along with all its parent directories.
+     * @throws IOException if the given file object is not a directory.
+     * @throws SecurityException See {@link File#mkdirs()}.
+     * @see @see File#mkdirs()
+     */
+    private static File mkdirs(final File directory) throws IOException {
+        if (directory != null) {
+            if (!directory.mkdirs() && !directory.isDirectory()) {
+                throw new IOException("Cannot create directory '" + directory + "'.");
+            }
+        }
+        return directory;
+    }
+
+    /**
      * Moves a directory.
      * <p>
      * When the destination directory is on another file system, do a "copy and delete".
@@ -2093,22 +2105,17 @@ public class FileUtils {
      *
      * @param srcDir the directory to be moved.
      * @param destDir the destination directory.
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws FileExistsException if the destination directory exists.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 1.4
      */
     public static void moveDirectory(final File srcDir, final File destDir) throws IOException {
         validateMoveParameters(srcDir, destDir);
-        if (!srcDir.isDirectory()) {
-            throw new IOException("Source '" + srcDir + "' is not a directory");
-        }
-        if (destDir.exists()) {
-            throw new FileExistsException("Destination '" + destDir + "' already exists");
-        }
-        final boolean rename = srcDir.renameTo(destDir);
-        if (!rename) {
+        requireDirectory(srcDir, "srcDir");
+        requireAbsent(destDir, "destDir");
+        if (!srcDir.renameTo(destDir)) {
             if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath() + File.separator)) {
                 throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir);
             }
@@ -2128,9 +2135,9 @@ public class FileUtils {
      * @param destDir the destination file.
      * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
      *        IOException.
-     * @throws NullPointerException if source or destination is {@code null}.
-     * @throws FileExistsException if the directory exists in the destination directory.
-     * @throws IOException if source or destination is invalid.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws IllegalArgumentException if the source or destination is invalid.
+     * @throws FileNotFoundException if the source does not exist.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
      * @since 1.4
      */
@@ -2141,9 +2148,7 @@ public class FileUtils {
             if (destDir.exists()) {
                 throw new IOException("Destination '" + destDir + "' is not a directory");
             } else if (createDestDir) {
-                if (!destDir.mkdirs()) {
-                    throw new IOException("Could not create destination directories '" + destDir + "'");
-                }
+                mkdirs(destDir);
             } else {
                 throw new FileNotFoundException("Destination directory '" + destDir +
                         "' does not exist [createDestDir=" + createDestDir + "]");
@@ -2160,7 +2165,7 @@ public class FileUtils {
      *
      * @param srcFile the file to be moved.
      * @param destFile the destination file.
-     * @throws NullPointerException if source or destination is {@code null}.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
      * @throws FileExistsException if the destination file exists.
      * @throws IOException if source or destination is invalid.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
@@ -2168,15 +2173,8 @@ public class FileUtils {
      */
     public static void moveFile(final File srcFile, final File destFile) throws IOException {
         validateMoveParameters(srcFile, destFile);
-        if (srcFile.isDirectory()) {
-            throw new IOException("Source '" + srcFile + "' is a directory");
-        }
-        if (destFile.exists()) {
-            throw new FileExistsException("Destination '" + destFile + "' already exists");
-        }
-        if (destFile.isDirectory()) {
-            throw new IOException("Destination '" + destFile + "' is a directory");
-        }
+        requireFile(srcFile, "srcFile");
+        requireAbsent(destFile, null);
         final boolean rename = srcFile.renameTo(destFile);
         if (!rename) {
             copyFile(srcFile, destFile);
@@ -2195,7 +2193,7 @@ public class FileUtils {
      * @param destDir the destination file.
      * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
      *        IOException.
-     * @throws NullPointerException if source or destination is {@code null}.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
      * @throws FileExistsException if the destination file exists.
      * @throws IOException if source or destination is invalid.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
@@ -2205,17 +2203,10 @@ public class FileUtils {
             throws IOException {
         validateMoveParameters(srcFile, destDir);
         if (!destDir.exists() && createDestDir) {
-            if (!destDir.mkdirs()) {
-                throw new IOException("Could not create destination directories '" + destDir + "'");
-            }
-        }
-        if (!destDir.exists()) {
-            throw new FileNotFoundException("Destination directory '" + destDir +
-                    "' does not exist [createDestDir=" + createDestDir + "]");
-        }
-        if (!destDir.isDirectory()) {
-            throw new IOException("Destination '" + destDir + "' is not a directory");
+            mkdirs(destDir);
         }
+        requireExistsChecked(destDir, "destDir");
+        requireDirectory(destDir, "destDir");
         moveFile(srcFile, new File(destDir, srcFile.getName()));
     }
 
@@ -2229,7 +2220,7 @@ public class FileUtils {
      * @param destDir the destination directory.
      * @param createDestDir If {@code true} create the destination directory, otherwise if {@code false} throw an
      *        IOException.
-     * @throws NullPointerException if source or destination is {@code null}.
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
      * @throws FileExistsException if the directory or file exists in the destination directory.
      * @throws IOException if source or destination is invalid.
      * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
@@ -2246,36 +2237,26 @@ public class FileUtils {
     }
 
     /**
-     * Opens a {@link FileInputStream} for the specified file, providing better
-     * error messages than simply calling <code>new FileInputStream(file)</code>.
+     * Opens a {@link FileInputStream} for the specified file, providing better error messages than simply calling
+     * <code>new FileInputStream(file)</code>.
      * <p>
-     * At the end of the method either the stream will be successfully opened,
-     * or an exception will have been thrown.
+     * At the end of the method either the stream will be successfully opened, or an exception will have been thrown.
      * </p>
      * <p>
-     * An exception is thrown if the file does not exist.
-     * An exception is thrown if the file object exists but is a directory.
-     * An exception is thrown if the file exists but cannot be read.
+     * An exception is thrown if the file does not exist. An exception is thrown if the file object exists but is a
+     * directory. An exception is thrown if the file exists but cannot be read.
      * </p>
      *
      * @param file the file to open for input, must not be {@code null}
      * @return a new {@link FileInputStream} for the specified file
-     * @throws FileNotFoundException if the file does not exist
-     * @throws IOException           if the file object is a directory
-     * @throws IOException           if the file cannot be read
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException See FileNotFoundException above, FileNotFoundException is a subclass of IOException.
      * @since 1.3
      */
     public static FileInputStream openInputStream(final File file) throws IOException {
-        if (file.exists()) {
-            if (file.isDirectory()) {
-                throw new IOException("File '" + file + "' exists but is a directory");
-            }
-            if (file.canRead() == false) {
-                throw new IOException("File '" + file + "' cannot be read");
-            }
-        } else {
-            throw new FileNotFoundException("File '" + file + "' does not exist");
-        }
+        Objects.requireNonNull(file, "file");
         return new FileInputStream(file);
     }
 
@@ -2324,26 +2305,19 @@ public class FileUtils {
      * @param append if {@code true}, then bytes will be added to the
      *               end of the file rather than overwriting
      * @return a new {@link FileOutputStream} for the specified file
-     * @throws IOException if the file object is a directory
-     * @throws IOException if the file cannot be written to
-     * @throws IOException if a parent directory needs creating but that fails
+     * @throws NullPointerException if the file object is {@code null}.
+     * @throws IllegalArgumentException if the file object is a directory
+     * @throws IllegalArgumentException if the file is not writable.
+     * @throws IOException if the directories could not be created.
      * @since 2.1
      */
     public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
+        Objects.requireNonNull(file, "file");
         if (file.exists()) {
-            if (file.isDirectory()) {
-                throw new IOException("File '" + file + "' exists but is a directory");
-            }
-            if (file.canWrite() == false) {
-                throw new IOException("File '" + file + "' cannot be written to");
-            }
+            requireFile(file, "file");
+            requireCanWrite(file, "file");
         } else {
-            final File parent = file.getParentFile();
-            if (parent != null) {
-                if (!parent.mkdirs() && !parent.isDirectory()) {
-                    throw new IOException("Directory '" + parent + "' could not be created");
-                }
-            }
+            createParentDirectories(file);
         }
         return new FileOutputStream(file, append);
     }
@@ -2354,7 +2328,10 @@ public class FileUtils {
      *
      * @param file the file to read, must not be {@code null}
      * @return the file contents, never {@code null}
-     * @throws IOException in case of an I/O error
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @since 1.1
      */
     public static byte[] readFileToByteArray(final File file) throws IOException {
@@ -2371,7 +2348,10 @@ public class FileUtils {
      *
      * @param file the file to read, must not be {@code null}
      * @return the file contents, never {@code null}
-     * @throws IOException in case of an I/O error
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @since 1.3.1
      * @deprecated 2.5 use {@link #readFileToString(File, Charset)} instead (and specify the appropriate encoding)
      */
@@ -2387,7 +2367,10 @@ public class FileUtils {
      * @param file     the file to read, must not be {@code null}
      * @param charsetName the name of the requested charset, {@code null} means platform default
      * @return the file contents, never {@code null}
-     * @throws IOException in case of an I/O error
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @since 2.3
      */
     public static String readFileToString(final File file, final Charset charsetName) throws IOException {
@@ -2402,9 +2385,12 @@ public class FileUtils {
      * @param file     the file to read, must not be {@code null}
      * @param charsetName the name of the requested charset, {@code null} means platform default
      * @return the file contents, never {@code null}
-     * @throws IOException                 in case of an I/O error
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
-     * .UnsupportedEncodingException} in version 2.2 if the encoding is not supported.
+     * .UnsupportedEncodingException} in version 2.2 if the named charset is unavailable.
      * @since 2.3
      */
     public static String readFileToString(final File file, final String charsetName) throws IOException {
@@ -2417,7 +2403,10 @@ public class FileUtils {
      *
      * @param file the file to read, must not be {@code null}
      * @return the list of Strings representing each line in the file, never {@code null}
-     * @throws IOException in case of an I/O error
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @since 1.3
      * @deprecated 2.5 use {@link #readLines(File, Charset)} instead (and specify the appropriate encoding)
      */
@@ -2426,7 +2415,6 @@ public class FileUtils {
         return readLines(file, Charset.defaultCharset());
     }
 
-
     /**
      * Reads the contents of a file line by line to a List of Strings.
      * The file is always closed.
@@ -2434,7 +2422,10 @@ public class FileUtils {
      * @param file     the file to read, must not be {@code null}
      * @param charset the charset to use, {@code null} means platform default
      * @return the list of Strings representing each line in the file, never {@code null}
-     * @throws IOException in case of an I/O error
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @since 2.3
      */
     public static List<String> readLines(final File file, final Charset charset) throws IOException {
@@ -2449,27 +2440,101 @@ public class FileUtils {
      * @param file     the file to read, must not be {@code null}
      * @param charsetName the name of the requested charset, {@code null} means platform default
      * @return the list of Strings representing each line in the file, never {@code null}
-     * @throws IOException                 in case of an I/O error
+     * @throws NullPointerException if file is {@code null}.
+     * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for some
+     *         other reason cannot be opened for reading.
+     * @throws IOException if an I/O error occurs.
      * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
-     * .UnsupportedEncodingException} in version 2.2 if the encoding is not supported.
+     * .UnsupportedEncodingException} in version 2.2 if the named charset is unavailable.
      * @since 1.1
      */
     public static List<String> readLines(final File file, final String charsetName) throws IOException {
         return readLines(file, Charsets.toCharset(charsetName));
     }
 
+    private static void requireAbsent(final File file, String name) throws FileExistsException {
+        if (file.exists()) {
+            throw new FileExistsException(
+                String.format("File element in parameter '%s' already exists: '%s'", name, file));
+        }
+    }
+
+
     /**
-     * Requires that the given {@code File} exists and is a directory.
+     * Throws IllegalArgumentException if the given files' canonical representations are equal.
+     * 
+     * @param file1 The first file to compare.
+     * @param file2 The second file to compare.
+     * @throws IllegalArgumentException if the given files' canonical representations are equal.
+     */
+    private static void requireCanonicalPathsNotEquals(final File file1, final File file2) throws IOException {
+        final String canonicalPath = file1.getCanonicalPath();
+        if (canonicalPath.equals(file2.getCanonicalPath())) {
+            throw new IllegalArgumentException(String
+                .format("File canonical paths are equal: '%s' (file1='%s', file2='%s')", canonicalPath, file1, file2));
+        }
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the file is not writable.
+     * 
+     * @param file The file to test.
+     * @param name The parameter name to use in the exception message.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if the file is not writable.
+     */
+    private static void requireCanWrite(final File file, String name) {
+        Objects.requireNonNull(file, "file");
+        if (!file.canWrite()) {
+            throw new IllegalArgumentException("File parameter '" + name + " is non-writable: '" + file + "'");
+        }
+    }
+
+    /**
+     * Requires that the given {@code File} is a directory.
      *
      * @param directory The {@code File} to check.
-     * @param param The param name to use in the exception message in case of null input.
+     * @param name 
      * @return the given directory.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
      * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory.
      */
-    private static File requireDirectory(final File directory, String param) {
-        requireExists(directory, param);
+    private static File requireDirectory(final File directory, String name) {
+        Objects.requireNonNull(directory, name);
         if (!directory.isDirectory()) {
-            throw new IllegalArgumentException(directory + " is not a directory");
+            throw new IllegalArgumentException("Parameter '" + name + "' is not a directory: '" + directory + "'");
+        }
+        return directory;
+    }
+
+    /**
+     * Requires that the given {@code File} exists and is a directory.
+     *
+     * @param directory The {@code File} to check.
+     * @param name The parameter name to use in the exception message in case of null input.
+     * @return the given directory.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory.
+     */
+    private static File requireDirectoryExists(final File directory, String name) {
+        requireExists(directory, name);
+        requireDirectory(directory, name);
+        return directory;
+    }
+
+    /**
+     * Requires that the given {@code File} is a directory if it exists.
+     *
+     * @param directory The {@code File} to check.
+     * @param name The parameter name to use in the exception message in case of null input.
+     * @return the given directory.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if the given {@code File} exists but is not a directory.
+     */
+    private static File requireDirectoryIfExists(final File directory, String name) {
+        Objects.requireNonNull(directory, name);
+        if (directory.exists()) {
+            requireDirectory(directory, name);
         }
         return directory;
     }
@@ -2492,50 +2557,83 @@ public class FileUtils {
     }
 
     /**
-     * Requires that the given {@code File} exists.
+     * Requires that the given {@code File} exists and throws an {@link IllegalArgumentException} if it doesn't.
      *
      * @param file The {@code File} to check.
-     * @param param The param name to use in the exception message in case of null input.
+     * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
      * @return the given file.
-     * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if the given {@code File} does not exist.
      */
-    private static File requireExists(final File file, String param) {
-        Objects.requireNonNull(file, param);
+    private static File requireExists(final File file, String fileParamName) {
+        Objects.requireNonNull(file, fileParamName);
         if (!file.exists()) {
-            throw new IllegalArgumentException(file + " does not exist");
+            throw new IllegalArgumentException(
+                "File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
         }
         return file;
     }
 
     /**
-     * Requires that the given {@code File} exists and is a file.
+     * Requires that the given {@code File} exists and throws an {@link FileNotFoundException} if it doesn't.
      *
      * @param file The {@code File} to check.
-     * @param param The param name to use in the exception message in case of null input.
+     * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
      * @return the given file.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws FileNotFoundException if the given {@code File} does not exist.
+     */
+    private static File requireExistsChecked(final File file, String fileParamName) throws FileNotFoundException {
+        Objects.requireNonNull(file, fileParamName);
+        if (!file.exists()) {
+            throw new FileNotFoundException(
+                "File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
+        }
+        return file;
+    }
+
+    /**
+     * Requires that the given {@code File} is a file.
+     *
+     * @param file The {@code File} to check.
+     * @param name The parameter name to use in the exception message.
+     * @return the given file.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
      * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory.
      */
-    private static File requireFile(final File file, String param) {
-        requireExists(file, param);
+    private static File requireFile(final File file, String name) {
+        Objects.requireNonNull(file, name);
         if (!file.isFile()) {
-            throw new IllegalArgumentException(file + " is not a file");
+            throw new IllegalArgumentException("Parameter '" + name + "' is not a file: " + file);
         }
         return file;
     }
 
     /**
-     * Requires requirements for file copy.
+     * Requires parameter attributes for a file copy operation.
      *
      * @param source the source file
      * @param destination the destination
-     * @throws FileNotFoundException if the destination does not exist
+     * @throws NullPointerException if any of the given {@code File}s are {@code null}.
+     * @throws FileNotFoundException if the source does not exist.
      */
-    private static void requireFileRequirements(final File source, final File destination) throws FileNotFoundException {
-        Objects.requireNonNull(source, "source");
-        Objects.requireNonNull(destination, "target");
-        if (!source.exists()) {
-            throw new FileNotFoundException("Source '" + source + "' does not exist");
-        }
+    private static void requireFileCopy(final File source, final File destination) throws FileNotFoundException {
+        requireExistsChecked(source, "source");
+        Objects.requireNonNull(destination, "destination");
+    }
+
+    /**
+     * Requires that the given {@code File} is a file if it exists.
+     *
+     * @param file The {@code File} to check.
+     * @param name The parameter name to use in the exception message in case of null input.
+     * @return the given directory.
+     * @throws NullPointerException if the given {@code File} is {@code null}.
+     * @throws IllegalArgumentException if the given {@code File} does exists but is not a directory.
+     */
+    private static File requireFileIfExists(final File file, String name) {
+        Objects.requireNonNull(file, name);
+        return file.exists() ? requireFile(file, name) : file;
     }
 
     /**
@@ -2543,11 +2641,27 @@ public class FileUtils {
      *
      * @param sourceFile The source file to query.
      * @param targetFile The target file to set.
-     * @throws IOException if an error occurs or setting the last-modified time didn't succeeded.
+     * @throws NullPointerException if sourceFile is {@code null}.
+     * @throws NullPointerException if targetFile is {@code null}.
+     * @throws IOException if setting the last-modified time failed.
      */
     private static void setLastModified(final File sourceFile, final File targetFile) throws IOException {
-        if (!targetFile.setLastModified(sourceFile.lastModified())) {
-            throw new IOException("Failed setLastModified on " + sourceFile);
+        Objects.requireNonNull(sourceFile, "sourceFile");
+        setLastModified(targetFile, sourceFile.lastModified());
+    }
+
+    /**
+     * Sets the given {@code targetFile}'s last modified date to the given value.
+     *
+     * @param file The source file to query.
+     * @param timeMillis The new last-modified time, measured in milliseconds since the epoch 01-01-1970 GMT.
+     * @throws NullPointerException if file is {@code null}.
+     * @throws IOException if setting the last-modified time failed.
+     */
+    private static void setLastModified(final File file, final long timeMillis) throws IOException {
+        Objects.requireNonNull(file, "file");
+        if (!file.setLastModified(timeMillis)) {
+            throw new IOException(String.format("Failed setLastModified(%s) on '%s'", timeMillis, file));
         }
     }
 
@@ -2576,18 +2690,18 @@ public class FileUtils {
      */
     public static long sizeOf(final File file) {
         requireExists(file, "file");
-        if (file.isDirectory()) {
-            return sizeOfDirectory0(file); // private method; expects directory
-        }
-        return file.length();
+        return file.isDirectory() ? sizeOfDirectory0(file) : file.length();
     }
 
     /**
-     * the size of a file
-     * @param file the file to check
-     * @return the size of the file
+     * Gets the size of a file.
+     *
+     * @param file the file to check.
+     * @return the size of the file.
+     * @throws NullPointerException if the file is {@code null}.
      */
     private static long sizeOf0(final File file) {
+        Objects.requireNonNull(file, "file");
         if (file.isDirectory()) {
             return sizeOfDirectory0(file);
         }
@@ -2614,22 +2728,18 @@ public class FileUtils {
      */
     public static BigInteger sizeOfAsBigInteger(final File file) {
         requireExists(file, "file");
-        if (file.isDirectory()) {
-            return sizeOfDirectoryBig0(file); // internal method
-        }
-        return BigInteger.valueOf(file.length());
+        return file.isDirectory() ? sizeOfDirectoryBig0(file) : BigInteger.valueOf(file.length());
     }
 
     /**
-     * Returns the size of a file
-     * @param fileOrDir The file
+     * Returns the size of a file or directory.
+     *
+     * @param file The file or directory.
      * @return the size
      */
-    private static BigInteger sizeOfBig0(final File fileOrDir) {
-        if (fileOrDir.isDirectory()) {
-            return sizeOfDirectoryBig0(fileOrDir);
-        }
-        return BigInteger.valueOf(fileOrDir.length());
+    private static BigInteger sizeOfBig0(final File file) {
+        Objects.requireNonNull(file, "fileOrDir");
+        return file.isDirectory() ? sizeOfDirectoryBig0(file) : BigInteger.valueOf(file.length());
     }
 
     /**
@@ -2646,15 +2756,18 @@ public class FileUtils {
      * @throws NullPointerException if the directory is {@code null}.
      */
     public static long sizeOfDirectory(final File directory) {
-        return sizeOfDirectory0(requireDirectory(directory, "directory"));
+        return sizeOfDirectory0(requireDirectoryExists(directory, "directory"));
     }
 
     /**
-     * the size of a director
+     * Gets the size of a directory.
+     *
      * @param directory the directory to check
      * @return the size
+     * @throws NullPointerException if the directory is {@code null}.
      */
     private static long sizeOfDirectory0(final File directory) {
+        Objects.requireNonNull(directory, "directory");
         final File[] files = directory.listFiles();
         if (files == null) {  // null if security restricted
             return 0L;
@@ -2663,7 +2776,7 @@ public class FileUtils {
 
         for (final File file : files) {
             if (!isSymlink(file)) {
-                size += sizeOf0(file); // internal method
+                size += sizeOf0(file);
                 if (size < 0) {
                     break;
                 }
@@ -2682,18 +2795,20 @@ public class FileUtils {
      * @since 2.4
      */
     public static BigInteger sizeOfDirectoryAsBigInteger(final File directory) {
-        return sizeOfDirectoryBig0(requireDirectory(directory, "directory"));
+        return sizeOfDirectoryBig0(requireDirectoryExists(directory, "directory"));
     }
 
     /**
-     * Finds the size of a directory
+     * Computes the size of a directory.
      *
-     * @param directory The directory
-     * @return the size
+     * @param directory The directory.
+     * @return the size.
      */
     private static BigInteger sizeOfDirectoryBig0(final File directory) {
+        Objects.requireNonNull(directory, "directory");
         final File[] files = directory.listFiles();
-        if (files == null) {  // null if security restricted
+        if (files == null) {
+            // null if security restricted
             return BigInteger.ZERO;
         }
         BigInteger size = BigInteger.ZERO;
@@ -2721,18 +2836,13 @@ public class FileUtils {
      */
     public static Stream<File> streamFiles(final File directory, final boolean recursive, final String... extensions)
         throws IOException {
-        final IOFileFilter filter;
-        if (extensions == null) {
-            filter = FileFileFilter.INSTANCE;
-        } else {
-            filter = FileFileFilter.INSTANCE.and(new SuffixFileFilter(toSuffixes(extensions)));
-        }
-        // We use filters that do not need file attributes so pass false.
+        final IOFileFilter filter = extensions == null ? FileFileFilter.INSTANCE
+            : FileFileFilter.INSTANCE.and(new SuffixFileFilter(toSuffixes(extensions)));
         return PathUtils.walk(directory.toPath(), filter, toMaxDepth(recursive), false).map(Path::toFile);
     }
 
     /**
-     * Convert from a <code>URL</code> to a <code>File</code>.
+     * Converts from a <code>URL</code> to a <code>File</code>.
      * <p>
      * From version 1.1 this method will decode the URL.
      * Syntax such as <code>file:///my%20docs/file.txt</code> will be
@@ -2750,9 +2860,8 @@ public class FileUtils {
         if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) {
             return null;
         }
-        String filename = url.getFile().replace('/', File.separatorChar);
-        filename = decodeUrl(filename);
-        return new File(filename);
+        final String filename = url.getFile().replace('/', File.separatorChar);
+        return new File(decodeUrl(filename));
     }
 
     /**
@@ -2784,9 +2893,8 @@ public class FileUtils {
         for (int i = 0; i < urls.length; i++) {
             final URL url = urls[i];
             if (url != null) {
-                if (url.getProtocol().equals("file") == false) {
-                    throw new IllegalArgumentException(
-                            "URL could not be converted to a File: " + url);
+                if (!"file".equalsIgnoreCase(url.getProtocol())) {
+                    throw new IllegalArgumentException("Can only convert file URL to a File: " + url);
                 }
                 files[i] = toFile(url);
             }
@@ -2809,13 +2917,14 @@ public class FileUtils {
     }
 
     /**
-     * Converts an array of file extensions to suffixes for use
-     * with IOFileFilters.
+     * Converts an array of file extensions to suffixes.
      *
      * @param extensions an array of extensions. Format: {"java", "xml"}
      * @return an array of suffixes. Format: {".java", ".xml"}
+     * @throws NullPointerException
      */
     private static String[] toSuffixes(final String... extensions) {
+        Objects.requireNonNull(extensions, "extensions");
         final String[] suffixes = new String[extensions.length];
         for (int i = 0; i < extensions.length; i++) {
             suffixes[i] = "." + extensions[i];
@@ -2833,17 +2942,16 @@ public class FileUtils {
      * creates parent directories if they do not exist.
      * </p>
      *
-     * @param file the File to touch
-     * @throws IOException If an I/O problem occurs
+     * @param file the File to touch.
+     * @throws IOException if an I/O problem occurs. 
+     * @throws IOException if setting the last-modified time failed.
      */
     public static void touch(final File file) throws IOException {
+        Objects.requireNonNull(file, "file");
         if (!file.exists()) {
             openOutputStream(file).close();
         }
-        final boolean success = file.setLastModified(System.currentTimeMillis());
-        if (!success) {
-            throw new IOException("Unable to set the last modification time for " + file);
-        }
+        setLastModified(file, System.currentTimeMillis());
     }
 
     /**
@@ -2858,12 +2966,11 @@ public class FileUtils {
      * @throws NullPointerException if the parameter is null
      */
     public static URL[] toURLs(final File... files) throws IOException {
+        Objects.requireNonNull(files, "files");
         final URL[] urls = new URL[files.length];
-
         for (int i = 0; i < urls.length; i++) {
             urls[i] = files[i].toURI().toURL();
         }
-
         return urls;
     }
 
@@ -2888,22 +2995,6 @@ public class FileUtils {
     }
 
     /**
-     * Lists files in a directory, asserting that the supplied directory satisfies exists and is a directory.
-     *
-     * @param directory The directory to list
-     * @return The files in the directory, never null.
-     * @throws IOException if an I/O error occurs
-     */
-    private static File[] verifiedListFiles(final File directory) throws IOException {
-        requireDirectory(directory, "directory");
-        final File[] files = directory.listFiles();
-        if (files == null) {  // null if security restricted
-            throw new IOException("Failed to list contents of " + directory);
-        }
-        return files;
-    }
-
-    /**
      * Waits for NFS to propagate a file creation, imposing a timeout.
      * <p>
      * This method repeatedly tests {@link File#exists()} until it returns
@@ -2916,6 +3007,7 @@ public class FileUtils {
      * @throws NullPointerException if the file is {@code null}
      */
     public static boolean waitFor(final File file, final int seconds) {
+        Objects.requireNonNull(file, "file");
         final long finishAt = System.currentTimeMillis() + (seconds * 1000L);
         boolean wasInterrupted = false;
         try {
@@ -2996,8 +3088,7 @@ public class FileUtils {
      */
     public static void write(final File file, final CharSequence data, final Charset charset, final boolean append)
             throws IOException {
-        final String str = data == null ? null : data.toString();
-        writeStringToFile(file, str, charset, append);
+        writeStringToFile(file, Objects.toString(data, null), charset, append);
     }
 
     // Private method, must be invoked will a directory parameter
@@ -3016,8 +3107,6 @@ public class FileUtils {
         write(file, data, charsetName, false);
     }
 
-    // Internal method - does not check existence
-
     /**
      * Writes a CharSequence to a file creating the file if it does not exist.
      *
@@ -3069,8 +3158,6 @@ public class FileUtils {
         writeByteArrayToFile(file, data, 0, data.length, append);
     }
 
-    // internal method; if file does not exist will return 0
-
     /**
      * Writes {@code len} bytes from the specified byte array starting
      * at offset {@code off} to a file, creating the file if it does
@@ -3361,7 +3448,9 @@ public class FileUtils {
 
     /**
      * Instances should NOT be constructed in standard programming.
+     * @deprecated Will be private in 3.0.
      */
+    @Deprecated
     public FileUtils() { //NOSONAR
         
     }
diff --git a/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTestCase.java b/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTestCase.java
index 3f94b39..da6bfc4 100644
--- a/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTestCase.java
+++ b/src/test/java/org/apache/commons/io/FileUtilsCopyDirectoryToDirectoryTestCase.java
@@ -66,7 +66,8 @@ public class FileUtilsCopyDirectoryToDirectoryTestCase {
         srcDir.mkdir();
         final File destDir = new File(temporaryFolder, "notadirectory");
         destDir.createNewFile();
-        final String expectedMessage = String.format("Destination '%s' is not a directory", destDir);
+        final String expectedMessage = String.format("Parameter 'destinationDir' is not a directory: '%s'",
+            destDir);
         assertExceptionTypeAndMessage(srcDir, destDir, IllegalArgumentException.class, expectedMessage);
     }
 
@@ -76,7 +77,8 @@ public class FileUtilsCopyDirectoryToDirectoryTestCase {
         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);
+        final String expectedMessage = String.format("Parameter 'sourceDir' is not a directory: '%s'",
+            srcDir);
         assertExceptionTypeAndMessage(srcDir, destDir, IllegalArgumentException.class, expectedMessage);
     }
 
diff --git a/src/test/java/org/apache/commons/io/FileUtilsTestCase.java b/src/test/java/org/apache/commons/io/FileUtilsTestCase.java
index b8a3798..dad0628 100644
--- a/src/test/java/org/apache/commons/io/FileUtilsTestCase.java
+++ b/src/test/java/org/apache/commons/io/FileUtilsTestCase.java
@@ -327,11 +327,7 @@ public class FileUtilsTestCase {
     public void test_openInputStream_existsButIsDirectory() throws Exception {
         final File directory = new File(temporaryFolder, "subdir");
         directory.mkdirs();
-        try (FileInputStream in = FileUtils.openInputStream(directory)) {
-            fail();
-        } catch (final IOException ioe) {
-            // expected
-        }
+        assertThrows(IOException.class, () -> FileUtils.openInputStream(directory));
     }
 
     @Test
@@ -358,11 +354,7 @@ public class FileUtilsTestCase {
     public void test_openOutputStream_existsButIsDirectory() throws Exception {
         final File directory = new File(temporaryFolder, "subdir");
         directory.mkdirs();
-        try (FileOutputStream out = FileUtils.openOutputStream(directory)) {
-            fail();
-        } catch (final IOException ioe) {
-            // expected
-        }
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.openOutputStream(directory));
     }
 
     @Test
@@ -593,12 +585,7 @@ public class FileUtilsTestCase {
         assertTrue(FileUtils.contentEquals(file2, file));
 
         // Directories
-        try {
-            FileUtils.contentEquals(temporaryFolder, temporaryFolder);
-            fail("Comparing directories should fail with an IOException");
-        } catch (final IOException ioe) {
-            //expected
-        }
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.contentEquals(temporaryFolder, temporaryFolder));
 
         // Different files
         final File objFile1 =
@@ -652,12 +639,8 @@ public class FileUtilsTestCase {
         assertTrue(FileUtils.contentEqualsIgnoreEOL(file2, file1, null));
 
         // Directories
-        try {
-            FileUtils.contentEqualsIgnoreEOL(temporaryFolder, temporaryFolder, null);
-            fail("Comparing directories should fail with an IOException");
-        } catch (final IOException ioe) {
-            //expected
-        }
+        assertThrows(IllegalArgumentException.class,
+            () -> FileUtils.contentEqualsIgnoreEOL(temporaryFolder, temporaryFolder, null));
 
         // Different files
         final File tfile1 = new File(temporaryFolder, getName() + ".txt1");
@@ -720,42 +703,22 @@ public class FileUtilsTestCase {
     }
 
     @Test
-    public void testCopyDirectoryErrors() throws Exception {
-        try {
-            FileUtils.copyDirectory(null, null);
-            fail();
-        } catch (final NullPointerException ignore) {
-        }
-        try {
-            FileUtils.copyDirectory(new File("a"), null);
-            fail();
-        } catch (final NullPointerException ignore) {
-        }
-        try {
-            FileUtils.copyDirectory(null, new File("a"));
-            fail();
-        } catch (final NullPointerException ignore) {
-        }
-        try {
-            FileUtils.copyDirectory(new File("doesnt-exist"), new File("a"));
-            fail();
-        } catch (final IOException ignore) {
-        }
-        try {
-            FileUtils.copyDirectory(testFile1, new File("a"));
-            fail();
-        } catch (final IOException ignore) {
-        }
-        try {
-            FileUtils.copyDirectory(temporaryFolder, testFile1);
-            fail();
-        } catch (final IOException ignore) {
-        }
-        try {
-            FileUtils.copyDirectory(temporaryFolder, temporaryFolder);
-            fail();
-        } catch (final IOException ignore) {
-        }
+    public void testCopyDirectoryExceptions() throws Exception {
+        //
+        // NullPointerException
+        assertThrows(NullPointerException.class, () -> FileUtils.copyDirectory(null, null));
+        assertThrows(NullPointerException.class, () -> FileUtils.copyDirectory(null, testFile1));
+        assertThrows(NullPointerException.class, () -> FileUtils.copyDirectory(testFile1, null));
+        assertThrows(NullPointerException.class, () -> FileUtils.copyDirectory(null, new File("a")));
+        //
+        // IllegalArgumentException
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.copyDirectory(testFile1, new File("a")));
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.copyDirectory(testFile1, new File("a")));
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.copyDirectory(temporaryFolder, temporaryFolder));
+        //
+        // IOException
+        assertThrows(IOException.class, () -> FileUtils.copyDirectory(new File("doesnt-exist"), new File("a")));
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.copyDirectory(temporaryFolder, testFile1));
     }
 
     @Test
@@ -1057,8 +1020,7 @@ public class FileUtilsTestCase {
         assertEquals(testFile1Size, destination.length(), "Check Full copy");
         assertEquals(testFile1.lastModified(), destination.lastModified(), "Check last modified date preserved");
 
-        assertThrows(IOException.class,
-            () -> FileUtils.copyFileToDirectory(destination, directory),
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.copyFileToDirectory(destination, directory),
             "Should not be able to copy a file into the same directory as itself");
     }
 
@@ -1245,13 +1207,7 @@ public class FileUtilsTestCase {
         final File destination = new File(temporaryFolder, "copy3.txt");
         //Prepare a test file
         FileUtils.copyFile(testFile1, destination);
-
-        try {
-            FileUtils.copyFile(destination, destination);
-            fail("file copy to self should not be possible");
-        } catch (final IOException ioe) {
-            //we want the exception, copy to self should be illegal
-        }
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.copyFile(destination, destination));
     }
 
     @Test
@@ -1501,11 +1457,7 @@ public class FileUtilsTestCase {
         assertTrue(testFile.exists(), "Test file does not exist.");
 
         // Tests with existing file
-        try {
-            FileUtils.forceMkdir(testFile);
-            fail("Exception expected.");
-        } catch (final IOException ignore) {
-        }
+        assertThrows(IOException.class, () -> FileUtils.forceMkdir(testFile));
 
         testFile.delete();
 
@@ -1774,6 +1726,7 @@ public class FileUtilsTestCase {
         assertTrue(FileUtils.isFileOlder(newFile, localDatePlusDay, localTime), "New File - Older - LocalDate plus one day,LocalTime");
 
         assertFalse(FileUtils.isFileOlder(invalidFile, reference), "Invalid - Older - File");
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.isFileOlder(newFile, invalidFile));
         try {
             FileUtils.isFileOlder(newFile, invalidFile);
             fail("Should have cause IllegalArgumentException");
@@ -1785,71 +1738,29 @@ public class FileUtilsTestCase {
 
         // ----- Test isFileNewer() exceptions -----
         // Null File
-        try {
-            FileUtils.isFileNewer(null, now);
-            fail("Newer Null, expected NullPointerException");
-        } catch (final NullPointerException expected) {
-            // expected result
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(null, now));
 
         // Null reference File
-        try {
-            FileUtils.isFileNewer(oldFile, (File) null);
-            fail("Newer Null reference, expected NullPointerException");
-        } catch (final NullPointerException ignore) {
-            // expected result
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(oldFile, (File) null));
 
         // Invalid reference File
-        try {
-            FileUtils.isFileNewer(oldFile, invalidFile);
-            fail("Newer invalid reference, expected IllegalArgumentException");
-        } catch (final IllegalArgumentException ignore) {
-            // expected result
-        }
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.isFileNewer(oldFile, invalidFile));
 
         // Null reference Date
-        try {
-            FileUtils.isFileNewer(oldFile, (Date) null);
-            fail("Newer Null date, expected NullPointerException");
-        } catch (final NullPointerException ignore) {
-            // expected result
-        }
-
+        assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(oldFile, (Date) null));
 
         // ----- Test isFileOlder() exceptions -----
         // Null File
-        try {
-            FileUtils.isFileOlder(null, now);
-            fail("Older Null, expected NullPointerException");
-        } catch (final NullPointerException ignore) {
-            // expected result
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.isFileOlder(null, now));
 
         // Null reference File
-        try {
-            FileUtils.isFileOlder(oldFile, (File) null);
-            fail("Older Null reference, expected NullPointerException");
-        } catch (final NullPointerException ignore) {
-            // expected result
-        }
-
-        // Invalid reference File
-        try {
-            FileUtils.isFileOlder(oldFile, invalidFile);
-            fail("Older invalid reference, expected IllegalArgumentException");
-        } catch (final IllegalArgumentException ignore) {
-            // expected result
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.isFileOlder(oldFile, (File) null));
 
         // Null reference Date
-        try {
-            FileUtils.isFileOlder(oldFile, (Date) null);
-            fail("Older Null date, expected NullPointerException");
-        } catch (final NullPointerException ignore) {
-            // expected result
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.isFileOlder(oldFile, (Date) null));
 
+        // Invalid reference File
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.isFileOlder(oldFile, invalidFile));
     }
 
     @Test
@@ -2094,18 +2005,8 @@ public class FileUtilsTestCase {
 
     @Test
     public void testMoveDirectory_Errors() throws Exception {
-        try {
-            FileUtils.moveDirectory(null, new File("foo"));
-            fail("Expected NullPointerException when source is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
-        try {
-            FileUtils.moveDirectory(new File("foo"), null);
-            fail("Expected NullPointerException when destination is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.moveDirectory(null, new File("foo")));
+        assertThrows(NullPointerException.class, () -> FileUtils.moveDirectory(new File("foo"), null));
         try {
             FileUtils.moveDirectory(new File("nonexistant"), new File("foo"));
             fail("Expected FileNotFoundException for source");
@@ -2124,12 +2025,7 @@ public class FileUtilsTestCase {
         } finally {
             IOUtils.closeQuietly(output);
         }
-        try {
-            FileUtils.moveDirectory(testFile, new File("foo"));
-            fail("Expected IOException when source is not a directory");
-        } catch (final IOException e) {
-            // expected
-        }
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.moveDirectory(testFile, new File("foo")));
         final File testSrcFile = new File(temporaryFolder, "testMoveDirectorySource");
         final File testDestFile = new File(temporaryFolder, "testMoveDirectoryDest");
         testSrcFile.mkdir();
@@ -2213,37 +2109,23 @@ public class FileUtilsTestCase {
 
     @Test
     public void testMoveDirectoryToDirectory_Errors() throws Exception {
-        try {
-            FileUtils.moveDirectoryToDirectory(null, new File("foo"), true);
-            fail("Expected NullPointerException when source is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
-        try {
-            FileUtils.moveDirectoryToDirectory(new File("foo"), null, true);
-            fail("Expected NullPointerException when destination is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.moveDirectoryToDirectory(null, new File("foo"), true));
+        assertThrows(NullPointerException.class, () -> FileUtils.moveDirectoryToDirectory(new File("foo"), null, true));
         final File testFile1 = new File(temporaryFolder, "testMoveFileFile1");
         final File testFile2 = new File(temporaryFolder, "testMoveFileFile2");
         if (!testFile1.getParentFile().exists()) {
-            throw new IOException("Cannot create file " + testFile1
-                    + " as the parent directory does not exist");
+            throw new IOException("Cannot create file " + testFile1 + " as the parent directory does not exist");
         }
-        final BufferedOutputStream output1 =
-                new BufferedOutputStream(new FileOutputStream(testFile1));
+        final BufferedOutputStream output1 = new BufferedOutputStream(new FileOutputStream(testFile1));
         try {
             TestUtils.generateTestData(output1, 0);
         } finally {
             IOUtils.closeQuietly(output1);
         }
         if (!testFile2.getParentFile().exists()) {
-            throw new IOException("Cannot create file " + testFile2
-                    + " as the parent directory does not exist");
+            throw new IOException("Cannot create file " + testFile2 + " as the parent directory does not exist");
         }
-        final BufferedOutputStream output =
-                new BufferedOutputStream(new FileOutputStream(testFile2));
+        final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(testFile2));
         try {
             TestUtils.generateTestData(output, 0);
         } finally {
@@ -2303,42 +2185,23 @@ public class FileUtilsTestCase {
             }
 
         };
-        try {
-            FileUtils.moveFile(src, destination);
-            fail("move should have failed as src has not been deleted");
-        } catch (final IOException e) {
-            // exepected
-            assertTrue(!destination.exists(), "Check Rollback");
-            assertTrue(src.exists(), "Original exists");
-        }
+        assertThrows(IOException.class, () -> FileUtils.moveFile(src, destination));
+        // expected
+        assertTrue(!destination.exists(), "Check Rollback");
+        assertTrue(src.exists(), "Original exists");
     }
 
     @Test
     public void testMoveFile_Errors() throws Exception {
-        try {
-            FileUtils.moveFile(null, new File("foo"));
-            fail("Expected NullPointerException when source is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
-        try {
-            FileUtils.moveFile(new File("foo"), null);
-            fail("Expected NullPointerException when destination is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.moveFile(null, new File("foo")));
+        assertThrows(NullPointerException.class, () -> FileUtils.moveFile(new File("foo"), null));
         try {
             FileUtils.moveFile(new File("nonexistant"), new File("foo"));
             fail("Expected FileNotFoundException for source");
         } catch (final FileNotFoundException e) {
             // expected
         }
-        try {
-            FileUtils.moveFile(temporaryFolder, new File("foo"));
-            fail("Expected IOException when source is a directory");
-        } catch (final IOException e) {
-            // expected
-        }
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.moveFile(temporaryFolder, new File("foo")));
         final File testSourceFile = new File(temporaryFolder, "testMoveFileSource");
         final File testDestFile = new File(temporaryFolder, "testMoveFileSource");
         if (!testSourceFile.getParentFile().exists()) {
@@ -2394,18 +2257,8 @@ public class FileUtilsTestCase {
 
     @Test
     public void testMoveFileToDirectory_Errors() throws Exception {
-        try {
-            FileUtils.moveFileToDirectory(null, new File("foo"), true);
-            fail("Expected NullPointerException when source is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
-        try {
-            FileUtils.moveFileToDirectory(new File("foo"), null, true);
-            fail("Expected NullPointerException when destination is null");
-        } catch (final NullPointerException e) {
-            // expected
-        }
+        assertThrows(NullPointerException.class, () -> FileUtils.moveFileToDirectory(null, new File("foo"), true));
+        assertThrows(NullPointerException.class, () -> FileUtils.moveFileToDirectory(new File("foo"), null, true));
         final File testFile1 = new File(temporaryFolder, "testMoveFileFile1");
         final File testFile2 = new File(temporaryFolder, "testMoveFileFile2");
         if (!testFile1.getParentFile().exists()) {
@@ -2430,12 +2283,7 @@ public class FileUtilsTestCase {
         } finally {
             IOUtils.closeQuietly(output);
         }
-        try {
-            FileUtils.moveFileToDirectory(testFile1, testFile2, true);
-            fail("Expected IOException when dest not a directory");
-        } catch (final IOException e) {
-            // expected
-        }
+        assertThrows(IllegalArgumentException.class, () -> FileUtils.moveFileToDirectory(testFile1, testFile2, true));
 
         final File nonexistant = new File(temporaryFolder, "testMoveFileNonExistant");
         try {