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
commit 1e73dd92a2224828dd45f47ae8e96a8c0cd2d3d8 Author: Gary Gregory <gardgreg...@gmail.com> AuthorDate: Thu Sep 16 13:05:43 2021 -0400 Use NIO internally to avoid using finalizable FileInputStream. --- src/main/java/org/apache/commons/io/FileUtils.java | 6 +- .../java/org/apache/commons/io/file/PathUtils.java | 96 ++++++++++++++++------ .../org/apache/commons/io/FileUtilsTestCase.java | 4 + 3 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java index 3d0d030..1749de5 100644 --- a/src/main/java/org/apache/commons/io/FileUtils.java +++ b/src/main/java/org/apache/commons/io/FileUtils.java @@ -3379,7 +3379,7 @@ public class FileUtils { */ public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len, final boolean append) throws IOException { - try (OutputStream out = openOutputStream(file, append)) { + try (OutputStream out = newOutputStream(file, append)) { out.write(data, off, len); } } @@ -3528,7 +3528,7 @@ public class FileUtils { */ public static void writeLines(final File file, final String charsetName, final Collection<?> lines, final String lineEnding, final boolean append) throws IOException { - try (OutputStream out = new BufferedOutputStream(openOutputStream(file, append))) { + try (OutputStream out = new BufferedOutputStream(newOutputStream(file, append))) { IOUtils.writeLines(lines, lineEnding, out, charsetName); } } @@ -3594,7 +3594,7 @@ public class FileUtils { */ public static void writeStringToFile(final File file, final String data, final Charset charset, final boolean append) throws IOException { - try (OutputStream out = openOutputStream(file, append)) { + try (OutputStream out = newOutputStream(file, append)) { IOUtils.write(data, out, charset); } } 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 c14c63b..cf17a7b 100644 --- a/src/main/java/org/apache/commons/io/file/PathUtils.java +++ b/src/main/java/org/apache/commons/io/file/PathUtils.java @@ -493,7 +493,7 @@ public final class PathUtils { throw new NoSuchFileException(file.toString()); } final PathCounters pathCounts = Counters.longPathCounters(); - final boolean exists = Files.exists(file, linkOptions); + final boolean exists = exists(file, linkOptions); final long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0; if (overrideReadOnly(deleteOptions) && exists) { setReadOnly(file, false, linkOptions); @@ -592,6 +592,10 @@ public final class PathUtils { return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals; } + private static boolean exists(final Path path, final LinkOption... options) { + return Files.exists(Objects.requireNonNull(path, "path"), options); + } + /** * Compares the file contents of two Paths to determine if they are equal or not. * <p> @@ -634,8 +638,8 @@ public final class PathUtils { } final Path nPath1 = path1.normalize(); final Path nPath2 = path2.normalize(); - final boolean path1Exists = Files.exists(nPath1, linkOptions); - if (path1Exists != Files.exists(nPath2, linkOptions)) { + final boolean path1Exists = exists(nPath1, linkOptions); + if (path1Exists != exists(nPath2, linkOptions)) { return false; } if (!path1Exists) { @@ -936,6 +940,7 @@ public final class PathUtils { return isOlder(file, getLastModifiedTime(reference)); } + /** * Tests whether the specified {@code Path} is a regular file or not. Implemented as a null-safe delegate to * {@code Files.isRegularFile(Path path, LinkOption... options)}. @@ -952,7 +957,6 @@ public final class PathUtils { return path != null && Files.isRegularFile(path, options); } - /** * Creates a new DirectoryStream for Paths rooted at the given directory. * @@ -978,6 +982,13 @@ public final class PathUtils { * @since 2.12.0 */ public static OutputStream newOutputStream(final Path path, final boolean append) throws IOException { + Objects.requireNonNull(path, "path"); + if (exists(path)) { + // requireFile(path, "path"); + // requireCanWrite(path, "path"); + } else { + createParentDirectories(path); + } // @formatter:off return Files.newOutputStream(path, append ? new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.APPEND} : @@ -985,8 +996,8 @@ public final class PathUtils { // @formatter:on } - private static boolean notExists(final Path file) { - return Files.notExists(Objects.requireNonNull(file, "file")); + private static boolean notExists(final Path path, final LinkOption... options) { + return Files.notExists(Objects.requireNonNull(path, "path"), options); } /** @@ -1063,6 +1074,57 @@ public final class PathUtils { } /** + * Throws an {@link IllegalArgumentException} if the file is not writable. This provides a more precise exception + * message than a plain access denied. + * + * @param file The file to test. + * @param name The parameter name to use in the exception message. + * @throws NullPointerException if the given {@code Path} is {@code null}. + * @throws IllegalArgumentException if the file is not writable. + */ + private static void requireCanWrite(final Path file, final String name) { + Objects.requireNonNull(file, "file"); + if (!Files.isWritable(file)) { + throw new IllegalArgumentException("File parameter '" + name + " is not writable: '" + file + "'"); + } + } + + /** + * Requires that the given {@code File} exists and throws an {@link IllegalArgumentException} if it doesn't. + * + * @param file The {@code File} to check. + * @param fileParamName The parameter name to use in the exception message in case of {@code null} input. + * @param options options indicating how symbolic links are handled. + * @return the given file. + * @throws NullPointerException if the given {@code File} is {@code null}. + * @throws IllegalArgumentException if the given {@code File} does not exist. + */ + private static Path requireExists(final Path file, final String fileParamName, final LinkOption... options) { + Objects.requireNonNull(file, fileParamName); + if (!exists(file, options)) { + throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'"); + } + return file; + } + + /** + * Requires that the given {@code Path} is a regular file. + * + * @param file The {@code Path} to check. + * @param name The parameter name to use in the exception message. + * @return the given file. + * @throws NullPointerException if the given {@code Path} is {@code null}. + * @throws IllegalArgumentException if the given {@code Path} does not exist or is not a regular file. + */ + private static Path requireFile(final Path file, final String name) { + Objects.requireNonNull(file, name); + if (!Files.isRegularFile(file)) { + throw new IllegalArgumentException("Parameter '" + name + "' is not a regular file: " + file); + } + return file; + } + + /** * Sets the last modified time of the given file path to now. * * @param path The file path to set. @@ -1137,8 +1199,7 @@ public final class PathUtils { /** * Returns the size of the specified file or directory. If the provided {@link File} is a regular file, then the file's - * length is returned. If the argument is a directory, then the size of the directory is calculated recursively. If a - * directory or subdirectory is security restricted, its size will not be included. + * length is returned. If the argument is a directory, then the size of the directory is calculated recursively. * <p> * Note that overflow is not detected, and the return value may be negative if overflow occurs. See * {@link #sizeOfAsBigInteger(Path)} for an alternative method that does not overflow. @@ -1207,23 +1268,6 @@ public final class PathUtils { } /** - * Requires that the given {@code File} exists and throws an {@link IllegalArgumentException} if it doesn't. - * - * @param file The {@code File} to check. - * @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 IllegalArgumentException if the given {@code File} does not exist. - */ - private static Path requireExists(final Path file, final String fileParamName) { - Objects.requireNonNull(file, fileParamName); - if (!Files.exists(file)) { - throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'"); - } - return file; - } - - /** * Converts an array of {@link FileVisitOption} to a {@link Set}. * * @param fileVisitOptions input array. @@ -1324,7 +1368,7 @@ public final class PathUtils { boolean interrupted = false; final long minSleepMillis = 100; try { - while (!Files.exists(file, options)) { + while (!exists(file, options)) { final Instant now = Instant.now(); if (now.isAfter(finishInstant)) { return false; diff --git a/src/test/java/org/apache/commons/io/FileUtilsTestCase.java b/src/test/java/org/apache/commons/io/FileUtilsTestCase.java index e6b5321..9bef98e 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsTestCase.java +++ b/src/test/java/org/apache/commons/io/FileUtilsTestCase.java @@ -184,6 +184,10 @@ public class FileUtilsTestCase { } } + /** + * May throw java.nio.file.FileSystemException: C:\Users\...\FileUtilsTestCase\cycle: A required privilege is not held + * by the client. On Windows, you are fine if you run a terminal with admin karma. + */ private void createCircularSymLink(final File file) throws IOException { assertTrue(file.exists()); final String linkName = file + "/cycle";