Implement additional Path extensions

#KT-19192
This commit is contained in:
Ilya Gorbunov
2020-10-09 02:14:45 +03:00
parent 038f1cbd1e
commit a43cc0d1f9
5 changed files with 1044 additions and 151 deletions
@@ -18,6 +18,9 @@ import java.nio.file.StandardOpenOption
/**
* Returns a new [InputStreamReader] for reading the content of this file.
*
* @param charset character set to use for reading text, UTF-8 by default.
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -29,9 +32,9 @@ public inline fun Path.reader(charset: Charset = Charsets.UTF_8, vararg options:
/**
* Returns a new [BufferedReader] for reading the content of this file.
*
* @param charset character set to use.
* @param charset character set to use for reading text, UTF-8 by default.
* @param bufferSize necessary size of the buffer.
* @param options options to determine how the file is opened
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -46,6 +49,9 @@ public inline fun Path.bufferedReader(
/**
* Returns a new [OutputStreamWriter] for writing the content of this file.
*
* @param charset character set to use for writing text, UTF-8 by default.
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -57,7 +63,7 @@ public inline fun Path.writer(charset: Charset = Charsets.UTF_8, vararg options:
/**
* Returns a new [BufferedWriter] for writing the content of this file.
*
* @param charset character set to use.
* @param charset character set to use for writing text, UTF-8 by default.
* @param bufferSize necessary size of the buffer.
* @param options options to determine how the file is opened.
*/
@@ -87,7 +93,7 @@ public inline fun Path.readBytes(): ByteArray {
}
/**
* Write an [array] of bytes to this file.
* Writes an [array] of bytes to this file.
*
* By default, the file will be overwritten if it already exists, but you can control this behavior
* with [options].
@@ -110,30 +116,33 @@ public inline fun Path.writeBytes(array: ByteArray, vararg options: OpenOption):
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.appendBytes(array: ByteArray): Unit {
writeBytes(array, StandardOpenOption.APPEND)
public inline fun Path.appendBytes(array: ByteArray) {
Files.write(this, array, StandardOpenOption.APPEND)
}
/**
* Gets the entire content of this file as a String using UTF-8 or specified [charset].
* Gets the entire content of this file as a String using UTF-8 or the specified [charset].
*
* This method is not recommended on huge files. It has an internal limitation of 2 GB file size.
*
* @param charset character set to use.
* @param charset character set to use for reading text, UTF-8 by default.
* @return the entire content of this file as a String.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public fun Path.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset)
@kotlin.internal.InlineOnly
public inline fun Path.readText(charset: Charset = Charsets.UTF_8): String =
readBytes().toString(charset)
/**
* Sets the content of this file as [text] encoded using UTF-8 or specified [charset].
* Sets the content of this file as [text] encoded using UTF-8 or the specified [charset].
*
* By default, the file will be overwritten if it already exists, but you can control this behavior
* with [options].
*
* @param text text to write into file.
* @param charset character set to use.
* @param charset character set to use for writing text, UTF-8 by default.
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -145,7 +154,7 @@ public fun Path.writeText(text: String, charset: Charset = Charsets.UTF_8, varar
* Appends [text] to the content of this file using UTF-8 or the specified [charset].
*
* @param text text to append to file.
* @param charset character set to use.
* @param charset character set to use for writing text, UTF-8 by default.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -160,7 +169,7 @@ public fun Path.appendText(text: String, charset: Charset = Charsets.UTF_8): Uni
* You may use this function on huge files.
*
* @param options options to determine how the file is opened.
* @param charset character set to use.
* @param charset character set to use for reading text, UTF-8 by default.
* @param action function to process file lines.
*/
@SinceKotlin("1.4")
@@ -203,7 +212,7 @@ public inline fun Path.outputStream(vararg options: OpenOption): OutputStream {
*
* Do not use this function for huge files.
*
* @param charset character set to use. By default uses UTF-8 charset.
* @param charset character set to use for reading text, UTF-8 by default.
* @return list of file lines.
*/
@SinceKotlin("1.4")
@@ -217,7 +226,7 @@ public inline fun Path.readLines(charset: Charset = Charsets.UTF_8): List<String
* Calls the [block] callback giving it a sequence of all the lines in this file and closes the reader once
* the processing is complete.
* @param charset character set to use. By default uses UTF-8 charset.
* @param charset character set to use for reading text, UTF-8 by default.
* @return the value returned by [block].
*/
@SinceKotlin("1.4")
@@ -226,3 +235,59 @@ public inline fun Path.readLines(charset: Charset = Charsets.UTF_8): List<String
public inline fun <T> Path.useLines(charset: Charset = Charsets.UTF_8, block: (Sequence<String>) -> T): T {
return bufferedReader(charset).use { block(it.lineSequence()) }
}
/**
* Write the specified collection of char sequences [lines] to a file terminating each one with the platform's line separator.
*
* By default, the file will be overwritten if it already exists, but you can control this behavior
* with [options].
*
* @param charset character set to use for writing text, UTF-8 by default.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.writeLines(lines: Iterable<CharSequence>, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Path {
return Files.write(this, lines, charset, *options)
}
/**
* Write the specified sequence of char sequences [lines] to a file terminating each one with the platform's line separator.
*
* By default, the file will be overwritten if it already exists, but you can control this behavior
* with [options].
*
* @param charset character set to use for writing text, UTF-8 by default.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.writeLines(lines: Sequence<CharSequence>, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Path {
return Files.write(this, lines.asIterable(), charset, *options)
}
/**
* Appends the specified collection of char sequences [lines] to a file terminating each one with the platform's line separator.
*
* @param charset character set to use for writing text, UTF-8 by default.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.appendLines(lines: Iterable<CharSequence>, charset: Charset = Charsets.UTF_8): Path {
return Files.write(this, lines, charset, StandardOpenOption.APPEND)
}
/**
* Appends the specified sequence of char sequences [lines] to a file terminating each one with the platform's line separator.
*
* @param charset character set to use for writing text, UTF-8 by default.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.appendLines(lines: Sequence<CharSequence>, charset: Charset = Charsets.UTF_8): Path {
return Files.write(this, lines.asIterable(), charset, StandardOpenOption.APPEND)
}
@@ -10,12 +10,33 @@
package kotlin.io.path
import java.io.IOException
import java.net.URI
import java.nio.file.*
import java.nio.file.FileAlreadyExistsException
import java.nio.file.NoSuchFileException
import java.nio.file.attribute.*
/**
* Returns the extension of this path (not including the dot), or an empty string if it doesn't have one.
* Returns the name of the file or directory denoted by this path as a string,
* or an empty string if this path has zero path elements.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public val Path.name: String
get() = fileName?.toString().orEmpty()
/**
* Returns the [name][Path.name] of this file or directory without an extension,
* or an empty string if this path has zero path elements.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public val Path.nameWithoutExtension: String
get() = fileName?.toString()?.substringBeforeLast(".") ?: ""
/**
* Returns the extension of this path (not including the dot),
* or an empty string if it doesn't have one.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -34,21 +55,14 @@ public val Path.invariantSeparatorsPath: String
return if (separator != "/") toString().replace(separator, "/") else toString()
}
/**
* Returns this path's [fileName][Path.getFileName] without an extension, or an empty string if
* this path has zero elements.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public val Path.nameWithoutExtension: String
get() = fileName?.toString()?.substringBeforeLast(".") ?: ""
/**
* Calculates the relative path for this path from a [base] path.
*
* Note that the [base] path is treated as a directory.
* If this path matches the [base] path, then a [Path] with an empty path will be returned.
*
* @return Path with relative path from [base] to this.
* @return the relative path from [base] to this.
*
* @throws IllegalArgumentException if this and base paths have different roots.
*/
@@ -57,15 +71,16 @@ public val Path.nameWithoutExtension: String
public fun Path.relativeTo(base: Path): Path = try {
PathRelativizer.tryRelativeTo(this, base)
} catch (e: IllegalArgumentException) {
throw java.lang.IllegalArgumentException(e.message + "\nthis path: $this\nbase path: $base", e)
throw IllegalArgumentException(e.message + "\nthis path: $this\nbase path: $base", e)
}
/**
* Calculates the relative path for this path from a [base] path.
*
* Note that the [base] path is treated as a directory.
* If this path matches the [base] path, then a [Path] with an empty path will be returned.
*
* @return Path with relative path from [base] to this, or `this` if this and base paths have different roots.
* @return the relative path from [base] to this, or `this` if this and base paths have different roots.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -74,10 +89,11 @@ public fun Path.relativeToOrSelf(base: Path): Path =
/**
* Calculates the relative path for this path from a [base] path.
*
* Note that the [base] path is treated as a directory.
* If this path matches the [base] path, then a [Path] with an empty path will be returned.
*
* @return Path with relative path from [base] to this, or `null` if this and base paths have different roots.
* @return the relative path from [base] to this, or `null` if this and base paths have different roots.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -87,7 +103,7 @@ public fun Path.relativeToOrNull(base: Path): Path? = try {
null
}
internal object PathRelativizer {
private object PathRelativizer {
private val emptyPath = Paths.get("")
private val parentPath = Paths.get("..")
@@ -117,9 +133,9 @@ internal object PathRelativizer {
}
/**
* Copies this path to the given [target] path.
* Copies a file or directory located by this path to the given [target] path.
*
* Unlike `File.copyTo`, if some directories on a way to the [target] are missing, then they won't be created automatically.
* 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:
* ```
* sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() })
@@ -141,18 +157,21 @@ internal object PathRelativizer {
* @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.
*
* @see Files.copy
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path {
val options = if (overwrite) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
return copyTo(target, *options)
@kotlin.internal.InlineOnly
public inline fun Path.copyTo(target: Path, overwrite: Boolean = false): Path {
val options = if (overwrite) arrayOf<CopyOption>(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
return Files.copy(this, target, *options)
}
/**
* Copies this path to the given [target] path.
* Copies a file or directory located by this path to the given [target] path.
*
* Unlike `File.copyTo`, if some directories on a way to the [target] are missing, then they won't be created automatically.
* 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:
* ```
* sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() })
@@ -177,17 +196,25 @@ public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path {
* @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.
*
* @see Files.copy
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public fun Path.copyTo(target: Path, vararg options: CopyOption): Path {
@kotlin.internal.InlineOnly
public inline fun Path.copyTo(target: Path, vararg options: CopyOption): Path {
return Files.copy(this, target, *options)
}
/**
* Check if this path exists.
* Checks if the file located by this path exists.
*
* @param options Options to control how symbolic links are handled.
* @return `true`, if the file definitely exists, `false` otherwise,
* including situations when the existence cannot be determined.
*
* @param options options to control how symbolic links are handled.
*
* @see Files.exists
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -195,9 +222,14 @@ public fun Path.copyTo(target: Path, vararg options: CopyOption): Path {
public inline fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options)
/**
* Check if this path does not exist.
* Checks if the file located by this path does not exist.
*
* @param options Options to control how symbolic links are handled.
* @return `true`, if the file definitely does not exist, `false` otherwise,
* including situations when the existence cannot be determined.
*
* @param options options to control how symbolic links are handled.
*
* @see Files.notExists
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -205,9 +237,11 @@ public inline fun Path.exists(vararg options: LinkOption): Boolean = Files.exist
public inline fun Path.notExists(vararg options: LinkOption): Boolean = Files.notExists(this, *options)
/**
* Check if this path is a file.
* Checks if the file located by this path is a regular file.
*
* @param options Options to control how symbolic links are handled.
* @param options options to control how symbolic links are handled.
*
* @see Files.isRegularFile
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -215,11 +249,13 @@ public inline fun Path.notExists(vararg options: LinkOption): Boolean = Files.no
public inline fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options)
/**
* Check if this path is a directory.
* Checks if the file located by this path is a directory.
*
* By default, symbolic links in the path are followed.
*
* @param options Options to control how symbolic links are handled.
* @param options options to control how symbolic links are handled.
*
* @see Files.isDirectory
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -227,7 +263,9 @@ public inline fun Path.isRegularFile(vararg options: LinkOption): Boolean = File
public inline fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options)
/**
* Check if this path exists and is a symbolic link.
* Checks if the file located by this path exists and is a symbolic link.
*
* @see Files.isSymbolicLink
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -235,7 +273,9 @@ public inline fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.
public inline fun Path.isSymbolicLink(): Boolean = Files.isSymbolicLink(this)
/**
* Check if this path exists and is executable.
* Checks if the file located by this path exists and is executable.
*
* @see Files.isExecutable
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -243,10 +283,12 @@ public inline fun Path.isSymbolicLink(): Boolean = Files.isSymbolicLink(this)
public inline fun Path.isExecutable(): Boolean = Files.isExecutable(this)
/**
* Check if this path is considered hidden.
* Checks if the file located by this path is considered hidden.
*
* This check is dependant on the current filesystem. For example, on UNIX-like operating systems, a
* path is considered hidden if its name begins with a dot. On Windows, file attributes are checked.
*
* @see Files.isHidden
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -254,7 +296,9 @@ public inline fun Path.isExecutable(): Boolean = Files.isExecutable(this)
public inline fun Path.isHidden(): Boolean = Files.isHidden(this)
/**
* Check if this path exists and is readable.
* Checks if the file located by this path exists and is readable.
*
* @see Files.isReadable
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -262,7 +306,9 @@ public inline fun Path.isHidden(): Boolean = Files.isHidden(this)
public inline fun Path.isReadable(): Boolean = Files.isReadable(this)
/**
* Check that this path exists and is writable.
* Checks if the file located by this path exists and is writable.
*
* @see Files.isWritable
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@@ -270,21 +316,23 @@ public inline fun Path.isReadable(): Boolean = Files.isReadable(this)
public inline fun Path.isWritable(): Boolean = Files.isWritable(this)
/**
* Check if this path points to the same file or directory as [other].
* Checks if the file located by this path points to the same file or directory as [other].
*
* @see Files.isSameFile
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.isSameFile(other: Path): Boolean = Files.isSameFile(this, other)
public inline fun Path.isSameFileAs(other: Path): Boolean = Files.isSameFile(this, other)
/**
* Return a list of the entries in this directory optionally filtered by matching against the specified [glob] pattern.
* Returns a list of the entries in this directory optionally filtered by matching against the specified [glob] pattern.
*
* @param glob the globbing pattern. The syntax is specified by the [FileSystem.getPathMatcher] method.
*
* @throws java.util.regex.PatternSyntaxException if the glob pattern is invalid.
* @throws NotDirectoryException If this path does not refer to a directory
* @throws IOException If an I/O error occurs
* @throws NotDirectoryException If this path does not refer to a directory.
* @throws IOException If an I/O error occurs.
*
* @see Files.newDirectoryStream
*/
@@ -295,37 +343,564 @@ public fun Path.listDirectoryEntries(glob: String = "*"): List<Path> {
}
/**
* Call the [block] callback with a sequence of all entries in this directory
* Calls the [block] callback with a sequence of all entries in this directory
* optionally filtered by matching against the specified [glob] pattern.
*
* @param glob the globbing pattern. The syntax is specified by the [FileSystem.getPathMatcher] method.
*
* @throws java.util.regex.PatternSyntaxException if the glob pattern is invalid.
* @throws NotDirectoryException If this path does not refer to a directory
* @throws IOException If an I/O error occurs
* @return the value returned by [block]
* @throws NotDirectoryException If this path does not refer to a directory.
* @throws IOException If an I/O error occurs.
* @return the value returned by [block].
*
* @see Files.newDirectoryStream
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public fun <T> Path.useDirectoryEntries(glob: String = "*", block: (Sequence<Path>) -> T): T {
@kotlin.internal.InlineOnly
public inline fun <T> Path.useDirectoryEntries(glob: String = "*", block: (Sequence<Path>) -> T): T {
return Files.newDirectoryStream(this, glob).use { block(it.asSequence()) }
}
/**
* Perform the given [action] on each entry in this directory optionally filtered by matching against the specified [glob] pattern.
* Performs the given [action] on each entry in this directory optionally filtered by matching against the specified [glob] pattern.
*
* @param glob the globbing pattern. The syntax is specified by the [FileSystem.getPathMatcher] method.
*
* @throws java.util.regex.PatternSyntaxException if the glob pattern is invalid.
* @throws NotDirectoryException If this path does not refer to a directory
* @throws IOException If an I/O error occurs
* @throws NotDirectoryException If this path does not refer to a directory.
* @throws IOException If an I/O error occurs.
*
* @see Files.newDirectoryStream
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
public fun Path.forEachDirectoryEntry(glob: String = "*", action: (Path) -> Unit) {
@kotlin.internal.InlineOnly
public inline fun Path.forEachDirectoryEntry(glob: String = "*", action: (Path) -> Unit) {
return Files.newDirectoryStream(this, glob).use { it.forEach(action) }
}
/**
* Returns the size of a regular file as a [Long] value of bytes or throws an exception if the file doesn't exist.
*
* @throws IOException if an I/O error occurred.
* @see Files.size
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.fileSize(): Long =
Files.size(this)
/**
* Deletes the existing file or empty directory specified by this path.
*
* @throws NoSuchFileException if the file or directory does not exist.
* @throws DirectoryNotEmptyException if the directory exists but is not empty.
*
* @see Files.delete
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.deleteExisting() {
Files.delete(this)
}
/**
* Deletes the file or empty directory specified by this path if it exists.
*
* @return `true` if the existing file was successfully deleted, `false` if the file does not exist.
*
* @throws DirectoryNotEmptyException if the directory exists but is not empty
*
* @see Files.deleteIfExists
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.deleteIfExists() =
Files.deleteIfExists(this)
/**
* Creates a new directory or throws an exception if there is already a file or directory located by this path.
*
* Note that the parent directory where this directory is going to be created must already exist.
* If you need to create all non-existent parent directories, use [Path.createDirectories].
*
* @param attributes an optional list of file attributes to set atomically when creating the directory.
*
* @throws FileAlreadyExistsException if there is already a file or directory located by this path
* (optional specific exception, some implementations may throw more general [IOException]).
* @throws IOException if an I/O error occurs or the parent directory does not exist.
* @throws UnsupportedOperationException if the [attributes ]array contains an attribute that cannot be set atomically
* when creating the directory.
*
* @see Files.createDirectory
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.createDirectory(vararg attributes: FileAttribute<*>): Path =
Files.createDirectory(this, *attributes)
/**
* Creates a directory ensuring that all nonexistent parent directories exist by creating them first.
*
* If the directory already exists, this function does not throw an exception, unlike [Path.createDirectory].
*
* @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
* (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 Files.createDirectories
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.createDirectories(vararg attributes: FileAttribute<*>): Path =
Files.createDirectories(this, *attributes)
/**
* Moves or renames the file located by this path to the [target] path.
*
* @param options options specifying how the move should be done, see [StandardCopyOption], [LinkOption].
*
* @throws FileAlreadyExistsException if the target file exists but cannot be replaced because the
* [StandardCopyOption.REPLACE_EXISTING] option is not specified (optional specific exception).
* @throws DirectoryNotEmptyException the [StandardCopyOption.REPLACE_EXISTING] option is specified but the file
* cannot be replaced because it is a non-empty directory, or the
* source is a non-empty directory containing entries that would
* be required to be moved (optional specific exception).
*
* @see Files.move
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.moveTo(target: Path, vararg options: CopyOption): Path =
Files.move(this, target, *options)
/**
* Moves or renames the file located by this path to the [target] path.
*
* @param overwrite allows to overwrite the target if it already exists.
*
* @throws FileAlreadyExistsException if the target file exists but cannot be replaced because the
* `overwrite = true` option is not specified (optional specific exception).
* @throws DirectoryNotEmptyException the `overwrite = true` option is specified but the file
* cannot be replaced because it is a non-empty directory, or the
* source is a non-empty directory containing entries that would
* be required to be moved (optional specific exception).
*
* @see Files.move
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.moveTo(target: Path, overwrite: Boolean = false): Path {
val options = if (overwrite) arrayOf<CopyOption>(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
return Files.move(this, target, *options)
}
/**
* Returns the [FileStore] representing the file store where a file is located.
*
* @see Files.getFileStore
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.fileStore(): FileStore =
Files.getFileStore(this)
/**
* Reads the value of a file attribute.
*
* The attribute name is specified with the [attribute] parameter optionally prefixed with the attribute view name:
* ```
* [view_name:]attribute_name
* ```
* When the view name is not specified, it defaults to `basic`.
*
* @throws UnsupportedOperationException if the attribute view is not supported.
* @throws IllegalArgumentException if the attribute name is not specified or is not recognized.
* @see Files.getAttribute
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.getAttribute(attribute: String, vararg options: LinkOption): Any? =
Files.getAttribute(this, attribute, *options)
/**
* Sets the value of a file attribute.
*
* The attribute name is specified with the [attribute] parameter optionally prefixed with the attribute view name:
* ```
* [view_name:]attribute_name
* ```
* When the view name is not specified, it defaults to `basic`.
*
* @throws UnsupportedOperationException if the attribute view is not supported.
* @throws IllegalArgumentException if the attribute name is not specified or is not recognized, or
* the attribute value is of the correct type but has an inappropriate value.
* @throws ClassCastException if the attribute value is not of the expected type
* @see Files.setAttribute
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.setAttribute(attribute: String, value: Any?, vararg options: LinkOption): Path =
Files.setAttribute(this, attribute, value, *options)
/**
* Returns a file attributes view of a given type [V]
* or `null` if the requested attribute view type is not available.
*
* The returned view allows to read and optionally to modify attributes of a file.
*
* @param V the reified type of the desired attribute view.
*
* @see Files.getFileAttributeView
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun <reified V : FileAttributeView> Path.fileAttributesViewOrNull(vararg options: LinkOption): V? =
Files.getFileAttributeView(this, V::class.java, *options)
/**
* Returns a file attributes view of a given type [V]
* or throws an [UnsupportedOperationException] if the requested attribute view type is not available..
*
* The returned view allows to read and optionally to modify attributes of a file.
*
* @param V the reified type of the desired attribute view, a subtype of [FileAttributeView].
*
* @see Files.getFileAttributeView
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun <reified V : FileAttributeView> Path.fileAttributesView(vararg options: LinkOption): V =
Files.getFileAttributeView(this, V::class.java, *options) ?: fileAttributeViewNotAvailable(this, V::class.java)
@PublishedApi
internal fun fileAttributeViewNotAvailable(path: Path, attributeViewClass: Class<*>): Nothing =
throw UnsupportedOperationException("The desired attribute view type $attributeViewClass is not available for the file $path.")
/**
* Reads a file's attributes of the specified type [A] in bulk.
*
* @param A the reified type of the desired attributes, a subtype of [BasicFileAttributes].
*
* @throws UnsupportedOperationException if the given attributes type [A] is not supported.
* @see Files.readAttributes
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun <reified A : BasicFileAttributes> Path.readAttributes(vararg options: LinkOption): A =
Files.readAttributes(this, A::class.java, *options)
/**
* Reads the specified list of attributes of a file in bulk.
*
* The list of [attributes] to read is specified in the following string form:
* ```
* [view:]attribute_name1[,attribute_name2...]
* ```
* So the names are comma-separated and optionally prefixed by the attribute view type name, `basic` by default.
* The special `*` attribute name can be used to read all attributes of the specified view.
*
* @return a [Map<String, Any?>][Map] having an entry for an each attribute read, where the key is the attribute name and the value is the attribute value.
* @throws UnsupportedOperationException if the attribute view is not supported.
* @throws IllegalArgumentException if no attributes are specified or an unrecognized attribute is specified.
* @see Files.readAttributes
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.readAttributes(attributes: String, vararg options: LinkOption): Map<String, Any?> =
Files.readAttributes(this, attributes, *options)
/**
* Returns the last modified time of the file located by this path.
*
* If the file system does not support modification timestamps, some implementation-specific default is returned.
*
* @see Files.getLastModifiedTime
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.getLastModifiedTime(vararg options: LinkOption): FileTime =
Files.getLastModifiedTime(this, *options)
/**
* Sets the last modified time attribute for the file located by this path.
*
* If the file system does not support modification timestamps, the behavior of this method is not defined.
*
* @see Files.setLastModifiedTime
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.setLastModifiedTime(value: FileTime): Path =
Files.setLastModifiedTime(this, value)
/**
* Returns the owner of a file.
*
* @throws UnsupportedOperationException if the associated file system does not support the [FileOwnerAttributeView].
*
* @see Files.getOwner
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.getOwner(vararg options: LinkOption): UserPrincipal? =
Files.getOwner(this, *options)
/**
* Sets the file owner to the specified [value].
*
* @throws UnsupportedOperationException if the associated file system does not support the [FileOwnerAttributeView].
*
* @see Files.setOwner
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.setOwner(value: UserPrincipal): Path =
Files.setOwner(this, value)
/**
* Returns the POSIX file permissions of the file located by this path.
*
* @throws UnsupportedOperationException if the associated file system does not support the [PosixFileAttributeView].
*
* @see Files.getPosixFilePermissions
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.getPosixFilePermissions(vararg options: LinkOption): Set<PosixFilePermission> =
Files.getPosixFilePermissions(this, *options)
/**
* Sets the POSIX file permissions for the file located by this path.
*
* @throws UnsupportedOperationException if the associated file system does not support the [PosixFileAttributeView].
*
* @see Files.setPosixFilePermissions
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.setPosixFilePermissions(value: Set<PosixFilePermission>): Path =
Files.setPosixFilePermissions(this, value)
/**
* Creates a new link (directory entry) located by this path for the existing file [target].
*
* Calling this function may require the process to be started with implementation specific privileges to create hard links
* or to create links to directories.
*
* @throws FileAlreadyExistsException if a file with this name already exists
* (optional specific exception, some implementations may throw a more general one).
* @throws UnsupportedOperationException if the implementation does not support creating a hard link.
*
* @see Files.createLink
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.createLinkPointingTo(target: Path): Path =
Files.createLink(this, target)
/**
* Creates a new symbolic link located by this path to the given [target].
*
* Calling this function may require the process to be started with implementation specific privileges to
* create symbolic links.
*
* @throws FileAlreadyExistsException if a file with this name already exists
* (optional specific exception, some implementations may throw a more general one).
* @throws UnsupportedOperationException if the implementation does not support symbolic links or the
* [attributes] array contains an attribute that cannot be set atomically when creating the symbolic link.
*
* @see Files.createSymbolicLink
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.createSymbolicLinkPointingTo(target: Path, vararg attributes: FileAttribute<*>): Path =
Files.createSymbolicLink(this, target, *attributes)
/**
* Reads the target of a symbolic link located by this path.
*
* @throws UnsupportedOperationException if symbolic links are not supported by this implementation.
* @throws NotLinkException if the target is not a symbolic link
* (optional specific exception, some implementations may throw a more general one).
*
* @see Files.readSymbolicLink
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.readSymbolicLink(): Path =
Files.readSymbolicLink(this)
/**
* Creates a new and empty file specified by this path, failing if the file already exists.
*
* @param attributes an optional list of file attributes to set atomically when creating the file.
*
* @throws FileAlreadyExistsException if a file specified by this path already exists
* (optional specific exception, some implementations may throw more general [IOException]).
* @throws UnsupportedOperationException if the [attributes] array contains an attribute that cannot be set atomically
* when creating the file.
*
* @see Files.createFile
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path.createFile(vararg attributes: FileAttribute<*>): Path =
Files.createFile(this, *attributes)
/**
* Creates an empty file in the default temp directory, using
* the given [prefix] and [suffix] to generate its name.
*
* @param attributes an optional list of file attributes to set atomically when creating the file.
* @return the path to the newly created file that did not exist before.
*
* @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically
* when creating the file.
*
* @see Files.createTempFile
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun createTempFile(prefix: String? = null, suffix: String? = null, vararg attributes: FileAttribute<*>): Path =
Files.createTempFile(prefix, suffix, *attributes)
/**
* Creates an empty file in the specified [directory], using
* the given [prefix] and [suffix] to generate its name.
*
* @param attributes an optional list of file attributes to set atomically when creating the file.
* @return the path to the newly created file that did not exist before.
*
* @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically
* when creating the file.
*
* @see Files.createTempFile
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun createTempFile(directory: Path, prefix: String? = null, suffix: String? = null, vararg attributes: FileAttribute<*>): Path =
Files.createTempFile(directory, prefix, suffix, *attributes)
/**
* Creates a new directory in the default temp directory, using the given [prefix] to generate its name.
*
* @param attributes an optional list of file attributes to set atomically when creating the directory.
* @return the path to the newly created directory that did not exist before.
*
* @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically
* when creating the directory.
*
* @see Files.createTempDirectory
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun createTempDirectory(prefix: String? = null, vararg attributes: FileAttribute<*>): Path =
Files.createTempDirectory(prefix, *attributes)
/**
* Creates a new directory in the specified [directory], using the given [prefix] to generate its name.
*
* @param attributes an optional list of file attributes to set atomically when creating the directory.
* @return the path to the newly created directory that did not exist before.
*
* @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically
* when creating the directory.
*
* @see Files.createTempDirectory
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun createTempDirectory(directory: Path, prefix: String? = null, vararg attributes: FileAttribute<*>): Path =
Files.createTempDirectory(directory, prefix, *attributes)
/**
* Resolves the given [other] path against this path.
*
* This operator is a shortcut for the [Path.resolve] function.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline operator fun Path.div(other: Path): Path =
this.resolve(other)
/**
* Resolves the given [other] path string against this path.
*
* This operator is a shortcut for the [Path.resolve] function.
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline operator fun Path.div(other: String): Path =
this.resolve(other)
/**
* Converts the provided [path] string to a [Path] object of the [default][FileSystems.getDefault] filesystem.
*
* @see Paths.get
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path(path: String): Path =
Paths.get(path)
/**
* Converts the name sequence specified with the [base] path string and a number of [subpaths] additional names
* to a [Path] object of the [default][FileSystems.getDefault] filesystem.
*
* @see Paths.get
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun Path(base: String, vararg subpaths: String): Path =
Paths.get(base, *subpaths)
/**
* Converts this URI to a [Path] object.
*
* @see Paths.get
*/
@SinceKotlin("1.4")
@ExperimentalPathApi
@kotlin.internal.InlineOnly
public inline fun URI.toPath(): Path =
Paths.get(this)
+282 -73
View File
@@ -7,38 +7,62 @@ package kotlin.jdk7.test
import java.io.IOException
import java.nio.file.*
import java.nio.file.attribute.*
import kotlin.io.path.*
import kotlin.random.Random
import kotlin.test.*
class PathExtensionsTest {
private val isCaseInsensitiveFileSystem = Paths.get("C:/") == Paths.get("c:/")
private val isCaseInsensitiveFileSystem = Path("C:/") == Path("c:/")
private val isBackslashSeparator = FileSystems.getDefault().separator == "\\"
@Test
fun extension() {
assertEquals("bbb", Paths.get("aaa.bbb").extension)
assertEquals("", Paths.get("aaa").extension)
assertEquals("", Paths.get("aaa.").extension)
assertEquals("bbb", Paths.get(".bbb").extension)
assertEquals("", Paths.get("/my.dir/log").extension)
assertEquals("", Paths.get("/").extension)
fun filenameComponents() {
fun check(path: String, name: String, nameNoExt: String, extension: String) {
val p = Path(path)
assertEquals(name, p.name, "name")
assertEquals(nameNoExt, p.nameWithoutExtension, "nameWithoutExtension")
assertEquals(extension, p.extension, "extension")
}
check(path = "aaa.bbb", name = "aaa.bbb", nameNoExt = "aaa", extension = "bbb")
check(path = "aaa", name = "aaa", nameNoExt = "aaa", extension = "")
check(path = "aaa.", name = "aaa.", nameNoExt = "aaa", extension = "")
check(path = ".aaa", name = ".aaa", nameNoExt = "", extension = "aaa")
check(path = "/dir.ext/aaa.bbb", name = "aaa.bbb", nameNoExt = "aaa", extension = "bbb")
check(path = "/dir.ext/aaa", name = "aaa", nameNoExt = "aaa", extension = "")
check(path = "/", name = "", nameNoExt = "", extension = "")
check(path = "", name = "", nameNoExt = "", extension = "")
}
@Test
fun nameWithoutExtension() {
assertEquals("aaa", Paths.get("aaa.bbb").nameWithoutExtension)
assertEquals("aaa", Paths.get("aaa").nameWithoutExtension)
assertEquals("aaa", Paths.get("aaa.").nameWithoutExtension)
assertEquals("", Paths.get(".bbb").nameWithoutExtension)
assertEquals("log", Paths.get("/my.dir/log").nameWithoutExtension)
assertEquals("", Paths.get("").nameWithoutExtension)
assertEquals("", Paths.get("/").nameWithoutExtension)
fun invariantSeparators() {
val path = Path("base") / "nested" / "leaf"
assertEquals("base/nested/leaf", path.invariantSeparatorsPath)
val path2 = Path("base", "nested", "leaf")
assertEquals("base/nested/leaf", path2.invariantSeparatorsPath)
}
@Test
fun testCopyTo() {
val srcFile = Files.createTempFile(null, null)
val dstFile = Files.createTempFile(null, null)
fun createNewFile() {
val dir = createTempDirectory()
val file = dir / "new-file"
assertTrue(file.notExists())
file.createFile()
assertTrue(file.exists())
assertTrue(file.isRegularFile())
assertFailsWith<FileAlreadyExistsException> { file.createFile() }
}
@Test
fun copyTo() {
val srcFile = createTempFile()
val dstFile = createTempFile()
try {
srcFile.writeText("Hello, World!")
assertFailsWith<FileAlreadyExistsException>("copy do not overwrite existing file") {
@@ -53,35 +77,34 @@ class PathExtensionsTest {
srcFile.copyTo(srcFile, overwrite = true)
compareFiles(dst, srcFile, "copying file to itself leaves it intact")
assertTrue(Files.deleteIfExists(dstFile))
assertTrue(dstFile.deleteIfExists())
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) }
assertFailsWith<FileSystemException> { srcFile.copyTo(subDst) }
assertFailsWith<FileSystemException> { srcFile.copyTo(subDst, overwrite = true) }
assertTrue(dstFile.deleteIfExists())
assertFailsWith<FileSystemException> { srcFile.copyTo(subDst) }
Files.createDirectory(dstFile)
val child = dstFile.resolve("child")
Files.createFile(child)
assertFailsWith<DirectoryNotEmptyException>( "copy with overwrite do not overwrite non-empty dir") {
dstFile.createDirectory()
val child = dstFile.resolve("child").createFile()
assertFailsWith<DirectoryNotEmptyException>("copy with overwrite do not overwrite non-empty dir") {
srcFile.copyTo(dstFile, overwrite = true)
}
Files.delete(child)
child.deleteExisting()
srcFile.copyTo(dstFile, overwrite = true)
assertEquals(srcFile.readText(), dstFile.readText(), "copy with overwrite over empty dir")
assertTrue(Files.deleteIfExists(srcFile))
assertTrue(Files.deleteIfExists(dstFile))
assertTrue(srcFile.deleteIfExists())
assertTrue(dstFile.deleteIfExists())
assertFailsWith<NoSuchFileException> {
srcFile.copyTo(dstFile)
}
Files.createDirectory(srcFile)
srcFile.createDirectory()
srcFile.resolve("somefile").writeText("some content")
dstFile.writeText("")
assertFailsWith<FileAlreadyExistsException>("copy dir do not overwrite file") {
@@ -111,35 +134,141 @@ class PathExtensionsTest {
@Test
fun copyToNameWithoutParent() {
val currentDir = Paths.get("").toAbsolutePath()
val srcFile = Files.createTempFile(null, null)
val dstFile = Files.createTempFile(currentDir, null, null)
val currentDir = Path("").toAbsolutePath()
val srcFile = createTempFile()
val dstFile = createTempFile(directory = currentDir)
try {
srcFile.writeText("Hello, World!", Charsets.UTF_8)
Files.delete(dstFile)
dstFile.deleteExisting()
val dstRelative = Paths.get(dstFile.fileName.toString())
val dstRelative = Path(dstFile.name)
srcFile.copyTo(dstRelative)
assertEquals(srcFile.readText(), dstFile.readText())
} finally {
Files.delete(dstFile)
Files.delete(srcFile)
dstFile.deleteExisting()
srcFile.deleteExisting()
}
}
@Test
fun moveTo() {
val original = createTempFile()
val srcFile = createTempFile()
val dstFile = createTempFile()
fun restoreSrcFile() { original.copyTo(srcFile, overwrite = true) }
try {
original.writeText("Hello, World!")
restoreSrcFile()
assertFailsWith<FileAlreadyExistsException>("do not overwrite existing file") {
srcFile.moveTo(dstFile)
}
var dst = srcFile.moveTo(dstFile, overwrite = true)
assertSame(dst, dstFile)
compareFiles(original, dst, "move with overwrite over existing file")
assertTrue(srcFile.notExists())
restoreSrcFile()
srcFile.moveTo(srcFile)
srcFile.moveTo(srcFile, overwrite = true)
compareFiles(original, srcFile, "move file to itself leaves it intact")
assertTrue(dstFile.deleteIfExists())
dst = srcFile.moveTo(dstFile)
compareFiles(original, dst, "move to new file")
restoreSrcFile()
val subDst = dstFile.resolve("foo/bar")
assertFailsWith<FileSystemException> { srcFile.moveTo(subDst) }
assertFailsWith<FileSystemException> { srcFile.moveTo(subDst, overwrite = true) }
assertTrue(dstFile.deleteIfExists())
assertFailsWith<FileSystemException> { srcFile.moveTo(subDst) }
dstFile.createDirectory()
val child = dstFile.resolve("child").createFile()
assertFailsWith<DirectoryNotEmptyException>("move with overwrite do not overwrite non-empty dir") {
srcFile.moveTo(dstFile, overwrite = true)
}
child.deleteExisting()
srcFile.moveTo(dstFile, overwrite = true)
compareFiles(original, dstFile, "move with overwrite over empty dir")
assertTrue(srcFile.notExists())
assertTrue(dstFile.deleteIfExists())
assertFailsWith<NoSuchFileException> {
srcFile.moveTo(dstFile)
}
srcFile.createDirectory()
srcFile.resolve("somefile").writeText("some content")
dstFile.writeText("")
assertFailsWith<FileAlreadyExistsException>("move dir do not overwrite file") {
srcFile.moveTo(dstFile)
}
srcFile.moveTo(dstFile, overwrite = true)
assertTrue(dstFile.isDirectory())
assertEquals(listOf(dstFile / "somefile"), dstFile.listDirectoryEntries(), "directory is moved with its content")
} finally {
srcFile.toFile().deleteRecursively()
dstFile.toFile().deleteRecursively()
}
}
private fun compareFiles(src: Path, dst: Path, message: String? = null) {
assertTrue(dst.exists())
assertEquals(src.isRegularFile(), dst.isRegularFile(), message)
assertEquals(src.isDirectory(), dst.isDirectory(), message)
if (dst.isRegularFile()) {
assertTrue(src.readBytes().contentEquals(dst.readBytes()), message)
}
}
@Test
fun testAttributeGettersOnFile() {
val file = Files.createTempFile(null, null)
fun fileSize() {
val file = createTempFile()
assertEquals(0, file.fileSize())
file.writeBytes(ByteArray(100))
assertEquals(100, file.fileSize())
file.appendText("Hello", Charsets.US_ASCII)
assertEquals(105, file.fileSize())
file.deleteExisting()
assertFailsWith<NoSuchFileException> { file.fileSize() }
}
@Test
fun deleteExisting() {
val file = createTempFile()
file.deleteExisting()
assertFailsWith<NoSuchFileException> { file.deleteExisting() }
val dir = createTempDirectory()
dir.deleteExisting()
assertFailsWith<NoSuchFileException> { dir.deleteExisting() }
}
@Test
fun deleteIfExists() {
val file = createTempFile()
assertTrue(file.deleteIfExists())
assertFalse(file.deleteIfExists())
val dir = createTempDirectory()
assertTrue(dir.deleteIfExists())
assertFalse(dir.deleteIfExists())
}
@Test
fun attributeGettersOnFile() {
val file = createTempFile("temp", ".file")
assertTrue(file.exists())
assertFalse(file.notExists())
assertTrue(file.isRegularFile())
@@ -147,17 +276,18 @@ class PathExtensionsTest {
assertFalse(file.isSymbolicLink())
assertTrue(file.isReadable())
assertTrue(file.isWritable())
assertTrue(file.isSameFile(file))
assertTrue(file.isSameFileAs(file))
// The default value of these depends on the current operating system, so just check that
// they don't throw an exception.
file.isExecutable()
file.isHidden()
file.deleteExisting()
}
@Test
fun testAttributeGettersOnDirectory() {
val file = Files.createTempDirectory(null)
fun attributeGettersOnDirectory() {
val file = createTempDirectory(".tmpdir")
assertTrue(file.exists())
assertFalse(file.notExists())
assertFalse(file.isRegularFile())
@@ -165,15 +295,16 @@ class PathExtensionsTest {
assertFalse(file.isSymbolicLink())
assertTrue(file.isReadable())
assertTrue(file.isWritable())
assertTrue(file.isSameFile(file))
assertTrue(file.isSameFileAs(file))
file.isExecutable()
file.isHidden()
file.deleteExisting()
}
@Test
fun testAttributeGettersOnNonExistentPath() {
val file = Files.createTempDirectory(null).resolve("foo")
fun attributeGettersOnNonExistentPath() {
val file = createTempDirectory().resolve("foo")
assertFalse(file.exists())
assertTrue(file.notExists())
assertFalse(file.isRegularFile())
@@ -181,7 +312,7 @@ class PathExtensionsTest {
assertFalse(file.isSymbolicLink())
assertFalse(file.isReadable())
assertFalse(file.isWritable())
assertTrue(file.isSameFile(file))
assertTrue(file.isSameFileAs(file))
file.isExecutable()
// This function will either throw an exception or return false,
@@ -190,15 +321,95 @@ class PathExtensionsTest {
assertFalse(file.isHidden())
} catch (e: IOException) {
}
file.parent.deleteExisting()
}
private interface SpecialFileAttributesView : FileAttributeView
private interface SpecialFileAttributes : BasicFileAttributes
@Test
fun readWriteAttributes() {
val file = createTempFile()
val modifiedTime = file.getLastModifiedTime()
assertEquals(modifiedTime, file.getAttribute("lastModifiedTime"))
assertEquals(modifiedTime, file.getAttribute("basic:lastModifiedTime"))
assertEquals(modifiedTime, file.readAttributes<BasicFileAttributes>().lastModifiedTime())
assertEquals(modifiedTime, file.readAttributes("basic:lastModifiedTime,creationTime")["lastModifiedTime"])
assertEquals(modifiedTime, file.readAttributes("*")["lastModifiedTime"])
assertFailsWith<UnsupportedOperationException> { file.readAttributes<SpecialFileAttributes>() }
assertFailsWith<UnsupportedOperationException> { file.readAttributes("really_unsupported_view:*") }
assertFailsWith<IllegalArgumentException> { file.readAttributes("basic:really_unknown_attribute") }
val newTime1 = FileTime.fromMillis(modifiedTime.toMillis() + 3600_000)
file.setLastModifiedTime(newTime1)
assertEquals(newTime1, file.getLastModifiedTime())
val newTime2 = FileTime.fromMillis(modifiedTime.toMillis() + 2 * 3600_000)
file.setAttribute("lastModifiedTime", newTime2)
assertEquals(newTime2, file.getLastModifiedTime())
val newTime3 = FileTime.fromMillis(modifiedTime.toMillis() + 3 * 3600_000)
file.fileAttributesView<BasicFileAttributeView>().setTimes(newTime3, null, null)
assertEquals(newTime3, file.getLastModifiedTime())
assertFailsWith<UnsupportedOperationException> { file.fileAttributesView<SpecialFileAttributesView>() }
assertNull(file.fileAttributesViewOrNull<SpecialFileAttributesView>())
file.setAttribute("lastModifiedTime", null)
assertEquals(newTime3, file.getLastModifiedTime())
file.deleteExisting()
}
@Test
fun testListDirectoryEntries() {
val dir = Files.createTempDirectory(null)
fun links() {
val dir = createTempDirectory()
val original = createTempFile(dir)
original.writeBytes(Random.nextBytes(100))
val link = try {
(dir / ("link-" + original.fileName)).createLinkPointingTo(original)
} catch (e: IOException) {
// may require a privilege
println("Creating a link failed with ${e.stackTraceToString()}")
return
}
assertTrue(link.isRegularFile())
assertTrue(link.isRegularFile(LinkOption.NOFOLLOW_LINKS))
assertTrue(original.isSameFileAs(link))
compareFiles(original, link)
assertFailsWith<NotLinkException> { link.readSymbolicLink() }
}
@Test
fun symlinks() {
val dir = createTempDirectory()
val original = createTempFile(dir)
original.writeBytes(Random.nextBytes(100))
val symlink = try {
(dir / ("symlink-" + original.fileName)).createSymbolicLinkPointingTo(original)
} catch (e: IOException) {
// may require a privilege
println("Creating a symlink failed with ${e.stackTraceToString()}")
return
}
assertTrue(symlink.isRegularFile())
assertFalse(symlink.isRegularFile(LinkOption.NOFOLLOW_LINKS))
assertTrue(original.isSameFileAs(symlink))
compareFiles(original, symlink)
assertEquals(original, symlink.readSymbolicLink())
}
@Test
fun directoryEntriesList() {
val dir = createTempDirectory()
assertEquals(0, dir.listDirectoryEntries().size)
val file = dir.resolve("f1")
Files.createFile(file)
val file = dir.resolve("f1").createFile()
assertEquals(listOf(file), dir.listDirectoryEntries())
val fileTxt = createTempFile(dir, suffix = ".txt")
@@ -208,12 +419,11 @@ class PathExtensionsTest {
}
@Test
fun testUseDirectoryEntries() {
val dir = Files.createTempDirectory(null)
fun directoryEntriesUseSequence() {
val dir = createTempDirectory()
assertEquals(0, dir.useDirectoryEntries { it.toList() }.size)
val file = dir.resolve("f1")
Files.createFile(file)
val file = dir.resolve("f1").createFile()
assertEquals(listOf(file), dir.useDirectoryEntries { it.toList() })
val fileTxt = createTempFile(dir, suffix = ".txt")
@@ -223,12 +433,11 @@ class PathExtensionsTest {
}
@Test
fun testForEachDirectoryEntry() {
val dir = Files.createTempDirectory(null)
fun directoryEntriesForEach() {
val dir = createTempDirectory()
dir.forEachDirectoryEntry { error("shouldn't get here, but received $it") }
val file = dir.resolve("f1")
Files.createFile(file)
val file = createTempFile(dir)
dir.forEachDirectoryEntry { assertEquals(file, it) }
val fileTxt = createTempFile(dir, suffix = ".txt")
@@ -239,9 +448,9 @@ class PathExtensionsTest {
private fun testRelativeTo(expected: String?, path: String, base: String) =
testRelativeTo(expected?.let { Paths.get(it) }, Paths.get(path), Paths.get(base))
testRelativeTo(expected?.let { Path(it) }, Path(path), Path(base))
private fun testRelativeTo(expected: String, path: Path, base: Path) =
testRelativeTo(Paths.get(expected), path, base)
testRelativeTo(Path(expected), path, base)
private fun testRelativeTo(expected: Path?, path: Path, base: Path) {
val context = "path: '$path', base: '$base'"
@@ -304,23 +513,23 @@ class PathExtensionsTest {
@Test
fun relativeToRelative() {
val nested = Paths.get("foo/bar")
val base = Paths.get("foo")
val nested = Path("foo/bar")
val base = Path("foo")
testRelativeTo("bar", nested, base)
testRelativeTo("..", base, nested)
val empty = Paths.get("")
val current = Paths.get(".")
val parent = Paths.get("..")
val outOfRoot = Paths.get("../bar")
val empty = Path("")
val current = Path(".")
val parent = Path("..")
val outOfRoot = Path("../bar")
testRelativeTo("../bar", outOfRoot, empty)
testRelativeTo("../../bar", outOfRoot, base)
testRelativeTo("bar", outOfRoot, parent)
testRelativeTo("..", parent, outOfRoot)
val root = Paths.get("/root")
val root = Path("/root")
val files = listOf(nested, base, empty, outOfRoot, current, parent)
val bases = listOf(nested, base, empty, current)
@@ -340,10 +549,10 @@ class PathExtensionsTest {
@Test
fun relativeToFails() {
val absolute = Paths.get("/foo/bar/baz")
val relative = Paths.get("foo/bar")
val networkShare1 = Paths.get("""\\my.host\share1/folder""")
val networkShare2 = Paths.get("""\\my.host\share2\folder""")
val absolute = Path("/foo/bar/baz")
val relative = Path("foo/bar")
val networkShare1 = Path("""\\my.host\share1/folder""")
val networkShare2 = Path("""\\my.host\share2\folder""")
val allFiles = listOf(absolute, relative) + if (isBackslashSeparator) listOf(networkShare1, networkShare2) else emptyList()
for (file in allFiles) {
+38 -14
View File
@@ -8,24 +8,25 @@ package kotlin.jdk7.test
import java.nio.file.Files
import java.nio.file.StandardOpenOption
import kotlin.io.path.*
import kotlin.random.Random
import kotlin.test.*
class PathReadWriteTest {
@Test
fun testAppendText() {
val file = Files.createTempFile(null, null)
fun appendText() {
val file = createTempFile()
file.writeText("Hello\n")
file.appendText("World\n")
file.writeText("Again", Charsets.US_ASCII, StandardOpenOption.APPEND)
assertEquals("Hello\nWorld\nAgain", file.readText())
assertEquals(listOf("Hello", "World", "Again"), file.readLines(Charsets.UTF_8))
file.toFile().deleteOnExit()
file.deleteExisting()
}
@Test
fun file() {
val file = Files.createTempFile(null, null)
val file = createTempFile()
val writer = file.outputStream().writer().buffered()
writer.write("Hello")
@@ -72,31 +73,54 @@ class PathReadWriteTest {
}
@Test
fun testBufferedReader() {
val file = Files.createTempFile(null, null)
fun bufferedReader() {
val file = createTempFile()
val lines = listOf("line1", "line2")
Files.write(file, lines, Charsets.UTF_8)
file.writeLines(lines)
assertEquals(file.bufferedReader().use { it.readLines() }, lines)
assertEquals(file.bufferedReader(Charsets.UTF_8, 1024, StandardOpenOption.READ).use { it.readLines() }, lines)
assertEquals(lines, file.bufferedReader().use { it.readLines() })
assertEquals(lines, file.bufferedReader(Charsets.UTF_8, 1024, StandardOpenOption.READ).use { it.readLines() })
}
@Test
fun testBufferedWriter() {
val file = Files.createTempFile(null, null)
fun bufferedWriter() {
val file = createTempFile()
file.bufferedWriter().use { it.write("line1\n") }
file.bufferedWriter(Charsets.UTF_8, 1024, StandardOpenOption.APPEND).use { it.write("line2\n") }
assertEquals(Files.readAllLines(file, Charsets.UTF_8), listOf("line1", "line2"))
assertEquals(listOf("line1", "line2"), file.readLines())
}
@Test
fun testWriteBytes() {
val file = Files.createTempFile(null, null)
fun writeBytes() {
val file = createTempFile()
file.writeBytes("Hello".encodeToByteArray())
file.appendBytes(" world!".encodeToByteArray())
assertEquals(file.readText(), "Hello world!")
val bytes = Random.nextBytes(100)
file.writeBytes(bytes)
file.appendBytes(bytes)
assertTrue((bytes + bytes) contentEquals file.readBytes())
}
@Test
fun writeLines() {
val file = createTempFile()
val lines = listOf("first line", "second line")
file.writeLines(lines)
assertEquals(lines, file.readLines())
file.writeLines(lines.asSequence())
assertEquals(lines, file.readLines())
val moreLines = listOf("third line", "the bottom line")
file.appendLines(moreLines)
assertEquals(lines + moreLines, file.readLines())
file.appendLines(moreLines.asSequence())
assertEquals(lines + moreLines + moreLines, file.readLines())
}
}
@@ -1,3 +1,23 @@
public abstract interface annotation class kotlin/io/path/ExperimentalPathApi : java/lang/annotation/Annotation {
}
public final class kotlin/io/path/PathsKt {
public static final fun appendText (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;)V
public static synthetic fun appendText$default (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;ILjava/lang/Object;)V
public static final fun fileAttributeViewNotAvailable (Ljava/nio/file/Path;Ljava/lang/Class;)Ljava/lang/Void;
public static final fun getExtension (Ljava/nio/file/Path;)Ljava/lang/String;
public static final fun getInvariantSeparatorsPath (Ljava/nio/file/Path;)Ljava/lang/String;
public static final fun getName (Ljava/nio/file/Path;)Ljava/lang/String;
public static final fun getNameWithoutExtension (Ljava/nio/file/Path;)Ljava/lang/String;
public static final fun listDirectoryEntries (Ljava/nio/file/Path;Ljava/lang/String;)Ljava/util/List;
public static synthetic fun listDirectoryEntries$default (Ljava/nio/file/Path;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/List;
public static final fun relativeTo (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
public static final fun relativeToOrNull (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
public static final fun relativeToOrSelf (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
public static final fun writeText (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;)V
public static synthetic fun writeText$default (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;ILjava/lang/Object;)V
}
public final class kotlin/jdk7/AutoCloseableKt {
public static final fun closeFinally (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V
}