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 2022/02/06 19:38:16 UTC

[commons-io] branch master updated (fc3b9c3 -> 4848ddd)

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

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


    from fc3b9c3  Fix formatting.
     new 2adf0f0  Add PathUtils.createParentDirectories(Path, LinkOption, FileAttribute<?>...)
     new 4848ddd  Restore some behavior based on findings described in https://github.com/apache/commons-io/pull/324

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/changes/changes.xml                            |   3 +
 .../java/org/apache/commons/io/file/PathUtils.java | 176 +++++++++++++--------
 .../java/org/apache/commons/io/FileUtilsTest.java  | 108 ++++++++-----
 .../org/apache/commons/io/file/PathUtilsTest.java  |  28 +++-
 4 files changed, 206 insertions(+), 109 deletions(-)

[commons-io] 02/02: Restore some behavior based on findings described in https://github.com/apache/commons-io/pull/324

Posted by gg...@apache.org.
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 4848ddde2f1e11bd83efa2ec624e1624a7191c4e
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Feb 6 14:38:13 2022 -0500

    Restore some behavior based on findings described in
    https://github.com/apache/commons-io/pull/324
---
 .../java/org/apache/commons/io/file/PathUtils.java |  23 +++--
 .../java/org/apache/commons/io/FileUtilsTest.java  | 108 +++++++++++++--------
 .../org/apache/commons/io/file/PathUtilsTest.java  |  27 +++---
 3 files changed, 100 insertions(+), 58 deletions(-)

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 1795eed..a88781a 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -53,6 +53,7 @@ import java.nio.file.attribute.PosixFilePermission;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.chrono.ChronoZonedDateTime;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -176,13 +177,13 @@ public final class PathUtils {
      * @since 2.9.0
      */
     public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = {LinkOption.NOFOLLOW_LINKS};
-    
+
     /**
      * A LinkOption used to follow link in this class, the inverse of {@link LinkOption#NOFOLLOW_LINKS}.
      *
      * @since 2.12.0
      */
-    public static final LinkOption FOLLOW_LINKS = null;
+    static final LinkOption NULL_LINK_OPTION = null;
 
     /**
      * Empty {@link OpenOption} array.
@@ -362,7 +363,7 @@ public final class PathUtils {
         return parent == null ? null : Files.createDirectories(parent, attrs);
     }
 
-    private static Path readIfSymbolicLink(Path path) throws IOException {
+    private static Path readIfSymbolicLink(final Path path) throws IOException {
         return Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path;
     }
 
@@ -650,7 +651,8 @@ public final class PathUtils {
     }
 
     private static boolean exists(final Path path, final LinkOption... options) {
-        return Files.exists(Objects.requireNonNull(path, "path"), options);
+        Objects.requireNonNull(path, "path");
+        return options != null ? Files.exists(path, options) : Files.exists(path);
     }
 
     /**
@@ -1087,14 +1089,19 @@ 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)) {
+        return newOutputStream(path, EMPTY_LINK_OPTION_ARRAY, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
+    }
+
+    static OutputStream newOutputStream(final Path path, final LinkOption[] linkOptions, final OpenOption... openOptions) throws IOException {
+        if (exists(path, linkOptions)) {
             // requireFile(path, "path");
             // requireCanWrite(path, "path");
         } else {
-            createParentDirectories(path);
+            createParentDirectories(path, linkOptions != null && linkOptions.length > 0 ? linkOptions[0] : NULL_LINK_OPTION);
         }
-        return Files.newOutputStream(path, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
+        final List<OpenOption> list = new ArrayList<>(Arrays.asList(openOptions != null ? openOptions : EMPTY_OPEN_OPTION_ARRAY));
+        list.addAll(Arrays.asList(linkOptions != null ? linkOptions : EMPTY_LINK_OPTION_ARRAY));
+        return Files.newOutputStream(path, list.toArray(EMPTY_OPEN_OPTION_ARRAY));
     }
 
     private static boolean notExists(final Path path, final LinkOption... options) {
diff --git a/src/test/java/org/apache/commons/io/FileUtilsTest.java b/src/test/java/org/apache/commons/io/FileUtilsTest.java
index 1e75d3f..f64451c 100644
--- a/src/test/java/org/apache/commons/io/FileUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/FileUtilsTest.java
@@ -251,6 +251,14 @@ public class FileUtilsTest extends AbstractTempDirTest {
         FileUtils.writeStringToFile(file6, "File 6 in grandChild2", "UTF8");
     }
 
+    private Path createTempSymlinkedRelativeDir() throws IOException {
+        final Path targetDir = tempDirPath.resolve("subdir");
+        final Path symlinkDir = tempDirPath.resolve("symlinked-dir");
+        Files.createDirectory(targetDir);
+        Files.createSymbolicLink(symlinkDir, targetDir);
+        return symlinkDir;
+    }
+
     private long getLastModifiedMillis(final File file) throws IOException {
         return FileUtils.lastModified(file);
     }
@@ -391,6 +399,17 @@ public class FileUtilsTest extends AbstractTempDirTest {
     }
 
     @Test
+    public void test_openOutputStream_intoExistingSymlinkedDir() throws Exception {
+        final Path symlinkedDir = createTempSymlinkedRelativeDir();
+
+        final File file = symlinkedDir.resolve("test.txt").toFile();
+        try (FileOutputStream out = FileUtils.openOutputStream(file)) {
+            out.write(0);
+        }
+        assertTrue(file.exists());
+    }
+
+    @Test
     public void test_openOutputStream_noParentCreateFile() throws Exception {
         openOutputStream_noParent(true);
     }
@@ -823,23 +842,6 @@ public class FileUtilsTest extends AbstractTempDirTest {
         FileUtils.deleteDirectory(target);
     }
 
-    /* Test for IO-141 */
-    @Test
-    public void testCopyDirectoryToChild() throws Exception {
-        final File grandParentDir = new File(tempDirFile, "grandparent");
-        final File parentDir = new File(grandParentDir, "parent");
-        final File childDir = new File(parentDir, "child");
-        createFilesForTestCopyDirectory(grandParentDir, parentDir, childDir);
-
-        final long expectedCount = LIST_WALKER.list(grandParentDir).size() + LIST_WALKER.list(parentDir).size();
-        final long expectedSize = FileUtils.sizeOfDirectory(grandParentDir) + FileUtils.sizeOfDirectory(parentDir);
-        FileUtils.copyDirectory(parentDir, childDir);
-        assertEquals(expectedCount, LIST_WALKER.list(grandParentDir).size());
-        assertEquals(expectedSize, FileUtils.sizeOfDirectory(grandParentDir));
-        assertTrue(expectedCount > 0, "Count > 0");
-        assertTrue(expectedSize > 0, "Size > 0");
-    }
-
 //   @Test public void testToURLs2() throws Exception {
 //        File[] files = new File[] {
 //            new File(temporaryFolder, "file1.txt"),
@@ -860,6 +862,23 @@ public class FileUtilsTest extends AbstractTempDirTest {
 //        assertEquals(0, urls.length);
 //    }
 
+    /* Test for IO-141 */
+    @Test
+    public void testCopyDirectoryToChild() throws Exception {
+        final File grandParentDir = new File(tempDirFile, "grandparent");
+        final File parentDir = new File(grandParentDir, "parent");
+        final File childDir = new File(parentDir, "child");
+        createFilesForTestCopyDirectory(grandParentDir, parentDir, childDir);
+
+        final long expectedCount = LIST_WALKER.list(grandParentDir).size() + LIST_WALKER.list(parentDir).size();
+        final long expectedSize = FileUtils.sizeOfDirectory(grandParentDir) + FileUtils.sizeOfDirectory(parentDir);
+        FileUtils.copyDirectory(parentDir, childDir);
+        assertEquals(expectedCount, LIST_WALKER.list(grandParentDir).size());
+        assertEquals(expectedSize, FileUtils.sizeOfDirectory(grandParentDir));
+        assertTrue(expectedCount > 0, "Count > 0");
+        assertTrue(expectedSize > 0, "Size > 0");
+    }
+
     @Test
     public void testCopyDirectoryToDirectory_NonExistingDest() throws Exception {
         if (!testFile1.getParentFile().exists()) {
@@ -1339,6 +1358,8 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertThrows(IllegalArgumentException.class, () -> FileUtils.deleteDirectory(testFile1));
     }
 
+    // copyToDirectory
+
     @Test
     public void testDeleteQuietlyDir() throws IOException {
         final File testDirectory = new File(tempDirFile, "testDeleteQuietlyDir");
@@ -1363,8 +1384,6 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertFalse(testFile.exists(), "Check No Exist");
     }
 
-    // copyToDirectory
-
     @Test
     public void testDeleteQuietlyFile() throws IOException {
         final File testFile = new File(tempDirFile, "testDeleteQuietlyFile");
@@ -2428,6 +2447,7 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertEquals(31, data[2]);
     }
 
+
     @Test
     public void testReadFileToStringWithDefaultEncoding() throws Exception {
         final File file = new File(tempDirFile, "read.obj");
@@ -2440,7 +2460,6 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertEquals("Hello /u1234", data);
     }
 
-
     @Test
     public void testReadFileToStringWithEncoding() throws Exception {
         final File file = new File(tempDirFile, "read.obj");
@@ -2872,6 +2891,7 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertEquals(expected, actual);
     }
 
+
     @Test
     public void testWriteLines_3argsWithAppendOptionTrue_ShouldNotDeletePreviousFileLines() throws Exception {
         final File file = TestUtils.newFile(tempDirFile, "lines.txt");
@@ -2888,7 +2908,6 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertEquals(expected, actual);
     }
 
-
     @Test
     public void testWriteLines_4arg() throws Exception {
         final Object[] data = {
@@ -3046,34 +3065,29 @@ public class FileUtilsTest extends AbstractTempDirTest {
     }
 
     @Test
-    public void testWriteStringToFile1() throws Exception {
-        final File file = new File(tempDirFile, "write.txt");
-        FileUtils.writeStringToFile(file, "Hello /u1234", "UTF8");
-        final byte[] text = "Hello /u1234".getBytes(StandardCharsets.UTF_8);
-        TestUtils.assertEqualContent(text, file);
-    }
-
-    @Test
-    public void testWriteStringToFile2() throws Exception {
-        final File file = new File(tempDirFile, "write.txt");
-        FileUtils.writeStringToFile(file, "Hello /u1234", (String) null);
+    public void testWriteStringToFileIntoNonExistentSubdir() throws Exception {
+        final File file = new File(tempDirFile, "subdir/write.txt");
+        FileUtils.writeStringToFile(file, "Hello /u1234", (Charset) null);
         final byte[] text = "Hello /u1234".getBytes();
         TestUtils.assertEqualContent(text, file);
     }
 
     @Test
-    public void testWriteStringToFile3() throws Exception {
-        final File file = new File(tempDirFile, "write.txt");
-        FileUtils.writeStringToFile(file, "Hello /u1234", (Charset) null);
+    public void testWriteStringToFileIntoSymlinkedDir() throws Exception {
+        final Path symlinkDir = createTempSymlinkedRelativeDir();
+
+        final File file = symlinkDir.resolve("file").toFile();
+        FileUtils.writeStringToFile(file, "Hello /u1234", StandardCharsets.UTF_8);
+
         final byte[] text = "Hello /u1234".getBytes();
         TestUtils.assertEqualContent(text, file);
     }
 
     @Test
-    public void testWriteStringToFile4() throws Exception {
-        final File file = new File(tempDirFile, "subdir/write.txt");
-        FileUtils.writeStringToFile(file, "Hello /u1234", (Charset) null);
-        final byte[] text = "Hello /u1234".getBytes();
+    public void testWriteStringToFileWithCharset() throws Exception {
+        final File file = new File(tempDirFile, "write.txt");
+        FileUtils.writeStringToFile(file, "Hello /u1234", "UTF8");
+        final byte[] text = "Hello /u1234".getBytes(StandardCharsets.UTF_8);
         TestUtils.assertEqualContent(text, file);
     }
 
@@ -3103,6 +3117,22 @@ public class FileUtilsTest extends AbstractTempDirTest {
     }
 
     @Test
+    public void testWriteStringToFileWithNullCharset() throws Exception {
+        final File file = new File(tempDirFile, "write.txt");
+        FileUtils.writeStringToFile(file, "Hello /u1234", (Charset) null);
+        final byte[] text = "Hello /u1234".getBytes();
+        TestUtils.assertEqualContent(text, file);
+    }
+
+    @Test
+    public void testWriteStringToFileWithNullStringCharset() throws Exception {
+        final File file = new File(tempDirFile, "write.txt");
+        FileUtils.writeStringToFile(file, "Hello /u1234", (String) null);
+        final byte[] text = "Hello /u1234".getBytes();
+        TestUtils.assertEqualContent(text, file);
+    }
+
+    @Test
     public void testWriteWithEncoding_WithAppendOptionFalse_ShouldDeletePreviousFileLines() throws Exception {
         final File file = TestUtils.newFile(tempDirFile, "lines.txt");
         FileUtils.writeStringToFile(file, "This line was there before you...");
diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsTest.java
index 6048b78..9f7427d 100644
--- a/src/test/java/org/apache/commons/io/file/PathUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/file/PathUtilsTest.java
@@ -33,6 +33,7 @@ import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.FileSystem;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.attribute.DosFileAttributeView;
@@ -69,7 +70,7 @@ public class PathUtilsTest extends AbstractTempDirTest {
      * <li>tempDirPath/subdir</li>
      * <li>tempDirPath/symlinked-dir -> tempDirPath/subdir</li>
      * </ol>
-     * 
+     *
      * @return Path to tempDirPath/subdir
      * @throws IOException if an I/O error occurs or the parent directory does not exist.
      */
@@ -129,7 +130,7 @@ public class PathUtilsTest extends AbstractTempDirTest {
     public void testCopyDirectoryForDifferentFilesystemsWithRelativePath() throws IOException {
         final Path archivePath = Paths.get(TEST_JAR_PATH);
         try (final FileSystem archive = openArchive(archivePath, false);
-                final FileSystem targetArchive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) {
+            final FileSystem targetArchive = openArchive(tempDirPath.resolve(TEST_JAR_NAME), true)) {
             final Path targetDir = targetArchive.getPath("targetDir");
             Files.createDirectory(targetDir);
             // relative jar -> relative dir
@@ -183,7 +184,7 @@ public class PathUtilsTest extends AbstractTempDirTest {
     public void testCreateDirectoriesSymlink() throws IOException {
         final Path symlinkedDir = createTempSymlinkedRelativeDir();
         final String leafDirName = "child";
-        final Path newDirFollowed = PathUtils.createParentDirectories(symlinkedDir.resolve(leafDirName), PathUtils.FOLLOW_LINKS);
+        final Path newDirFollowed = PathUtils.createParentDirectories(symlinkedDir.resolve(leafDirName), PathUtils.NULL_LINK_OPTION);
         assertEquals(Files.readSymbolicLink(symlinkedDir), newDirFollowed);
     }
 
@@ -279,14 +280,18 @@ public class PathUtilsTest extends AbstractTempDirTest {
     public void testNewOutputStreamNewFileInsideExistingSymlinkedDir() throws IOException {
         final Path symlinkDir = createTempSymlinkedRelativeDir();
         final Path file = symlinkDir.resolve("test.txt");
-        assertThrowsExactly(FileAlreadyExistsException.class, () -> PathUtils.newOutputStream(file, false));
-    }
-
-    @Test
-    public void testNewOutputStreamNewFileInsideExistingSymlinkedDirFollow() throws IOException {
-        final Path symlinkDir = createTempSymlinkedRelativeDir();
-        final Path file = symlinkDir.resolve("test.txt");
-        assertThrowsExactly(FileAlreadyExistsException.class, () -> PathUtils.newOutputStream(file, false));
+        try (OutputStream outputStream = PathUtils.newOutputStream(file, new LinkOption[] {})) {
+            // empty
+        }
+        try (OutputStream outputStream = PathUtils.newOutputStream(file, null)) {
+            // empty
+        }
+        try (OutputStream outputStream = PathUtils.newOutputStream(file, true)) {
+            // empty
+        }
+        try (OutputStream outputStream = PathUtils.newOutputStream(file, false)) {
+            // empty
+        }
     }
 
     @Test

[commons-io] 01/02: Add PathUtils.createParentDirectories(Path, LinkOption, FileAttribute...)

Posted by gg...@apache.org.
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 2adf0f089dbb0528056ffc6d9570756e0bbe2bce
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Feb 6 11:42:12 2022 -0500

    Add PathUtils.createParentDirectories(Path, LinkOption,
    FileAttribute<?>...)
---
 src/changes/changes.xml                            |   3 +
 .../java/org/apache/commons/io/file/PathUtils.java | 159 +++++++++++++--------
 .../org/apache/commons/io/file/PathUtilsTest.java  |  17 ++-
 3 files changed, 117 insertions(+), 62 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 74060bd..769d199 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -320,6 +320,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add CharsetDecoders.
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add PathUtils.createParentDirectories(Path, LinkOption, FileAttribute...).
+      </action>
       <!-- UPDATE -->
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Update FileEntry to use FileTime instead of long for file time stamps.
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 a15b944..1795eed 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -81,7 +81,8 @@ import org.apache.commons.io.function.IOFunction;
 public final class PathUtils {
 
     /**
-     * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted relative lists when comparing directories.
+     * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted relative
+     * lists when comparing directories.
      */
     private static class RelativeSortedPaths {
 
@@ -102,7 +103,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 FileVisitOption[] fileVisitOptions) throws IOException {
             final List<Path> tmpRelativeDirList1;
             final List<Path> tmpRelativeDirList2;
             List<Path> tmpRelativeFileList1 = null;
@@ -141,9 +142,9 @@ public final class PathUtils {
         }
     }
 
-    private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
+    private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
 
-    private static final OpenOption[] OPEN_OPTIONS_APPEND = { StandardOpenOption.CREATE, StandardOpenOption.APPEND };
+    private static final OpenOption[] OPEN_OPTIONS_APPEND = {StandardOpenOption.CREATE, StandardOpenOption.APPEND};
 
     /**
      * Empty {@link CopyOption} array.
@@ -174,7 +175,14 @@ public final class PathUtils {
      *
      * @since 2.9.0
      */
-    public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { LinkOption.NOFOLLOW_LINKS };
+    public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = {LinkOption.NOFOLLOW_LINKS};
+    
+    /**
+     * A LinkOption used to follow link in this class, the inverse of {@link LinkOption#NOFOLLOW_LINKS}.
+     *
+     * @since 2.12.0
+     */
+    public static final LinkOption FOLLOW_LINKS = null;
 
     /**
      * Empty {@link OpenOption} array.
@@ -251,7 +259,7 @@ public final class PathUtils {
     public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
         final Path absoluteSource = sourceDirectory.toAbsolutePath();
         return visitFileTree(new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions), absoluteSource)
-                .getPathCounters();
+            .getPathCounters();
     }
 
     /**
@@ -335,10 +343,29 @@ public final class PathUtils {
      * @since 2.9.0
      */
     public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException {
-        final Path parent = getParent(path);
+        return createParentDirectories(path, LinkOption.NOFOLLOW_LINKS, attrs);
+    }
+
+    /**
+     * Creates the parent directories for the given {@code path}.
+     *
+     * @param path The path to a file (or directory).
+     * @param linkOption A {@link LinkOption} or null.
+     * @param attrs An optional list of file attributes to set atomically when creating the directories.
+     * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
+     * @throws IOException if an I/O error occurs.
+     * @since 2.12.0
+     */
+    public static Path createParentDirectories(final Path path, final LinkOption linkOption, final FileAttribute<?>... attrs) throws IOException {
+        Path parent = getParent(path);
+        parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : readIfSymbolicLink(parent);
         return parent == null ? null : Files.createDirectories(parent, attrs);
     }
 
+    private static Path readIfSymbolicLink(Path path) throws IOException {
+        return Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path;
+    }
+
     /**
      * Gets the current directory.
      *
@@ -438,7 +465,7 @@ public final class PathUtils {
         final LinkOption[] linkOptions = PathUtils.NOFOLLOW_LINK_OPTION_ARRAY;
         // POSIX ops will noop on non-POSIX.
         return withPosixFileAttributes(getParent(directory), linkOptions, overrideReadOnly(deleteOptions),
-                pfa -> visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters());
+            pfa -> visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters());
     }
 
     /**
@@ -536,8 +563,8 @@ public final class PathUtils {
     }
 
     /**
-     * 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.
+     * 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.
      *
      * @param path1 The first directory.
      * @param path2 The second directory.
@@ -549,8 +576,8 @@ public final class PathUtils {
     }
 
     /**
-     * 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.
+     * 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.
      *
      * @param path1 The first directory.
      * @param path2 The second directory.
@@ -561,7 +588,7 @@ 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 FileVisitOption[] fileVisitOption) throws IOException {
         // First walk both file trees and gather normalized paths.
         if (path1 == null && path2 == null) {
             return true;
@@ -593,8 +620,8 @@ public final class PathUtils {
     }
 
     /**
-     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
-     * sub-directories.
+     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
+     * comparison includes all files in all sub-directories.
      *
      * @param path1 The first directory.
      * @param path2 The second directory.
@@ -606,8 +633,8 @@ public final class PathUtils {
     }
 
     /**
-     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
-     * sub-directories.
+     * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The
+     * comparison includes all files in all sub-directories.
      *
      * @param path1 The first directory.
      * @param path2 The second directory.
@@ -618,7 +645,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, final LinkOption[] linkOptions,
-            final FileVisitOption[] fileVisitOptions) throws IOException {
+        final FileVisitOption[] fileVisitOptions) throws IOException {
         return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
     }
 
@@ -659,7 +686,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 {
+        throws IOException {
         if (path1 == null && path2 == null) {
             return true;
         }
@@ -694,15 +721,15 @@ 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);
         }
     }
 
     /**
      * <p>
-     * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the original file list that matches the provided
-     * filter.
+     * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the original
+     * file list that matches the provided filter.
      * </p>
      *
      * <p>
@@ -819,10 +846,10 @@ public final class PathUtils {
      *
      * @param path the path to the file.
      * @param options options indicating how to handle symbolic links
-     * @return {@code true} if the file is a directory; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
-     *         determined if the file is a directory or not.
-     * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
-     *         checkRead} method is invoked to check read access to the directory.
+     * @return {@code true} if the file is a directory; {@code false} if the path is null, the file does not exist, is not a
+     *         directory, or it cannot be determined if the file is a directory or not.
+     * @throws SecurityException In the case of the default provider, and a security manager is installed, the
+     *         {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read access to the directory.
      * @since 2.9.0
      */
     public static boolean isDirectory(final Path path, final LinkOption... options) {
@@ -845,10 +872,11 @@ public final class PathUtils {
      *
      * @param directory the directory to query.
      * @return whether the directory is empty.
-     * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory <i>(optional specific exception)</i>.
+     * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory <i>(optional
+     *         specific exception)</i>.
      * @throws IOException if an I/O error occurs.
-     * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
-     *         checkRead} method is invoked to check read access to the directory.
+     * @throws SecurityException In the case of the default provider, and a security manager is installed, the
+     *         {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read access to the directory.
      */
     public static boolean isEmptyDirectory(final Path directory) throws IOException {
         try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
@@ -862,8 +890,8 @@ public final class PathUtils {
      * @param file the file to query.
      * @return whether the file is empty.
      * @throws IOException if an I/O error occurs.
-     * @throws SecurityException In the case of the default provider, and a security manager is installed, its {@link SecurityManager#checkRead(String)
-     *         checkRead} method denies read access to the file.
+     * @throws SecurityException In the case of the default provider, and a security manager is installed, its
+     *         {@link SecurityManager#checkRead(String) checkRead} method denies read access to the file.
      */
     public static boolean isEmptyFile(final Path file) throws IOException {
         return Files.size(file) <= 0;
@@ -1025,10 +1053,10 @@ public final class PathUtils {
      *
      * @param path the path to the file.
      * @param options options indicating how to handle symbolic links.
-     * @return {@code true} if the file is a regular file; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
-     *         determined if the file is a regular file or not.
-     * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
-     *         checkRead} method is invoked to check read access to the directory.
+     * @return {@code true} if the file is a regular file; {@code false} if the path is null, the file does not exist, is
+     *         not a directory, or it cannot be determined if the file is a regular file or not.
+     * @throws SecurityException In the case of the default provider, and a security manager is installed, the
+     *         {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read access to the directory.
      * @since 2.9.0
      */
     public static boolean isRegularFile(final Path path, final LinkOption... options) {
@@ -1048,7 +1076,8 @@ public final class PathUtils {
     }
 
     /**
-     * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes to the file.
+     * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes
+     * to the file.
      *
      * @param path the Path.
      * @param append Whether or not to append.
@@ -1086,8 +1115,8 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the BasicFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}. Throws
-     * {@link UncheckedIOExceptions} instead of {@link IOException}.
+     * Reads the BasicFileAttributes from the given path. Returns null instead of throwing
+     * {@link UnsupportedOperationException}. Throws {@link UncheckedIOExceptions} instead of {@link IOException}.
      *
      * @param <A> The {@code BasicFileAttributes} type
      * @param path The Path to test.
@@ -1123,7 +1152,8 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the BasicFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
+     * Reads the BasicFileAttributes from the given path. Returns null instead of throwing
+     * {@link UnsupportedOperationException}.
      *
      * @param path the path to read.
      * @param options options indicating how to handle symbolic links.
@@ -1135,7 +1165,8 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the BasicFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
+     * Reads the BasicFileAttributes from the given path. Returns null instead of throwing
+     * {@link UnsupportedOperationException}.
      *
      * @param path the path to read.
      * @return the path attributes.
@@ -1149,7 +1180,8 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the DosFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
+     * Reads the DosFileAttributes from the given path. Returns null instead of throwing
+     * {@link UnsupportedOperationException}.
      *
      * @param path the path to read.
      * @param options options indicating how to handle symbolic links.
@@ -1161,7 +1193,8 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the PosixFileAttributes or DosFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
+     * Reads the PosixFileAttributes or DosFileAttributes from the given path. Returns null instead of throwing
+     * {@link UnsupportedOperationException}.
      *
      * @param path The Path to read.
      * @param options options indicating how to handle symbolic links.
@@ -1174,7 +1207,8 @@ public final class PathUtils {
     }
 
     /**
-     * Reads the PosixFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
+     * Reads the PosixFileAttributes from the given path. Returns null instead of throwing
+     * {@link UnsupportedOperationException}.
      *
      * @param path The Path to read.
      * @param options options indicating how to handle symbolic links.
@@ -1217,7 +1251,8 @@ 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.
+     * 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.
@@ -1300,7 +1335,7 @@ public final class PathUtils {
      * @throws IOException if an I/O error occurs.
      */
     private static boolean setPosixDeletePermissions(final Path parent, final boolean enableDeleteChildren, final LinkOption... linkOptions)
-            throws IOException {
+        throws IOException {
         // To delete a file in POSIX, you need write and execute permissions on its parent directory.
         // @formatter:off
         return setPosixPermissions(parent, enableDeleteChildren, Arrays.asList(
@@ -1325,7 +1360,7 @@ public final class PathUtils {
      * @throws IOException if an I/O error occurs.
      */
     private static boolean setPosixPermissions(final Path path, final boolean addPermissions, final List<PosixFilePermission> updatePermissions,
-            final LinkOption... linkOptions) throws IOException {
+        final LinkOption... linkOptions) throws IOException {
         if (path != null) {
             final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
             if (addPermissions) {
@@ -1407,11 +1442,11 @@ public final class PathUtils {
     }
 
     /**
-     * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
-     * directory, then the size of the directory is calculated recursively.
+     * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size
+     * 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.
+     * 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.
      * </p>
      *
      * @param path the regular file or directory to return the size of, must not be {@code null}.
@@ -1427,8 +1462,8 @@ public final class PathUtils {
     }
 
     /**
-     * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
-     * directory, then the size of the directory is calculated recursively.
+     * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size
+     * is returned. If the argument is a directory, then the size of the directory is calculated recursively.
      *
      * @param path the regular file or directory to return the size of (must not be {@code null}).
      * @return the length of the file, or recursive size of the directory, provided (in bytes).
@@ -1445,12 +1480,13 @@ public final class PathUtils {
     /**
      * Counts the size of a directory recursively (sum of the size of all files).
      * <p>
-     * Note that overflow is not detected, and the return value may be negative if overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(Path)} for an
-     * alternative method that does not overflow.
+     * Note that overflow is not detected, and the return value may be negative if overflow occurs. See
+     * {@link #sizeOfDirectoryAsBigInteger(Path)} for an alternative method that does not overflow.
      * </p>
      *
      * @param directory directory to inspect, must not be {@code null}.
-     * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total is greater than {@link Long#MAX_VALUE}.
+     * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total is
+     *         greater than {@link Long#MAX_VALUE}.
      * @throws NullPointerException if the directory is {@code null}.
      * @throws IOException if an I/O error occurs.
      * @since 2.12.0
@@ -1515,7 +1551,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 start, final Set<FileVisitOption> options,
-            final int maxDepth) throws IOException {
+        final int maxDepth) throws IOException {
         Files.walkFileTree(start, options, maxDepth, visitor);
         return visitor;
     }
@@ -1556,7 +1592,8 @@ public final class PathUtils {
     /**
      * Waits for the file system to propagate a file creation, with a timeout.
      * <p>
-     * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} until it returns true up to the maximum time given.
+     * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} until it returns true up to the maximum time
+     * given.
      * </p>
      *
      * @param file the file to check, must not be {@code null}.
@@ -1606,13 +1643,13 @@ public final class PathUtils {
      * @since 2.9.0
      */
     public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, final boolean readAttributes,
-            final FileVisitOption... options) throws IOException {
+        final FileVisitOption... options) throws IOException {
         return Files.walk(start, maxDepth, options)
-                .filter(path -> pathFilter.accept(path, readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
+            .filter(path -> pathFilter.accept(path, readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE);
     }
 
     private static <R> R withPosixFileAttributes(final Path path, final LinkOption[] linkOptions, final boolean overrideReadOnly,
-            final IOFunction<PosixFileAttributes, R> function) throws IOException {
+        final IOFunction<PosixFileAttributes, R> function) throws IOException {
         final PosixFileAttributes posixFileAttributes = overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
         try {
             return function.apply(posixFileAttributes);
@@ -1635,7 +1672,7 @@ public final class PathUtils {
      * @since 2.12.0
      */
     public static Path writeString(final Path path, final CharSequence charSequence, final Charset charset, final OpenOption... openOptions)
-            throws IOException {
+        throws IOException {
         // Check the text is not null before opening file.
         Objects.requireNonNull(path, "path");
         Objects.requireNonNull(charSequence, "charSequence");
diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsTest.java
index eb4540c..6048b78 100644
--- a/src/test/java/org/apache/commons/io/file/PathUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/file/PathUtilsTest.java
@@ -180,7 +180,15 @@ public class PathUtilsTest extends AbstractTempDirTest {
     }
 
     @Test
-    public void testCreateDirectoriesWithClashingSymlink() throws IOException {
+    public void testCreateDirectoriesSymlink() throws IOException {
+        final Path symlinkedDir = createTempSymlinkedRelativeDir();
+        final String leafDirName = "child";
+        final Path newDirFollowed = PathUtils.createParentDirectories(symlinkedDir.resolve(leafDirName), PathUtils.FOLLOW_LINKS);
+        assertEquals(Files.readSymbolicLink(symlinkedDir), newDirFollowed);
+    }
+
+    @Test
+    public void testCreateDirectoriesSymlinkClashing() throws IOException {
         final Path symlinkedDir = createTempSymlinkedRelativeDir();
         assertThrowsExactly(FileAlreadyExistsException.class, () -> PathUtils.createParentDirectories(symlinkedDir.resolve("child")));
     }
@@ -275,6 +283,13 @@ public class PathUtilsTest extends AbstractTempDirTest {
     }
 
     @Test
+    public void testNewOutputStreamNewFileInsideExistingSymlinkedDirFollow() throws IOException {
+        final Path symlinkDir = createTempSymlinkedRelativeDir();
+        final Path file = symlinkDir.resolve("test.txt");
+        assertThrowsExactly(FileAlreadyExistsException.class, () -> PathUtils.newOutputStream(file, false));
+    }
+
+    @Test
     public void testReadAttributesPosix() throws IOException {
         boolean isPosix;
         try {