Add java.nio.Path extensions to stdlib-jdk7: part 2

- Add notExists
- Rename isFile to isRegularFile
- Remove forEachBlock
- Rename listFiles
- Add relativeTo extensions
- Remove extra overloads
- Update doc comments
- Address review comments

#KT-19192
This commit is contained in:
AJ
2020-09-28 14:58:37 -07:00
committed by Ilya Gorbunov
parent 03cc0bf6aa
commit b3a87356bd
4 changed files with 378 additions and 379 deletions
@@ -17,43 +17,13 @@ import java.nio.file.OpenOption
import java.nio.file.Path
import java.nio.file.StandardOpenOption
/**
* The default block size for forEachBlock().
*/
private const val DEFAULT_BLOCK_SIZE: Int = 4096
/**
* The minimum block size for forEachBlock().
*/
private const val MINIMUM_BLOCK_SIZE: Int = 512
/**
* Returns a new [InputStreamReader] for reading the content of this file.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.reader(charset: Charset = Charsets.UTF_8): InputStreamReader {
return inputStream().reader(charset)
}
/**
* Returns a new [InputStreamReader] for reading the content of this file.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.reader(vararg options: OpenOption): InputStreamReader {
return inputStream(*options).reader()
}
/**
* Returns a new [InputStreamReader] for reading the content of this file.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.reader(charset: Charset, vararg options: OpenOption): InputStreamReader {
public inline fun Path.reader(charset: Charset = Charsets.UTF_8, vararg options: OpenOption): InputStreamReader {
return inputStream(*options).reader(charset)
}
@@ -62,63 +32,16 @@ public inline fun Path.reader(charset: Charset, vararg options: OpenOption): Inp
*
* @param charset character set to use.
* @param bufferSize necessary size of the buffer.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedReader(charset: Charset = Charsets.UTF_8, bufferSize: Int = DEFAULT_BUFFER_SIZE): BufferedReader {
return reader(charset).buffered(bufferSize)
}
/**
* Returns a new [BufferedReader] for reading the content of this file.
*
* @param options options to determine how the file is opened
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedReader(vararg options: OpenOption): BufferedReader {
return reader(*options).buffered()
}
/**
* Returns a new [BufferedReader] for reading the content of this file.
*
* @param bufferSize necessary size of the buffer.
* @param options options to determine how the file is opened
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedReader(bufferSize: Int, vararg options: OpenOption): BufferedReader {
return reader(*options).buffered(bufferSize)
}
/**
* Returns a new [BufferedReader] for reading the content of this file.
*
* @param charset character set to use.
* @param options options to determine how the file is opened
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedReader(charset: Charset, vararg options: OpenOption): BufferedReader {
return reader(charset, *options).buffered()
}
/**
* Returns a new [BufferedReader] for reading the content of this file.
*
* @param charset character set to use.
* @param bufferSize necessary size of the buffer.
* @param options options to determine how the file is opened
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedReader(charset: Charset, bufferSize: Int, vararg options: OpenOption): BufferedReader {
public inline fun Path.bufferedReader(
charset: Charset = Charsets.UTF_8,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
vararg options: OpenOption
): BufferedReader {
return reader(charset, *options).buffered(bufferSize)
}
@@ -128,27 +51,7 @@ public inline fun Path.bufferedReader(charset: Charset, bufferSize: Int, vararg
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.writer(charset: Charset = Charsets.UTF_8): OutputStreamWriter {
return outputStream().writer(charset)
}
/**
* Returns a new [OutputStreamWriter] for writing the content of this file.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.writer(vararg options: OpenOption): OutputStreamWriter {
return outputStream(*options).writer()
}
/**
* Returns a new [OutputStreamWriter] for writing the content of this file.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.writer(charset: Charset, vararg options: OpenOption): OutputStreamWriter {
public inline fun Path.writer(charset: Charset = Charsets.UTF_8, vararg options: OpenOption): OutputStreamWriter {
return outputStream(*options).writer(charset)
}
@@ -157,63 +60,16 @@ public inline fun Path.writer(charset: Charset, vararg options: OpenOption): Out
*
* @param charset character set to use.
* @param bufferSize necessary size of the buffer.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedWriter(charset: Charset = Charsets.UTF_8, bufferSize: Int = DEFAULT_BUFFER_SIZE): BufferedWriter {
return writer(charset).buffered(bufferSize)
}
/**
* Returns a new [BufferedWriter] for writing the content of this file.
*
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedWriter(vararg options: OpenOption): BufferedWriter {
return writer(*options).buffered()
}
/**
* Returns a new [BufferedWriter] for writing the content of this file.
*
* @param charset character set to use.
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedWriter(charset: Charset, vararg options: OpenOption): BufferedWriter {
return writer(charset, *options).buffered()
}
/**
* Returns a new [BufferedWriter] for writing the content of this file.
*
* @param bufferSize necessary size of the buffer.
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedWriter(bufferSize: Int, vararg options: OpenOption): BufferedWriter {
return writer(*options).buffered(bufferSize)
}
/**
* Returns a new [BufferedWriter] for writing the content of this file.
*
* @param charset character set to use.
* @param bufferSize necessary size of the buffer.
* @param options options to determine how the file is opened.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.bufferedWriter(charset: Charset, bufferSize: Int, vararg options: OpenOption): BufferedWriter {
public inline fun Path.bufferedWriter(
charset: Charset = Charsets.UTF_8,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
vararg options: OpenOption
): BufferedWriter {
return writer(charset, *options).buffered(bufferSize)
}
@@ -223,28 +79,8 @@ public inline fun Path.bufferedWriter(charset: Charset, bufferSize: Int, vararg
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.printWriter(charset: Charset = Charsets.UTF_8): PrintWriter {
return PrintWriter(bufferedWriter(charset))
}
/**
* Returns a new [PrintWriter] for writing the content of this file.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.printWriter(vararg options: OpenOption): PrintWriter {
return PrintWriter(bufferedWriter(*options))
}
/**
* Returns a new [PrintWriter] for writing the content of this file.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.printWriter(charset: Charset, vararg options: OpenOption): PrintWriter {
return PrintWriter(bufferedWriter(charset, *options))
public inline fun Path.printWriter(charset: Charset = Charsets.UTF_8, vararg options: OpenOption): PrintWriter {
return PrintWriter(bufferedWriter(charset, options = options))
}
/**
@@ -301,20 +137,6 @@ public inline fun Path.appendBytes(array: ByteArray): Unit {
@ExperimentalStdlibApi
public 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].
*
* 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.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.writeText(text: String, vararg options: OpenOption): Unit {
writeBytes(text.toByteArray(), *options)
}
/**
* Sets the content of this file as [text] encoded using UTF-8 or specified [charset].
*
@@ -326,25 +148,10 @@ public fun Path.writeText(text: String, vararg options: OpenOption): Unit {
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.writeText(text: String, charset: Charset, vararg options: OpenOption): Unit {
public fun Path.writeText(text: String, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit {
writeBytes(text.toByteArray(charset), *options)
}
/**
* Sets the content of this file as [text] encoded using UTF-8 or specified [charset].
* If this file exists, it becomes overwritten.
*
* @param text text to write into file.
* @param charset character set to use.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.writeText(text: String, charset: Charset = Charsets.UTF_8): Unit {
writeBytes(text.toByteArray(charset))
}
/**
* Appends [text] to the content of this file using UTF-8 or the specified [charset].
*
@@ -357,45 +164,6 @@ public fun Path.appendText(text: String, charset: Charset = Charsets.UTF_8): Uni
writeText(text, charset, StandardOpenOption.APPEND)
}
/**
* Reads file by byte blocks and calls [action] for each block read.
* Block has default size which is implementation-dependent.
* This function passes the byte array and amount of bytes in the array to the [action] function.
*
* You can use this function for huge files.
*
* @param action function to process file blocks.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.forEachBlock(action: (buffer: ByteArray, bytesRead: Int) -> Unit): Unit = forEachBlock(DEFAULT_BLOCK_SIZE, action)
/**
* Reads file by byte blocks and calls [action] for each block read.
* This functions passes the byte array and amount of bytes in the array to the [action] function.
*
* You can use this function for huge files.
*
* @param action function to process file blocks.
* @param blockSize size of a block, replaced by 512 if it's less, 4096 by default.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.forEachBlock(blockSize: Int, action: (buffer: ByteArray, bytesRead: Int) -> Unit): Unit {
val arr = ByteArray(blockSize.coerceAtLeast(MINIMUM_BLOCK_SIZE))
inputStream().use { input ->
do {
val size = input.read(arr)
if (size <= 0) {
break
} else {
action(arr, size)
}
} while (true)
}
}
/**
* Reads this file line by line using the specified [charset] and calls [action] for each line.
* Default charset is UTF-8.
@@ -408,29 +176,13 @@ public fun Path.forEachBlock(blockSize: Int, action: (buffer: ByteArray, bytesRe
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.forEachLine(vararg options: OpenOption, charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit): Unit {
public fun Path.forEachLine(charset: Charset = Charsets.UTF_8, vararg options: OpenOption, action: (line: String) -> Unit): Unit {
// Note: close is called at forEachLine
bufferedReader(charset, *options).forEachLine(action)
bufferedReader(charset, options = options).forEachLine(action)
}
/**
* Reads this file line by line using the specified [charset] and calls [action] for each line.
* Default charset is UTF-8.
*
* You may use this function on huge files.
*
* @param charset character set to use.
* @param action function to process file lines.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.forEachLine(charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit): Unit {
// Note: close is called at forEachLine
bufferedReader(charset = charset).forEachLine(action)
}
/**
* Constructs a new InputStream of this path and returns it as a result.
* Constructs a new InputStream of this file and returns it as a result.
*
* The [options] parameter determines how the file is opened. If no options are present then it is
* equivalent to opening the file with the [READ][StandardOpenOption.READ] option.
@@ -448,7 +200,7 @@ public inline fun Path.inputStream(vararg options: OpenOption): InputStream {
* The [options] parameter determines how the file is opened. If no options are present then it is
* equivalent to opening the file with the [CREATE][StandardOpenOption.CREATE],
* [TRUNCATE_EXISTING][StandardOpenOption.TRUNCATE_EXISTING], and [WRITE][StandardOpenOption.WRITE]
* option.
* options.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@@ -10,23 +10,21 @@
package kotlin.io
import java.io.File
import java.io.IOException
import java.nio.channels.FileChannel
import java.nio.file.*
import java.nio.file.FileAlreadyExistsException
import java.nio.file.NoSuchFileException
/**
* Returns the extension of this file (not including the dot), or an empty string if it doesn't have one.
* Returns the extension of this path (not including the dot), or an empty string if it doesn't have one.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public val Path.extension: String
get() = fileName.toString().substringAfterLast('.', "")
get() = fileName?.toString()?.substringAfterLast('.', "") ?: ""
/**
* Returns [path][File.path] of this File using the invariant separator '/' to
* Returns this path as a [String] using the invariant separator '/' to
* separate the names in the name sequence.
*/
@SinceKotlin("1.4")
@@ -38,13 +36,55 @@ public val Path.invariantSeparatorsPath: String
}
/**
* Returns file's name without an extension.
* Returns this path's [fileName][Path.getFileName] without an extension, or an empty string if
* this path has zero elements.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public val Path.nameWithoutExtension: String
get() = fileName.toString().substringBeforeLast(".")
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.
*
* @throws IllegalArgumentException if this and base paths have different roots.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.relativeTo(base: Path): Path = base.relativize(this)
/**
* 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.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.relativeToOrSelf(base: Path): Path =
relativeToOrNull(base) ?: this
/**
* 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.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.relativeToOrNull(base: Path): Path? {
return try {
base.relativize(this)
} catch (e: IllegalArgumentException) {
null
}
}
/**
* Copies this path to the given [target] path.
@@ -54,15 +94,15 @@ public val Path.nameWithoutExtension: String
*
* When [overwrite] is `true` and [target] is a directory, it is replaced only if it is empty.
*
* If this file is a directory, it is copied without its content, i.e. an empty [target] directory is created.
* 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].
*
* The operation doesn't preserve copied file attributes such as creation/modification date, permissions, etc.
*
* @param overwrite `true` if destination overwrite is allowed.
* @return the [target] file.
* @throws NoSuchFileException if the source file doesn't exist.
* @throws FileAlreadyExistsException if the destination file already exists and [overwrite] argument is set to `false`.
* @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 IOException if any errors occur while copying.
*/
@SinceKotlin("1.4")
@@ -82,32 +122,32 @@ public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path {
* When [REPLACE_EXISTING][StandardCopyOption.REPLACE_EXISTING] is used and [target] is a directory,
* it is replaced only if it is empty.
*
* If this file is a directory, it is copied without its content, i.e. an empty [target] directory is created.
* 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].
*
* The operation doesn't preserve copied file attributes such as creation/modification date,
* permissions, etc. unless [COPY_ATTRIBUTES][StandardCopyOption.COPY_ATTRIBUTES] is used.
*
* @param options options to control how the path is copied.
* @return the [target] file.
* @throws NoSuchFileException if the source file doesn't exist.
* @throws FileAlreadyExistsException if the destination file already exists and [REPLACE_EXISTING][StandardCopyOption.REPLACE_EXISTING] is not used.
* @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 IOException if any errors occur while copying.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.copyTo(target: Path, vararg options: CopyOption): Path {
if (!this.exists()) {
throw NoSuchFileException(toString(), null, "The source file doesn't exist.")
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 file already exists.")
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 file already exists.")
throw FileAlreadyExistsException(toString(), null, "The destination path already exists.")
}
try {
Files.createDirectories(target)
@@ -125,7 +165,7 @@ public fun Path.copyTo(target: Path, vararg options: CopyOption): Path {
}
/**
* Check if this file exists.
* Check if this path exists.
*
* @param options Options to control how symbolic links are handled.
*/
@@ -134,6 +174,16 @@ public fun Path.copyTo(target: Path, vararg options: CopyOption): Path {
@kotlin.internal.InlineOnly
public inline fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options)
/**
* Check if this path does not exist.
*
* @param options Options to control how symbolic links are handled.
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.notExists(vararg options: LinkOption): Boolean = Files.notExists(this, *options)
/**
* Check if this path is a file.
*
@@ -142,7 +192,7 @@ public inline fun Path.exists(vararg options: LinkOption): Boolean = Files.exist
@SinceKotlin("1.4")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun Path.isFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options)
public inline fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options)
/**
* Check if this path is a directory.
@@ -208,13 +258,38 @@ public inline fun Path.isWritable(): Boolean = Files.isWritable(this)
public inline fun Path.isSameFile(other: Path): Boolean = Files.isSameFile(this, other)
/**
* Return a list of the files and directories in this directory.
* Return a list of the entries in this directory.
*
* @throws NotDirectoryException If this path does not refer to a directory
* @throws IOException If an I/O error occurs
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.listFiles(): List<Path> {
public fun Path.listDirectoryEntries(): List<Path> {
return Files.newDirectoryStream(this).use { it.toList() }
}
/**
* Call the [block] callback with a sequence of all entries in this directory.
*
* @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]
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun <T> Path.useDirectoryEntries(block: (Sequence<Path>) -> T): T {
return Files.newDirectoryStream(this).use { block(it.asSequence()) }
}
/**
* Perform the given [action] on each entry in this directory.
*
* @throws NotDirectoryException If this path does not refer to a directory
* @throws IOException If an I/O error occurs
*/
@SinceKotlin("1.4")
@ExperimentalStdlibApi
public fun Path.forEachDirectoryEntry(action: (Path) -> Unit) {
return Files.newDirectoryStream(this).use { it.forEach(action) }
}