Simplify Path.copyTo implementation
Delegate most checks to the platform Files.copy. This changes the exception type thrown in one case, so document when it happens. Also 'copyTo' no longer creates target parent directory if it doesn't exist. #KT-19192
This commit is contained in:
@@ -120,7 +120,12 @@ internal object PathRelativizer {
|
||||
/**
|
||||
* Copies this path to the given [target] path.
|
||||
*
|
||||
* If some directories on a way to the [target] are missing, then they will be created.
|
||||
* Unlike `File.copyTo`, if some directories on a 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:
|
||||
* ```
|
||||
* sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() })
|
||||
* ```
|
||||
*
|
||||
* If the [target] path already exists, this function will fail unless [overwrite] argument is set to `true`.
|
||||
*
|
||||
* When [overwrite] is `true` and [target] is a directory, it is replaced only if it is empty.
|
||||
@@ -134,6 +139,8 @@ internal object PathRelativizer {
|
||||
* @return the [target] path.
|
||||
* @throws NoSuchFileException if the source path doesn't exist.
|
||||
* @throws FileAlreadyExistsException if the destination path already exists and [overwrite] argument is set to `false`.
|
||||
* @throws DirectoryNotEmptyException if the destination path point to an existing directory and [overwrite] argument is `true`,
|
||||
* when the directory being replaced is not empty.
|
||||
* @throws IOException if any errors occur while copying.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@@ -146,15 +153,20 @@ public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path {
|
||||
/**
|
||||
* Copies this path to the given [target] path.
|
||||
*
|
||||
* If some directories on a way to the [target] are missing, then they will be created.
|
||||
* Unlike `File.copyTo`, if some directories on a 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:
|
||||
* ```
|
||||
* sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() })
|
||||
* ```
|
||||
*
|
||||
* If the [target] path already exists, this function will fail unless the
|
||||
* [REPLACE_EXISTING][StandardCopyOption.REPLACE_EXISTING] is option is used.
|
||||
*
|
||||
* When [REPLACE_EXISTING][StandardCopyOption.REPLACE_EXISTING] is used and [target] is a directory,
|
||||
* it is replaced only if it is empty.
|
||||
*
|
||||
* If this path is a directory, it is copied without its content, i.e. an empty [target] directory is created.
|
||||
* If you want to copy directory including its contents, use [copyRecursively].
|
||||
* If this path is a directory, it is copied *without* its content, i.e. an empty [target] directory is created.
|
||||
* If you want to copy a directory including its contents, use [copyRecursively].
|
||||
*
|
||||
* The operation doesn't preserve copied file attributes such as creation/modification date,
|
||||
* permissions, etc. unless [COPY_ATTRIBUTES][StandardCopyOption.COPY_ATTRIBUTES] is used.
|
||||
@@ -163,36 +175,14 @@ public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path {
|
||||
* @return the [target] path.
|
||||
* @throws NoSuchFileException if the source path doesn't exist.
|
||||
* @throws FileAlreadyExistsException if the destination path already exists and [REPLACE_EXISTING][StandardCopyOption.REPLACE_EXISTING] is not used.
|
||||
* @throws DirectoryNotEmptyException if the destination path point to an existing directory and [REPLACE_EXISTING][StandardCopyOption.REPLACE_EXISTING] is used,
|
||||
* when the directory being replaced is not empty.
|
||||
* @throws IOException if any errors occur while copying.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public fun Path.copyTo(target: Path, vararg options: CopyOption): Path {
|
||||
if (this.notExists()) {
|
||||
throw NoSuchFileException(toString(), null, "The source path doesn't exist.")
|
||||
}
|
||||
|
||||
if (target.exists() && StandardCopyOption.REPLACE_EXISTING !in options) {
|
||||
throw FileAlreadyExistsException(toString(), null, "The destination path already exists.")
|
||||
}
|
||||
|
||||
if (this.isDirectory()) {
|
||||
if (target.isDirectory() && Files.newDirectoryStream(target).use { it.firstOrNull() } != null) {
|
||||
throw FileAlreadyExistsException(toString(), null, "The destination path already exists.")
|
||||
}
|
||||
try {
|
||||
Files.createDirectories(target)
|
||||
} catch (_: FileAlreadyExistsException) {
|
||||
// File already exists and is not a directory
|
||||
Files.delete(target)
|
||||
Files.createDirectories(target)
|
||||
}
|
||||
} else {
|
||||
target.parent?.let { Files.createDirectories(it) }
|
||||
Files.copy(this, target, *options)
|
||||
}
|
||||
|
||||
return target
|
||||
return Files.copy(this, target, *options)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,7 @@ class PathExtensionsTest {
|
||||
val dstFile = Files.createTempFile(null, null)
|
||||
try {
|
||||
srcFile.writeText("Hello, World!")
|
||||
assertFailsWith(FileAlreadyExistsException::class, "copy do not overwrite existing file") {
|
||||
assertFailsWith<FileAlreadyExistsException>("copy do not overwrite existing file") {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
|
||||
@@ -48,15 +48,24 @@ class PathExtensionsTest {
|
||||
assertSame(dst, dstFile)
|
||||
compareFiles(srcFile, dst, "copy with overwrite over existing file")
|
||||
|
||||
srcFile.copyTo(srcFile)
|
||||
srcFile.copyTo(srcFile, overwrite = true)
|
||||
compareFiles(dst, srcFile, "copying file to itself leaves it intact")
|
||||
|
||||
assertTrue(Files.deleteIfExists(dstFile))
|
||||
dst = srcFile.copyTo(dstFile)
|
||||
compareFiles(srcFile, dst, "copy to new file")
|
||||
|
||||
val subDst = dstFile.resolve("foo/bar")
|
||||
assertFailsWith<NoSuchFileException> { srcFile.copyTo(subDst) }
|
||||
assertFailsWith<NoSuchFileException> { srcFile.copyTo(subDst, overwrite = true) }
|
||||
assertTrue(Files.deleteIfExists(dstFile))
|
||||
assertFailsWith<NoSuchFileException> { srcFile.copyTo(subDst) }
|
||||
|
||||
Files.createDirectory(dstFile)
|
||||
val child = dstFile.resolve("child")
|
||||
Files.createFile(child)
|
||||
assertFailsWith(DirectoryNotEmptyException::class, "copy with overwrite do not overwrite non-empty dir") {
|
||||
assertFailsWith<DirectoryNotEmptyException>( "copy with overwrite do not overwrite non-empty dir") {
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
}
|
||||
Files.delete(child)
|
||||
@@ -67,21 +76,21 @@ class PathExtensionsTest {
|
||||
assertTrue(Files.deleteIfExists(srcFile))
|
||||
assertTrue(Files.deleteIfExists(dstFile))
|
||||
|
||||
assertFailsWith(NoSuchFileException::class) {
|
||||
assertFailsWith<NoSuchFileException> {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
|
||||
Files.createDirectory(srcFile)
|
||||
srcFile.resolve("somefile").writeText("some content")
|
||||
dstFile.writeText("")
|
||||
assertFailsWith(FileAlreadyExistsException::class, "copy dir do not overwrite file") {
|
||||
assertFailsWith<FileAlreadyExistsException>("copy dir do not overwrite file") {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
assertTrue(dstFile.isDirectory())
|
||||
assertTrue(dstFile.listDirectoryEntries().isEmpty(), "only directory is copied, but not its content")
|
||||
|
||||
assertFailsWith(FileAlreadyExistsException::class, "copy dir do not overwrite dir") {
|
||||
assertFailsWith<FileAlreadyExistsException>("copy dir do not overwrite dir") {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
|
||||
@@ -90,7 +99,7 @@ class PathExtensionsTest {
|
||||
assertTrue(dstFile.listDirectoryEntries().isEmpty(), "only directory is copied, but not its content")
|
||||
|
||||
dstFile.resolve("somefile2").writeText("some content2")
|
||||
assertFailsWith(FileAlreadyExistsException::class, "copy dir do not overwrite non-empty dir") {
|
||||
assertFailsWith<DirectoryNotEmptyException>("copy dir do not overwrite non-empty dir") {
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
}
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user