diff --git a/libraries/stdlib/jdk7/src/kotlin/io/path/PathRecursiveFunctions.kt b/libraries/stdlib/jdk7/src/kotlin/io/path/PathRecursiveFunctions.kt index 6706d3b1e9f..9684aa414bd 100644 --- a/libraries/stdlib/jdk7/src/kotlin/io/path/PathRecursiveFunctions.kt +++ b/libraries/stdlib/jdk7/src/kotlin/io/path/PathRecursiveFunctions.kt @@ -19,10 +19,10 @@ import java.nio.file.attribute.BasicFileAttributes * Note that if this function throws, partial copying may have taken place. * * Unlike `File.copyRecursively`, if some directories on the way to the [target] are missing, then they won't be created automatically. - * You can use the following approach to ensure that required intermediate directories are created: + * You can use the [createParentDirectories] function to ensure that required intermediate directories are created: * ``` * sourcePath.copyToRecursively( - * destinationPath.apply { parent?.createDirectories() }, + * destinationPath.createParentDirectories(), * followLinks = false * ) * ``` @@ -93,10 +93,10 @@ public fun Path.copyToRecursively( * Note that if this function throws, partial copying may have taken place. * * Unlike `File.copyRecursively`, if some directories on the way to the [target] are missing, then they won't be created automatically. - * You can use the following approach to ensure that required intermediate directories are created: + * You can use the [createParentDirectories] function to ensure that required intermediate directories are created: * ``` * sourcePath.copyToRecursively( - * destinationPath.apply { parent?.createDirectories() }, + * destinationPath.createParentDirectories(), * followLinks = false * ) * ``` diff --git a/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt b/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt index ce58fc03f4d..279c01155bc 100644 --- a/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt +++ b/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt @@ -189,9 +189,9 @@ private object PathRelativizer { * Copies a file or directory located by this path to the given [target] path. * * Unlike `File.copyTo`, if some directories on the way to the [target] are missing, then they won't be created automatically. - * You can use the following approach to ensure that required intermediate directories are created: + * You can use the [createParentDirectories] function to ensure that required intermediate directories are created: * ``` - * sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() }) + * sourcePath.copyTo(destinationPath.createParentDirectories()) * ``` * * If the [target] path already exists, this function will fail unless [overwrite] argument is set to `true`. @@ -226,9 +226,9 @@ public inline fun Path.copyTo(target: Path, overwrite: Boolean = false): Path { * Copies a file or directory located by this path to the given [target] path. * * Unlike `File.copyTo`, if some directories on the way to the [target] are missing, then they won't be created automatically. - * You can use the following approach to ensure that required intermediate directories are created: + * You can use the [createParentDirectories] function to ensure that required intermediate directories are created: * ``` - * sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() }) + * sourcePath.copyTo(destinationPath.createParentDirectories()) * ``` * * If the [target] path already exists, this function will fail unless the @@ -500,6 +500,8 @@ public inline fun Path.deleteIfExists() = * when creating the directory. * * @see Files.createDirectory + * @see Path.createDirectories + * @see Path.createParentDirectories */ @SinceKotlin("1.5") @WasExperimental(ExperimentalPathApi::class) @@ -513,15 +515,20 @@ public inline fun Path.createDirectory(vararg attributes: FileAttribute<*>): Pat * * If the directory already exists, this function does not throw an exception, unlike [Path.createDirectory]. * + * @return the path of this directory if it already exists or has been created successfully. + * The returned path can be converted [Path.toAbsolutePath][to absolute path] if it was relative. + * * @param attributes an optional list of file attributes to set atomically when creating the directory. * - * @throws FileAlreadyExistsException if there is already a file located by this path + * @throws FileAlreadyExistsException if there is already a file located by this path or one of its parent paths * (optional specific exception, some implementations may throw more general [IOException]). * @throws IOException if an I/O error occurs. - * @throws UnsupportedOperationException if the [attributes ]array contains an attribute that cannot be set atomically + * @throws UnsupportedOperationException if the [attributes] array contains an attribute that cannot be set atomically * when creating the directory. * * @see Files.createDirectories + * @see Path.createDirectory + * @see Path.createParentDirectories */ @SinceKotlin("1.5") @WasExperimental(ExperimentalPathApi::class) @@ -530,6 +537,33 @@ public inline fun Path.createDirectory(vararg attributes: FileAttribute<*>): Pat public inline fun Path.createDirectories(vararg attributes: FileAttribute<*>): Path = Files.createDirectories(this, *attributes) +/** + * Ensures that all parent directories of this path exist, creating them if required. + * + * If the parent directory already exists, this function does nothing. + * + * Note that the [parent][Path.getParent] directory is not always the directory that contains the entry specified by this path. + * For example, the parent of the path `x/y/.` is `x/y`, which is logically the same directory, + * and the parent of `x/y/..` (which means just `x/`) is also `x/y`. + * Use the function [Path.normalize] to eliminate redundant name elements from the path. + * + * @param attributes an optional list of file attributes to set atomically when creating the missing parent directories. + * + * @return this path unchanged if all parent directories already exist or have been created successfully. + * + * @throws FileAlreadyExistsException if there is already a file located by the [parent][Path.getParent] path or one of its parent paths + * (optional specific exception, some implementations may throw more general [IOException]). + * @throws IOException if an I/O error occurs. + * @throws UnsupportedOperationException if the [attributes] array contains an attribute that cannot be set atomically + * when creating the directory. + * + * @see Path.getParent + * @see Path.createDirectories + */ +@SinceKotlin("1.9") +@Throws(IOException::class) +public fun Path.createParentDirectories(vararg attributes: FileAttribute<*>): Path = + this.apply { parent?.createDirectories(*attributes) } /** * Moves or renames the file located by this path to the [target] path. diff --git a/libraries/stdlib/jdk7/test/PathExtensionsTest.kt b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt index 11d881d26a5..55edbf6d6a7 100644 --- a/libraries/stdlib/jdk7/test/PathExtensionsTest.kt +++ b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt @@ -59,6 +59,45 @@ class PathExtensionsTest : AbstractPathTest() { assertFailsWith { file.createFile() } } + @Test + fun createParentDirectories() { + val dir = createTempDirectory().cleanupRecursively() + val file = dir / "test-dir" / "sub-dir" / "new-file" + val parent = file.parent!! + + assertTrue(file.notExists()) + assertTrue(parent.notExists()) + + val result = file.createParentDirectories() + assertTrue(file.notExists()) + assertTrue(parent.isDirectory()) + assertEquals(file, result) + + file.createFile() + file.createParentDirectories() + assertTrue(file.exists()) + assertTrue(parent.isDirectory()) + } + + @Test + fun createParentDirectoriesRelativePath() { + run { + val path = Path("test_path_without_parent") + path.createParentDirectories() + assertTrue(path.toAbsolutePath().parent!!.isDirectory()) + } + + run { + val path = Path("build/test_subdirectory/test_path") + val parent = path.parent!! + assertTrue(parent.notExists()) + parent.cleanupRecursively() + path.createParentDirectories() + assertTrue(path.notExists()) + assertTrue(parent.isDirectory()) + } + } + @Test fun createTempFileDefaultDir() { val file1 = createTempFile().cleanup() diff --git a/libraries/stdlib/jdk7/test/PathRecursiveFunctionsTest.kt b/libraries/stdlib/jdk7/test/PathRecursiveFunctionsTest.kt index 25c9eba1471..b9f648aad49 100644 --- a/libraries/stdlib/jdk7/test/PathRecursiveFunctionsTest.kt +++ b/libraries/stdlib/jdk7/test/PathRecursiveFunctionsTest.kt @@ -359,7 +359,7 @@ class PathRecursiveFunctionsTest : AbstractPathTest() { OnErrorResult.SKIP_SUBTREE }) - src.copyToRecursively(dst.apply { parent?.createDirectories() }, followLinks = false) + src.copyToRecursively(dst.createParentDirectories(), followLinks = false) } @Test diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 7f545c7ace7..8ef07981da5 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -3361,6 +3361,7 @@ public final class kotlin/io/path/PathsKt { public static final fun copyToRecursively (Ljava/nio/file/Path;Ljava/nio/file/Path;Lkotlin/jvm/functions/Function3;ZZ)Ljava/nio/file/Path; public static synthetic fun copyToRecursively$default (Ljava/nio/file/Path;Ljava/nio/file/Path;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/nio/file/Path; public static synthetic fun copyToRecursively$default (Ljava/nio/file/Path;Ljava/nio/file/Path;Lkotlin/jvm/functions/Function3;ZZILjava/lang/Object;)Ljava/nio/file/Path; + public static final fun createParentDirectories (Ljava/nio/file/Path;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/file/Path; public static final fun createTempDirectory (Ljava/nio/file/Path;Ljava/lang/String;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/file/Path; public static synthetic fun createTempDirectory$default (Ljava/nio/file/Path;Ljava/lang/String;[Ljava/nio/file/attribute/FileAttribute;ILjava/lang/Object;)Ljava/nio/file/Path; public static final fun createTempFile (Ljava/nio/file/Path;Ljava/lang/String;Ljava/lang/String;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/file/Path;