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 8fe59e4  [IO-684] Add force version of PathUtils.delete() APIs and 
friends.
8fe59e4 is described below

commit 8fe59e41e8f092b33923bcb9a281fbc228a2bf8f
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Aug 14 21:40:59 2020 -0400

    [IO-684] Add force version of PathUtils.delete() APIs and friends.
    
    - Add PathUtils.delete(Path, DeleteOption...).
    - Add PathUtils.deleteDirectory(Path, DeleteOption...).
    - Add PathUtils.deleteFile(Path, DeleteOption...).
    - Add PathUtils.setReadOnly(Path, boolean, LinkOption...).
    - Add CleaningPathVisitor.CleaningPathVisitor(PathCounters,
    DeleteOption[], String...).
    - Add DeletingPathVisitor.DeletingPathVisitor(PathCounters,
    DeleteOption[], String...).
    
    Since the NIO APIs in Files class are parameterized with interfaces
    implemented by enums (e.g. StandardCopyOption implements CopyOption),
    the new PathUtils APIs here similarly work with a new enum called
    StandardDeleteOption that implements a new DeleteOption interface. This
    allow the API signature to remain stable while additional features can
    be provided by new enums or adding values to StandardDeleteOption.
---
 src/changes/changes.xml                            |   8 +
 src/main/java/org/apache/commons/io/FileUtils.java |   5 +-
 .../commons/io/file/CleaningPathVisitor.java       |  22 ++-
 .../org/apache/commons/io/file/DeleteOption.java   |  32 ++++
 .../commons/io/file/DeletingPathVisitor.java       |  21 ++-
 .../java/org/apache/commons/io/file/PathUtils.java | 199 ++++++++++++++++-----
 .../commons/io/file/StandardDeleteOption.java      |  52 ++++++
 .../io/FileUtilsCleanDirectoryTestCase.java        |  12 +-
 .../io/file/PathUtilsDeleteDirectoryTest.java      |  22 +++
 .../commons/io/file/PathUtilsDeleteFileTest.java   |  50 +++++-
 .../commons/io/file/PathUtilsDeleteTest.java       | 120 ++++++++++++-
 11 files changed, 474 insertions(+), 69 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index fda50bf..1a6c1c5 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -95,6 +95,14 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add ReversedLinesFileReader.readLines(int).
       </action>
+      <action issue="IO-684" dev="ggregory" type="add" due-to="Gary Gregory, 
Robin Jansohn">
+        Add PathUtils.delete(Path, DeleteOption...).
+        Add PathUtils.deleteDirectory(Path, DeleteOption...).
+        Add PathUtils.deleteFile(Path, DeleteOption...).
+        Add PathUtils.setReadOnly(Path, boolean, LinkOption...).
+        Add CleaningPathVisitor.CleaningPathVisitor(PathCounters, 
DeleteOption[], String...).
+        Add DeletingPathVisitor.DeletingPathVisitor(PathCounters, 
DeleteOption[], String...).
+      </action>
       <action dev="ggregory" type="fix" due-to="Gary Gregory">
         Replace FindBugs with SpotBugs.
       </action>
diff --git a/src/main/java/org/apache/commons/io/FileUtils.java 
b/src/main/java/org/apache/commons/io/FileUtils.java
index 3bcd996..8cf6607 100644
--- a/src/main/java/org/apache/commons/io/FileUtils.java
+++ b/src/main/java/org/apache/commons/io/FileUtils.java
@@ -49,6 +49,7 @@ import java.util.zip.Checksum;
 
 import org.apache.commons.io.file.Counters;
 import org.apache.commons.io.file.PathUtils;
+import org.apache.commons.io.file.StandardDeleteOption;
 import org.apache.commons.io.filefilter.DirectoryFileFilter;
 import org.apache.commons.io.filefilter.FalseFileFilter;
 import org.apache.commons.io.filefilter.FileFilterUtils;
@@ -325,6 +326,7 @@ public class FileUtils {
      * @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
+     * @see #forceDelete(File)
      */
     public static void cleanDirectory(final File directory) throws IOException 
{
         final File[] files = verifiedListFiles(directory);
@@ -349,6 +351,7 @@ public class FileUtils {
      * @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
+     * @see #forceDeleteOnExit(File)
      */
     private static void cleanDirectoryOnExit(final File directory) throws 
IOException {
         final File[] files = verifiedListFiles(directory);
@@ -1437,7 +1440,7 @@ public class FileUtils {
     public static void forceDelete(final File file) throws IOException {
         final Counters.PathCounters deleteCounters;
         try {
-            deleteCounters = PathUtils.delete(file.toPath());
+            deleteCounters = PathUtils.delete(file.toPath(), 
StandardDeleteOption.OVERRIDE_READ_ONLY);
         } catch (IOException e) {
             throw new IOException("Unable to delete file: " + file, e);
         }
diff --git a/src/main/java/org/apache/commons/io/file/CleaningPathVisitor.java 
b/src/main/java/org/apache/commons/io/file/CleaningPathVisitor.java
index f56ee7d..80546c0 100644
--- a/src/main/java/org/apache/commons/io/file/CleaningPathVisitor.java
+++ b/src/main/java/org/apache/commons/io/file/CleaningPathVisitor.java
@@ -20,6 +20,7 @@ package org.apache.commons.io.file;
 import java.io.IOException;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Arrays;
@@ -53,6 +54,7 @@ public class CleaningPathVisitor extends CountingPathVisitor {
     }
 
     private final String[] skip;
+    private final boolean overrideReadOnly;
 
     /**
      * Constructs a new visitor that deletes files except for the files and 
directories explicitly given.
@@ -61,10 +63,24 @@ public class CleaningPathVisitor extends 
CountingPathVisitor {
      * @param skip The files to skip deleting.
      */
     public CleaningPathVisitor(final PathCounters pathCounter, final String... 
skip) {
+        this(pathCounter, PathUtils.EMPTY_DELETE_OPTION_ARRAY, skip);
+    }
+
+    /**
+     * Constructs a new visitor that deletes files except for the files and 
directories explicitly given.
+     *
+     * @param pathCounter How to count visits.
+     * @param deleteOption options indicating how deletion is handled.
+     * @param skip The files to skip deleting.
+     * @since 2.8.0
+     */
+    public CleaningPathVisitor(final PathCounters pathCounter, final 
DeleteOption[] deleteOption,
+        final String... skip) {
         super(pathCounter);
         final String[] temp = skip != null ? skip.clone() : EMPTY_STRING_ARRAY;
         Arrays.sort(temp);
         this.skip = temp;
+        this.overrideReadOnly = 
StandardDeleteOption.overrideReadOnly(deleteOption);
     }
 
     /**
@@ -85,7 +101,11 @@ public class CleaningPathVisitor extends 
CountingPathVisitor {
 
     @Override
     public FileVisitResult visitFile(final Path file, final 
BasicFileAttributes attributes) throws IOException {
-        if (accept(file) && Files.exists(file)) {
+        // Files.deleteIfExists() never follows links, so use 
LinkOption.NOFOLLOW_LINKS in other calls to Files.
+        if (accept(file) && Files.exists(file, LinkOption.NOFOLLOW_LINKS)) {
+            if (overrideReadOnly) {
+                PathUtils.setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS);
+            }
             Files.deleteIfExists(file);
         }
         updateFileCounters(file, attributes);
diff --git a/src/main/java/org/apache/commons/io/file/DeleteOption.java 
b/src/main/java/org/apache/commons/io/file/DeleteOption.java
new file mode 100644
index 0000000..3fe21ad
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/file/DeleteOption.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.file;
+
+/**
+ * An object that configures how to delete a file.
+ *
+ * <p>
+ * The {@link StandardDeleteOption} enumeration type defines our standard 
options.
+ * </p>
+ *
+ * @see StandardDeleteOption
+ * @since 2.8.0
+ */
+public interface DeleteOption {
+    // empty
+}
diff --git a/src/main/java/org/apache/commons/io/file/DeletingPathVisitor.java 
b/src/main/java/org/apache/commons/io/file/DeletingPathVisitor.java
index 2d6405d..7437650 100644
--- a/src/main/java/org/apache/commons/io/file/DeletingPathVisitor.java
+++ b/src/main/java/org/apache/commons/io/file/DeletingPathVisitor.java
@@ -20,6 +20,7 @@ package org.apache.commons.io.file;
 import java.io.IOException;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Arrays;
@@ -54,6 +55,7 @@ public class DeletingPathVisitor extends CountingPathVisitor {
     }
 
     private final String[] skip;
+    private final boolean overrideReadOnly;
 
     /**
      * Constructs a new visitor that deletes files except for the files and 
directories explicitly given.
@@ -63,10 +65,23 @@ public class DeletingPathVisitor extends 
CountingPathVisitor {
      * @param skip The files to skip deleting.
      */
     public DeletingPathVisitor(final PathCounters pathCounter, final String... 
skip) {
+        this(pathCounter, PathUtils.EMPTY_DELETE_OPTION_ARRAY, skip);
+    }
+
+    /**
+     * Constructs a new visitor that deletes files except for the files and 
directories explicitly given.
+     *
+     * @param pathCounter How to count visits.
+     * @param deleteOption options indicating how deletion is handled.
+     * @param skip The files to skip deleting.
+     * @since 2.8.0
+     */
+    public DeletingPathVisitor(final PathCounters pathCounter, final 
DeleteOption[] deleteOption, final String... skip) {
         super(pathCounter);
         final String[] temp = skip != null ? skip.clone() : EMPTY_STRING_ARRAY;
         Arrays.sort(temp);
         this.skip = temp;
+        this.overrideReadOnly = 
StandardDeleteOption.overrideReadOnly(deleteOption);
     }
 
     /**
@@ -95,7 +110,11 @@ public class DeletingPathVisitor extends 
CountingPathVisitor {
 
     @Override
     public FileVisitResult visitFile(final Path file, final 
BasicFileAttributes attrs) throws IOException {
-        if (accept(file) && Files.exists(file)) {
+        // Files.deleteIfExists() never follows links, so use 
LinkOption.NOFOLLOW_LINKS in other calls to Files.
+        if (accept(file) && Files.exists(file, LinkOption.NOFOLLOW_LINKS)) {
+            if (overrideReadOnly) {
+                PathUtils.setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS);
+            }
             Files.deleteIfExists(file);
         }
         updateFileCounters(file, attrs);
diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java 
b/src/main/java/org/apache/commons/io/file/PathUtils.java
index 6f5a8c3..d370ef0 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -27,12 +27,16 @@ import java.nio.file.FileVisitOption;
 import java.nio.file.FileVisitor;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
-import java.nio.file.NotDirectoryException;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.attribute.AclEntry;
 import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -76,7 +80,7 @@ public final class PathUtils {
          * @throws IOException if an I/O error is thrown by a visitor method.
          */
         private RelativeSortedPaths(final Path dir1, final Path dir2, final 
int maxDepth,
-                final LinkOption[] linkOptions, final FileVisitOption[] 
fileVisitOptions) throws IOException {
+            final LinkOption[] linkOptions, final FileVisitOption[] 
fileVisitOptions) throws IOException {
             List<Path> tmpRelativeDirList1 = null;
             List<Path> tmpRelativeDirList2 = null;
             List<Path> tmpRelativeFileList1 = null;
@@ -91,10 +95,10 @@ public final class PathUtils {
                 if (!parentDirExists1 || !parentDirExists2) {
                     equals = !parentDirExists1 && !parentDirExists2;
                 } else {
-                    AccumulatorPathVisitor visitor1 = accumulate(dir1, 
maxDepth, fileVisitOptions);
-                    AccumulatorPathVisitor visitor2 = accumulate(dir2, 
maxDepth, fileVisitOptions);
+                    final AccumulatorPathVisitor visitor1 = accumulate(dir1, 
maxDepth, fileVisitOptions);
+                    final AccumulatorPathVisitor visitor2 = accumulate(dir2, 
maxDepth, fileVisitOptions);
                     if (visitor1.getDirList().size() != 
visitor2.getDirList().size()
-                            || visitor1.getFileList().size() != 
visitor2.getFileList().size()) {
+                        || visitor1.getFileList().size() != 
visitor2.getFileList().size()) {
                         equals = false;
                     } else {
                         tmpRelativeDirList1 = 
visitor1.relativizeDirectories(dir1, true, null);
@@ -117,6 +121,13 @@ public final class PathUtils {
     }
 
     /**
+     * Empty {@link LinkOption} array.
+     *
+     * @since 2.8.0
+     */
+    public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = new 
DeleteOption[0];
+
+    /**
      * Empty {@link FileVisitOption} array.
      */
     public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = new 
FileVisitOption[0];
@@ -141,9 +152,9 @@ public final class PathUtils {
      * @return file tree information.
      */
     private static AccumulatorPathVisitor accumulate(final Path directory, 
final int maxDepth,
-            final FileVisitOption[] fileVisitOptions) throws IOException {
+        final FileVisitOption[] fileVisitOptions) throws IOException {
         return visitFileTree(AccumulatorPathVisitor.withLongCounters(), 
directory,
-                toFileVisitOptionSet(fileVisitOptions), maxDepth);
+            toFileVisitOptionSet(fileVisitOptions), maxDepth);
     }
 
     /**
@@ -154,22 +165,21 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method.
      */
     public static PathCounters cleanDirectory(final Path directory) throws 
IOException {
-        return cleanDirectory(directory, FileVisitOption.FOLLOW_LINKS);
+        return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
     }
 
     /**
      * Cleans a directory including sub-directories without deleting 
directories.
      *
      * @param directory directory to clean.
-     * @param fileVisitOptions See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param options options indicating how deletion is handled.
      * @return The visitation path counters.
      * @throws IOException if an I/O error is thrown by a visitor method.
      * @since 2.8.0
      */
-    public static PathCounters cleanDirectory(final Path directory, final 
FileVisitOption... fileVisitOptions)
-        throws IOException {
-        return visitFileTree(CleaningPathVisitor.withLongCounters(), 
directory, toFileVisitOptionSet(fileVisitOptions),
-            Integer.MAX_VALUE).getPathCounters();
+    public static PathCounters cleanDirectory(final Path directory, final 
DeleteOption... options) throws IOException {
+        return visitFileTree(new 
CleaningPathVisitor(Counters.longPathCounters(), options), directory)
+            .getPathCounters();
     }
 
     /**
@@ -182,10 +192,10 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method.
      */
     public static PathCounters copyDirectory(final Path sourceDirectory, final 
Path targetDirectory,
-            final CopyOption... copyOptions) throws IOException {
+        final CopyOption... copyOptions) throws IOException {
         return visitFileTree(
-                new CopyDirectoryVisitor(Counters.longPathCounters(), 
sourceDirectory, targetDirectory, copyOptions),
-                sourceDirectory).getPathCounters();
+            new CopyDirectoryVisitor(Counters.longPathCounters(), 
sourceDirectory, targetDirectory, copyOptions),
+            sourceDirectory).getPathCounters();
     }
 
     /**
@@ -199,7 +209,7 @@ public final class PathUtils {
      * @see Files#copy(InputStream, Path, CopyOption...)
      */
     public static Path copyFile(final URL sourceFile, final Path targetFile, 
final CopyOption... copyOptions)
-            throws IOException {
+        throws IOException {
         try (final InputStream inputStream = sourceFile.openStream()) {
             Files.copy(inputStream, targetFile, copyOptions);
             return targetFile;
@@ -217,7 +227,7 @@ public final class PathUtils {
      * @see Files#copy(Path, Path, CopyOption...)
      */
     public static Path copyFileToDirectory(final Path sourceFile, final Path 
targetDirectory,
-            final CopyOption... copyOptions) throws IOException {
+        final CopyOption... copyOptions) throws IOException {
         return Files.copy(sourceFile, 
targetDirectory.resolve(sourceFile.getFileName()), copyOptions);
     }
 
@@ -232,7 +242,7 @@ public final class PathUtils {
      * @see Files#copy(InputStream, Path, CopyOption...)
      */
     public static Path copyFileToDirectory(final URL sourceFile, final Path 
targetDirectory,
-            final CopyOption... copyOptions) throws IOException {
+        final CopyOption... copyOptions) throws IOException {
         try (final InputStream inputStream = sourceFile.openStream()) {
             Files.copy(inputStream, 
targetDirectory.resolve(sourceFile.getFile()), copyOptions);
             return targetDirectory;
@@ -267,7 +277,31 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method or if 
an I/O error occurs.
      */
     public static PathCounters delete(final Path path) throws IOException {
-        return Files.isDirectory(path) ? deleteDirectory(path) : 
deleteFile(path);
+        return delete(path, EMPTY_DELETE_OPTION_ARRAY);
+    }
+
+    /**
+     * Deletes a file or directory. If the path is a directory, delete it and 
all sub-directories.
+     * <p>
+     * The difference between File.delete() and this method are:
+     * </p>
+     * <ul>
+     * <li>A directory to delete does not have to be empty.</li>
+     * <li>You get exceptions when a file or directory cannot be deleted; 
{@link java.io.File#delete()} returns a
+     * boolean.
+     * </ul>
+     *
+     * @param path file or directory to delete, must not be {@code null}
+     * @param options options indicating how deletion is handled.
+     * @return The visitor used to delete the given directory.
+     * @throws NullPointerException if the directory is {@code null}
+     * @throws IOException if an I/O error is thrown by a visitor method or if 
an I/O error occurs.
+     * @since 2.8.0
+     */
+    public static PathCounters delete(final Path path, final DeleteOption... 
options) throws IOException {
+        // File deletion through Files deletes links, not targets, so use 
LinkOption.NOFOLLOW_LINKS.
+        return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? 
deleteDirectory(path, options)
+            : deleteFile(path, options);
     }
 
     /**
@@ -278,20 +312,21 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method.
      */
     public static PathCounters deleteDirectory(final Path directory) throws 
IOException {
-        return deleteDirectory(directory, FileVisitOption.FOLLOW_LINKS);
+        return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
     }
 
     /**
      * Deletes a directory including sub-directories.
      *
      * @param directory directory to delete.
-     * @param fileVisitOptions See {@link 
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+     * @param options options indicating how deletion is handled.
      * @return The visitor used to delete the given directory.
      * @throws IOException if an I/O error is thrown by a visitor method.
      * @since 2.8.0
      */
-    public static PathCounters deleteDirectory(final Path directory, final 
FileVisitOption... fileVisitOptions) throws IOException {
-        return visitFileTree(DeletingPathVisitor.withLongCounters(), 
directory).getPathCounters();
+    public static PathCounters deleteDirectory(final Path directory, final 
DeleteOption... options) throws IOException {
+        return visitFileTree(new 
DeletingPathVisitor(Counters.longPathCounters(), options), directory)
+            .getPathCounters();
     }
 
     /**
@@ -300,14 +335,33 @@ public final class PathUtils {
      * @param file The file to delete.
      * @return A visitor with path counts set to 1 file, 0 directories, and 
the size of the deleted file.
      * @throws IOException if an I/O error occurs.
-     * @throws NotDirectoryException if the file is a directory.
+     * @throws NoSuchFileException if the file is a directory.
      */
     public static PathCounters deleteFile(final Path file) throws IOException {
-        if (Files.isDirectory(file)) {
-            throw new NotDirectoryException(file.toString());
+        return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
+    }
+
+    /**
+     * Deletes the given file.
+     *
+     * @param file The file to delete.
+     * @param options options indicating how deletion is handled.
+     * @return A visitor with path counts set to 1 file, 0 directories, and 
the size of the deleted file.
+     * @throws IOException if an I/O error occurs.
+     * @throws NoSuchFileException if the file is a directory.
+     * @since 2.8.0
+     */
+    public static PathCounters deleteFile(final Path file, final 
DeleteOption... options) throws IOException {
+        // Files.deleteIfExists() never follows links, so use 
LinkOption.NOFOLLOW_LINKS in other calls to Files.
+        if (Files.isDirectory(file, LinkOption.NOFOLLOW_LINKS)) {
+            throw new NoSuchFileException(file.toString());
         }
         final PathCounters pathCounts = Counters.longPathCounters();
-        final long size = Files.exists(file) ? Files.size(file) : 0;
+        final boolean exists = Files.exists(file, LinkOption.NOFOLLOW_LINKS);
+        final long size = exists ? Files.size(file) : 0;
+        if (overrideReadOnly(options) && exists) {
+            setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS);
+        }
         if (Files.deleteIfExists(file)) {
             pathCounts.getFileCounter().increment();
             pathCounts.getByteCounter().add(size);
@@ -316,6 +370,24 @@ public final class PathUtils {
     }
 
     /**
+     * Returns true if the given options contain {@link 
StandardDeleteOption#OVERRIDE_READ_ONLY}.
+     *
+     * @param options the array to test
+     * @return true if the given options contain {@link 
StandardDeleteOption#OVERRIDE_READ_ONLY}.
+     */
+    private static boolean overrideReadOnly(final DeleteOption[] options) {
+        if (options == null) {
+            return false;
+        }
+        for (final DeleteOption deleteOption : options) {
+            if (deleteOption == StandardDeleteOption.OVERRIDE_READ_ONLY) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Compares the file sets of two Paths to determine if they are equal or 
not while considering file contents. The
      * comparison includes all files in all sub-directories.
      *
@@ -326,7 +398,7 @@ public final class PathUtils {
      */
     public static boolean directoryAndFileContentEquals(final Path path1, 
final Path path2) throws IOException {
         return directoryAndFileContentEquals(path1, path2, 
EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY,
-                EMPTY_FILE_VISIT_OPTION_ARRAY);
+            EMPTY_FILE_VISIT_OPTION_ARRAY);
     }
 
     /**
@@ -342,8 +414,8 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method
      */
     public static boolean directoryAndFileContentEquals(final Path path1, 
final Path path2,
-            final LinkOption[] linkOptions, final OpenOption[] openOptions, 
final FileVisitOption[] fileVisitOption)
-            throws IOException {
+        final LinkOption[] linkOptions, final OpenOption[] openOptions, final 
FileVisitOption[] fileVisitOption)
+        throws IOException {
         // First walk both file trees and gather normalized paths.
         if (path1 == null && path2 == null) {
             return true;
@@ -355,7 +427,7 @@ public final class PathUtils {
             return true;
         }
         final RelativeSortedPaths relativeSortedPaths = new 
RelativeSortedPaths(path1, path2, Integer.MAX_VALUE,
-                linkOptions, fileVisitOption);
+            linkOptions, fileVisitOption);
         // If the normalized path names and counts are not the same, no need 
to compare contents.
         if (!relativeSortedPaths.equals) {
             return false;
@@ -363,7 +435,7 @@ public final class PathUtils {
         // Both visitors contain the same normalized paths, we can compare 
file contents.
         final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
         final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
-        for (Path path : fileList1) {
+        for (final Path path : fileList1) {
             final int binarySearch = Collections.binarySearch(fileList2, path);
             if (binarySearch > -1) {
                 if (!fileContentEquals(path1.resolve(path), 
path2.resolve(path), linkOptions, openOptions)) {
@@ -387,7 +459,7 @@ public final class PathUtils {
      */
     public static boolean directoryContentEquals(final Path path1, final Path 
path2) throws IOException {
         return directoryContentEquals(path1, path2, Integer.MAX_VALUE, 
EMPTY_LINK_OPTION_ARRAY,
-                EMPTY_FILE_VISIT_OPTION_ARRAY);
+            EMPTY_FILE_VISIT_OPTION_ARRAY);
     }
 
     /**
@@ -403,7 +475,7 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method
      */
     public static boolean directoryContentEquals(final Path path1, final Path 
path2, final int maxDepth,
-            LinkOption[] linkOptions, FileVisitOption[] fileVisitOptions) 
throws IOException {
+        final LinkOption[] linkOptions, final FileVisitOption[] 
fileVisitOptions) throws IOException {
         return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, 
fileVisitOptions).equals;
     }
 
@@ -440,7 +512,7 @@ public final class PathUtils {
      * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, 
java.io.File)
      */
     public static boolean fileContentEquals(final Path path1, final Path 
path2, final LinkOption[] linkOptions,
-            final OpenOption[] openOptions) throws IOException {
+        final OpenOption[] openOptions) throws IOException {
         if (path1 == null && path2 == null) {
             return true;
         }
@@ -475,7 +547,7 @@ public final class PathUtils {
             return true;
         }
         try (final InputStream inputStream1 = Files.newInputStream(nPath1, 
openOptions);
-                final InputStream inputStream2 = Files.newInputStream(nPath2, 
openOptions)) {
+            final InputStream inputStream2 = Files.newInputStream(nPath2, 
openOptions)) {
             return IOUtils.contentEquals(inputStream1, inputStream2);
         }
     }
@@ -541,8 +613,8 @@ public final class PathUtils {
      * @param comparator How to sort.
      * @return A collection of relativized paths, optionally sorted.
      */
-    static List<Path> relativize(Collection<Path> collection, Path parent, 
boolean sort,
-            Comparator<? super Path> comparator) {
+    static List<Path> relativize(final Collection<Path> collection, final Path 
parent, final boolean sort,
+        final Comparator<? super Path> comparator) {
         Stream<Path> stream = collection.stream().map(parent::relativize);
         if (sort) {
             stream = comparator == null ? stream.sorted() : 
stream.sorted(comparator);
@@ -551,6 +623,43 @@ public final class PathUtils {
     }
 
     /**
+     * Sets the given Path to the {@code readOnly} value.
+     * <p>
+     * This behavior is OS dependent.
+     * </p>
+     *
+     * @param path The path to set.
+     * @param readOnly true for read-only, false for not read-only.
+     * @param options options indicating how symbolic links are handled.
+     * @return The given path.
+     * @throws IOException if an I/O error occurs.
+     * @since 2.8.0
+     */
+    public static Path setReadOnly(final Path path, final boolean readOnly, 
final LinkOption... options)
+        throws IOException {
+        final DosFileAttributeView fileAttributeView = 
Files.getFileAttributeView(path, DosFileAttributeView.class,
+            options);
+        if (fileAttributeView != null) {
+            fileAttributeView.setReadOnly(readOnly);
+            return path;
+        }
+        final PosixFileAttributeView posixFileAttributeView = 
Files.getFileAttributeView(path,
+            PosixFileAttributeView.class, options);
+        if (posixFileAttributeView != null) {
+            // Works on Windows but not on Ubuntu:
+            // Files.setAttribute(path, "unix:readonly", readOnly, options);
+            // java.lang.IllegalArgumentException: 'unix:readonly' not 
recognized
+            final PosixFileAttributes readAttributes = 
posixFileAttributeView.readAttributes();
+            final Set<PosixFilePermission> permissions = 
readAttributes.permissions();
+            permissions.remove(PosixFilePermission.OWNER_WRITE);
+            permissions.remove(PosixFilePermission.GROUP_WRITE);
+            permissions.remove(PosixFilePermission.OTHERS_WRITE);
+            return Files.setPosixFilePermissions(path, permissions);
+        }
+        throw new IOException("No DosFileAttributeView or 
PosixFileAttributeView for " + path);
+    }
+
+    /**
      * Converts an array of {@link FileVisitOption} to a {@link Set}.
      *
      * @param fileVisitOptions input array.
@@ -558,7 +667,7 @@ public final class PathUtils {
      */
     static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... 
fileVisitOptions) {
         return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class)
-                : Arrays.stream(fileVisitOptions).collect(Collectors.toSet());
+            : Arrays.stream(fileVisitOptions).collect(Collectors.toSet());
     }
 
     /**
@@ -574,7 +683,7 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method
      */
     public static <T extends FileVisitor<? super Path>> T visitFileTree(final 
T visitor, final Path directory)
-            throws IOException {
+        throws IOException {
         Files.walkFileTree(directory, visitor);
         return visitor;
     }
@@ -593,8 +702,8 @@ public final class PathUtils {
      *
      * @throws IOException if an I/O error is thrown by a visitor method
      */
-    public static <T extends FileVisitor<? super Path>> T visitFileTree(T 
visitor, Path start,
-            Set<FileVisitOption> options, int maxDepth) throws IOException {
+    public static <T extends FileVisitor<? super Path>> T visitFileTree(final 
T visitor, final Path start,
+        final Set<FileVisitOption> options, final int maxDepth) throws 
IOException {
         Files.walkFileTree(start, options, maxDepth, visitor);
         return visitor;
     }
@@ -613,7 +722,7 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method
      */
     public static <T extends FileVisitor<? super Path>> T visitFileTree(final 
T visitor, final String first,
-            final String... more) throws IOException {
+        final String... more) throws IOException {
         return visitFileTree(visitor, Paths.get(first, more));
     }
 
@@ -630,7 +739,7 @@ public final class PathUtils {
      * @throws IOException if an I/O error is thrown by a visitor method
      */
     public static <T extends FileVisitor<? super Path>> T visitFileTree(final 
T visitor, final URI uri)
-            throws IOException {
+        throws IOException {
         return visitFileTree(visitor, Paths.get(uri));
     }
 
diff --git a/src/main/java/org/apache/commons/io/file/StandardDeleteOption.java 
b/src/main/java/org/apache/commons/io/file/StandardDeleteOption.java
new file mode 100644
index 0000000..e7343cb
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/file/StandardDeleteOption.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.file;
+
+/**
+ * Defines the standard delete options.
+ *
+ * @since 2.8.0
+ */
+public enum StandardDeleteOption implements DeleteOption {
+
+    /**
+     * Overrides the read-only attribute to allow deletion.
+     */
+    OVERRIDE_READ_ONLY;
+
+    /**
+     * Returns true if the given options contain {@link 
StandardDeleteOption#OVERRIDE_READ_ONLY}.
+     *
+     * For now, assume the array is not sorted.
+     *
+     * @param options the array to test
+     * @return true if the given options contain {@link 
StandardDeleteOption#OVERRIDE_READ_ONLY}.
+     */
+    public static boolean overrideReadOnly(final DeleteOption[] options) {
+        if (options == null || options.length == 0) {
+            return false;
+        }
+        for (final DeleteOption deleteOption : options) {
+            if (deleteOption == StandardDeleteOption.OVERRIDE_READ_ONLY) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git 
a/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTestCase.java 
b/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTestCase.java
index 2586da3..6abee2d 100644
--- a/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTestCase.java
+++ b/src/test/java/org/apache/commons/io/FileUtilsCleanDirectoryTestCase.java
@@ -85,11 +85,8 @@ public class FileUtilsCleanDirectoryTestCase {
         assumeTrue(chmod(top, 0, false));
 
         try {
+            // cleanDirectory calls forceDelete
             FileUtils.cleanDirectory(top);
-            fail("expected IOException");
-        } catch (final IOException e) {
-            assertEquals("Failed to list contents of " +
-                    top.getAbsolutePath(), e.getMessage());
         } finally {
             chmod(top, 755, false);
         }
@@ -104,19 +101,16 @@ public class FileUtilsCleanDirectoryTestCase {
         assumeTrue(chmod(top, 500, false));
 
         try {
+            // cleanDirectory calls forceDelete
             FileUtils.cleanDirectory(top);
-            fail("expected IOException");
-        } catch (final IOException e) {
-            assertEquals("Unable to delete file: " +
-                    file.getAbsolutePath(), e.getMessage());
         } finally {
             chmod(top, 755, false);
         }
     }
 
+    /** Only runs on Linux. */
     private boolean chmod(final File file, final int mode, final boolean 
recurse)
             throws InterruptedException {
-        // TODO: Refactor this to FileSystemUtils
         final List<String> args = new ArrayList<>();
         args.add("chmod");
 
diff --git 
a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteDirectoryTest.java 
b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteDirectoryTest.java
index 8caf5ec..f126f0a 100644
--- a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteDirectoryTest.java
+++ b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteDirectoryTest.java
@@ -20,6 +20,7 @@ package org.apache.commons.io.file;
 import static org.apache.commons.io.file.CounterAssertions.assertCounts;
 
 import java.io.IOException;
+import java.nio.file.FileVisitOption;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -60,6 +61,27 @@ public class PathUtilsDeleteDirectoryTest {
     }
 
     /**
+     * Tests a directory with one file of size 0.
+     */
+    private void testDeleteDirectory1FileSize0(final DeleteOption... options) 
throws IOException {
+        // TODO Setup the test to use FileVisitOption.
+        
PathUtils.copyDirectory(Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0"),
 tempDir);
+        assertCounts(1, 1, 0, PathUtils.deleteDirectory(tempDir, options));
+        // This will throw if not empty.
+        Files.deleteIfExists(tempDir);
+    }
+
+    @Test
+    public void testDeleteDirectory1FileSize0NoOptions() throws IOException {
+        testDeleteDirectory1FileSize0(PathUtils.EMPTY_DELETE_OPTION_ARRAY);
+    }
+
+    @Test
+    public void testDeleteDirectory1FileSize0OverrideReadOnly() throws 
IOException {
+        testDeleteDirectory1FileSize0(StandardDeleteOption.OVERRIDE_READ_ONLY);
+    }
+
+    /**
      * Tests a directory with one file of size 1.
      */
     @Test
diff --git 
a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java 
b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java
index 32710f9..fa2375d 100644
--- a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java
+++ b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteFileTest.java
@@ -18,14 +18,15 @@
 package org.apache.commons.io.file;
 
 import static org.apache.commons.io.file.CounterAssertions.assertCounts;
-
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import java.io.IOException;
 import java.nio.file.Files;
-import java.nio.file.NotDirectoryException;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-
 import org.apache.commons.io.file.Counters.PathCounters;
+import org.apache.commons.lang3.SystemUtils;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -96,7 +97,48 @@ public class PathUtilsDeleteFileTest {
      */
     @Test
     public void testDeleteFileEmptyDirectory() throws IOException {
-        Assertions.assertThrows(NotDirectoryException.class, () -> 
testDeleteFileEmpty(PathUtils.deleteFile(tempDir)));
+        Assertions.assertThrows(NoSuchFileException.class, () -> 
testDeleteFileEmpty(PathUtils.deleteFile(tempDir)));
+        // This will throw if not empty.
+        Files.deleteIfExists(tempDir);
+    }
+
+    /**
+     * Tests a directory with one file of size 1.
+     */
+    @Test
+    public void testDeleteReadOnlyFileDirectory1FileSize1() throws IOException 
{
+        final String fileName = "file-size-1.bin";
+        PathUtils.copyFileToDirectory(
+            
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + 
fileName), tempDir);
+        final Path resolved = tempDir.resolve(fileName);
+        PathUtils.setReadOnly(resolved, true);
+        if (SystemUtils.IS_OS_WINDOWS) {
+            // Fails on Windows's Ubuntu subsystem.
+            assertFalse(Files.isWritable(resolved));
+            assertThrows(IOException.class, () -> 
PathUtils.deleteFile(resolved));
+        }
+        assertCounts(0, 1, 1, PathUtils.deleteFile(resolved, 
StandardDeleteOption.OVERRIDE_READ_ONLY));
+        // This will throw if not empty.
+        Files.deleteIfExists(tempDir);
+    }
+
+    /**
+     * Tests a directory with one file of size 1.
+     */
+    @Test
+    public void testSetReadOnlyFileDirectory1FileSize1() throws IOException {
+        final String fileName = "file-size-1.bin";
+        PathUtils.copyFileToDirectory(
+            
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + 
fileName), tempDir);
+        final Path resolved = tempDir.resolve(fileName);
+        PathUtils.setReadOnly(resolved, true);
+        if (SystemUtils.IS_OS_WINDOWS) {
+            // Fails on Windows's Ubuntu subsystem.
+            assertFalse(Files.isWritable(resolved));
+            assertThrows(IOException.class, () -> 
PathUtils.deleteFile(resolved));
+        }
+        PathUtils.setReadOnly(resolved, false);
+        PathUtils.deleteFile(resolved);
         // This will throw if not empty.
         Files.deleteIfExists(tempDir);
     }
diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteTest.java 
b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteTest.java
index ef56b72..b509a49 100644
--- a/src/test/java/org/apache/commons/io/file/PathUtilsDeleteTest.java
+++ b/src/test/java/org/apache/commons/io/file/PathUtilsDeleteTest.java
@@ -19,8 +19,10 @@ package org.apache.commons.io.file;
 
 import static org.apache.commons.io.file.CounterAssertions.assertCounts;
 
+import java.awt.geom.PathIterator;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
@@ -50,34 +52,101 @@ public class PathUtilsDeleteTest {
         tempDir = Files.createTempDirectory(getClass().getCanonicalName());
     }
 
-    /**
-     * Tests a directory with one file of size 0.
-     */
     @Test
     public void testDeleteDirectory1FileSize0() throws IOException {
         final String fileName = "file-size-0.bin";
         FileUtils.copyFileToDirectory(
-                
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0/" + 
fileName).toFile(),
-                tempDir.toFile());
+            
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0/" + 
fileName).toFile(),
+            tempDir.toFile());
         assertCounts(0, 1, 0, PathUtils.delete(tempDir.resolve(fileName)));
         // This will throw if not empty.
         Files.deleteIfExists(tempDir);
     }
 
+    private void testDeleteDirectory1FileSize0(final DeleteOption... options) 
throws IOException {
+        final String fileName = "file-size-0.bin";
+        FileUtils.copyFileToDirectory(
+            
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-0/" + 
fileName).toFile(),
+            tempDir.toFile());
+        assertCounts(0, 1, 0, PathUtils.delete(tempDir.resolve(fileName), 
options));
+        // This will throw if not empty.
+        Files.deleteIfExists(tempDir);
+    }
+
     /**
-     * Tests a directory with one file of size 1.
+     * Tests a directory with one file of size 0.
      */
     @Test
+    public void testDeleteDirectory1FileSize0ForceOff() throws IOException {
+        testDeleteDirectory1FileSize0();
+    }
+
+    /**
+     * Tests a directory with one file of size 0.
+     */
+    @Test
+    public void testDeleteDirectory1FileSize0ForceOn() throws IOException {
+        testDeleteDirectory1FileSize0();
+    }
+
+    @Test
+    public void testDeleteDirectory1FileSize0NoOption() throws IOException {
+        testDeleteDirectory1FileSize0(PathUtils.EMPTY_DELETE_OPTION_ARRAY);
+    }
+
+    @Test
+    public void testDeleteDirectory1FileSize0OverrideReadonly() throws 
IOException {
+        testDeleteDirectory1FileSize0(StandardDeleteOption.OVERRIDE_READ_ONLY);
+    }
+
+    @Test
     public void testDeleteDirectory1FileSize1() throws IOException {
         final String fileName = "file-size-1.bin";
         FileUtils.copyFileToDirectory(
-                
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + 
fileName).toFile(),
-                tempDir.toFile());
+            
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + 
fileName).toFile(),
+            tempDir.toFile());
         assertCounts(0, 1, 1, PathUtils.delete(tempDir.resolve(fileName)));
         // This will throw if not empty.
         Files.deleteIfExists(tempDir);
     }
 
+    private void testDeleteDirectory1FileSize1(final DeleteOption... options) 
throws IOException {
+        // TODO Setup the test to use LinkOption.
+        final String fileName = "file-size-1.bin";
+        FileUtils.copyFileToDirectory(
+            
Paths.get("src/test/resources/org/apache/commons/io/dirs-1-file-size-1/" + 
fileName).toFile(),
+            tempDir.toFile());
+        assertCounts(0, 1, 1, PathUtils.delete(tempDir.resolve(fileName), 
options));
+        // This will throw if not empty.
+        Files.deleteIfExists(tempDir);
+    }
+
+    /**
+     * Tests a directory with one file of size 1.
+     */
+    @Test
+    public void testDeleteDirectory1FileSize1ForceOff() throws IOException {
+        testDeleteDirectory1FileSize1();
+    }
+
+    /**
+     * Tests a directory with one file of size 1.
+     */
+    @Test
+    public void testDeleteDirectory1FileSize1ForceOn() throws IOException {
+        testDeleteDirectory1FileSize1();
+    }
+
+    @Test
+    public void testDeleteDirectory1FileSize1NoOption() throws IOException {
+        testDeleteDirectory1FileSize1(PathUtils.EMPTY_DELETE_OPTION_ARRAY);
+    }
+
+    @Test
+    public void testDeleteDirectory1FileSize1OverrideReadOnly() throws 
IOException {
+        testDeleteDirectory1FileSize1(StandardDeleteOption.OVERRIDE_READ_ONLY);
+    }
+
     /**
      * Tests an empty folder.
      */
@@ -88,11 +157,46 @@ public class PathUtilsDeleteTest {
         Files.deleteIfExists(tempDir);
     }
 
+    /**
+     * Tests an empty folder.
+     */
+    private void testDeleteEmptyDirectory(final DeleteOption... options) 
throws IOException {
+        testDeleteEmptyDirectory(PathUtils.delete(tempDir, options));
+        // This will throw if not empty.
+        Files.deleteIfExists(tempDir);
+    }
+
     private void testDeleteEmptyDirectory(final PathCounters pathCounts) {
         assertCounts(1, 0, 0, pathCounts);
     }
 
     /**
+     * Tests an empty folder.
+     */
+    @Test
+    public void testDeleteEmptyDirectoryForceOff() throws IOException {
+        testDeleteEmptyDirectory();
+    }
+
+    /**
+     * Tests an empty folder.
+     */
+    @Test
+    public void testDeleteEmptyDirectoryForceOn() throws IOException {
+        testDeleteEmptyDirectory();
+    }
+
+    @Test
+    public void testDeleteEmptyDirectoryNoOption() throws IOException {
+        testDeleteEmptyDirectory(PathUtils.EMPTY_DELETE_OPTION_ARRAY);
+    }
+
+    @Test
+    public void testDeleteEmptyDirectoryOverrideReadOnly() throws IOException {
+        testDeleteEmptyDirectory(StandardDeleteOption.OVERRIDE_READ_ONLY);
+    }
+
+    /**
      * Tests a file that does not exist.
      */
     @Test

Reply via email to