Add java.nio.Path extensions to stdlib-jdk7
This PR adds most extensions on `java.io.File` in `kotlin.io` to `java.nio.Path`. This includes extensions from `FileReadWrite.kt`, `Utils.kt`, and `FileTreeWalk.kt`. I attempted to keep the implementations, documentation, and tests as similar as possible to the existing implementations. I am happy to add, remove, or move to separate PRs any of the functions of this PR. ### `File` extensions that were not added to `Path` ##### `createTempDir`, `createTempFile` These functions have no `File` parameters, so can't be overloaded. Equivalents exist as `Files.createTempFile()` and `Files.createTempDirectory()`. ##### `startsWith`, `endsWith`, `normalize`, `resolve`, `resolveSibling` These exist as member functions on `Path` ##### `relativeTo`, `relativeToOrNull`, `relativeToOrSelf`, `toRelativeString`, `toRelativeStringOrNull` This functionality exists as the `Path.relativize` member function, which is equivalent to `relativeTo`, but with the receiver and parameter flipped. `foo.relativeTo(bar)` is equivalent to `bar.relativize(foo)`. We could potentially add a `relativizeOrNull` extension to make that pattern simpler. ##### `isRooted` `Path` has a `root` method, so `isRooted` is equivalent to `root != null` ### New extensions All of the simple boolean attribute checks from `java.nio.Files` were added as extensions on `Path`. These extensions are used commonly enough that it seems worth supporting them. This functionality for `File` is implemented as member methods. The following `Path` extensions were added: - `exists` - `isDirectory` - `isExecutable` - `isFile` - `isHidden` - `isReadable` - `isSameFile` - `isSymbolicLink` - `isWritable` Some of these extensions take options that are forwarded to their `Files` method, so all of the extensions were implemented as functions rather than properties for consistency. Additionally, `Path.listFiles` was added to match the `File.listFiles` method. One motivation for its addition was that it's used several times in the implementation of other file extensions. The way to list directory contents with `java.nio` is via `Files.newDirectoryStream()`, which returns an iterable object that must be closed to avoid leaking resources. It's difficult to use correctly with functions like `map` and `filter`, so this extension was added as a simpler, less error-prone alternative. ### Other changes I added overloads of several of the read-write that take `OpenOptions` to expose the greater control that `java.nio` introduces. For example, you can use `printWriter(APPEND)` to create a `PrintWriter` that doesn't delete the contents of an existing file. All the new extensions throw exceptions (such as `NoSuchFileException`) from `java.nio` rather than the copies from `kotlin.io`. The `kotlin.io` copies take `File` objects as parameters, and so aren't compatible with `Path`s. ### Address review comments - Move varargs parameters to the last position - Remove PathTreeWalk #KT-19192
This commit is contained in:
@@ -3,6 +3,7 @@ module kotlin.stdlib.jdk7 {
|
||||
requires transitive kotlin.stdlib;
|
||||
|
||||
exports kotlin.jdk7;
|
||||
exports kotlin.io.jdk7;
|
||||
|
||||
exports kotlin.internal.jdk7 to kotlin.stdlib.jdk8;
|
||||
opens kotlin.internal.jdk7 to kotlin.stdlib;
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "RedundantVisibilityModifier", "RedundantUnitReturnType", "SameParameterValue")
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("PathsKt")
|
||||
@file:kotlin.jvm.JvmPackageName("kotlin.io.jdk7")
|
||||
|
||||
package kotlin.io
|
||||
|
||||
import java.io.*
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.Files
|
||||
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 {
|
||||
return inputStream(*options).reader(charset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [BufferedReader] for reading the content of this file.
|
||||
*
|
||||
* @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 {
|
||||
return reader(charset, *options).buffered(bufferSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = 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 {
|
||||
return outputStream(*options).writer(charset)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new [BufferedWriter] for writing the content of this file.
|
||||
*
|
||||
* @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 {
|
||||
return writer(charset, *options).buffered(bufferSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entire content of this file as a byte array.
|
||||
*
|
||||
* This method is not recommended on huge files. It has an internal limitation of 2 GB byte array size.
|
||||
*
|
||||
* @return the entire content of this file as a byte array.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.readBytes(): ByteArray {
|
||||
return Files.readAllBytes(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write 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].
|
||||
*
|
||||
* @param array byte array to write into this file.
|
||||
* @param options options to determine how the file is opened.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.writeBytes(array: ByteArray, vararg options: OpenOption): Unit {
|
||||
Files.write(this, array, *options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an [array] of bytes to the content of this file.
|
||||
*
|
||||
* @param array byte array to append to this file.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.appendBytes(array: ByteArray): Unit {
|
||||
writeBytes(array, StandardOpenOption.APPEND)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entire content of this file as a String using UTF-8 or 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.
|
||||
* @return the entire content of this file as a String.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@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].
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public fun Path.writeText(text: String, charset: Charset, 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].
|
||||
*
|
||||
* @param text text to append to file.
|
||||
* @param charset character set to use.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public fun Path.appendText(text: String, charset: Charset = Charsets.UTF_8): Unit {
|
||||
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.
|
||||
*
|
||||
* 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 action function to process file lines.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public fun Path.forEachLine(vararg options: OpenOption, charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit): Unit {
|
||||
// Note: close is called at forEachLine
|
||||
bufferedReader(charset, *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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.inputStream(vararg options: OpenOption): InputStream {
|
||||
return Files.newInputStream(this, *options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new OutputStream 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 [CREATE][StandardOpenOption.CREATE],
|
||||
* [TRUNCATE_EXISTING][StandardOpenOption.TRUNCATE_EXISTING], and [WRITE][StandardOpenOption.WRITE]
|
||||
* option.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.outputStream(vararg options: OpenOption): OutputStream {
|
||||
return Files.newOutputStream(this, *options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the file content as a list of lines.
|
||||
*
|
||||
* Do not use this function for huge files.
|
||||
*
|
||||
* @param charset character set to use. By default uses UTF-8 charset.
|
||||
* @return list of file lines.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.readLines(charset: Charset = Charsets.UTF_8): List<String> {
|
||||
return Files.readAllLines(this, charset)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return the value returned by [block].
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun <T> Path.useLines(charset: Charset = Charsets.UTF_8, block: (Sequence<String>) -> T): T {
|
||||
return bufferedReader(charset).use { block(it.lineSequence()) }
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "RedundantVisibilityModifier")
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("PathsKt")
|
||||
@file:kotlin.jvm.JvmPackageName("kotlin.io.jdk7")
|
||||
|
||||
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.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public val Path.extension: String
|
||||
get() = fileName.toString().substringAfterLast('.', "")
|
||||
|
||||
/**
|
||||
* Returns [path][File.path] of this File using the invariant separator '/' to
|
||||
* separate the names in the name sequence.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public val Path.invariantSeparatorsPath: String
|
||||
get() {
|
||||
val separator = fileSystem.separator
|
||||
return if (separator != "/") toString().replace(separator, "/") else toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns file's name without an extension.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public val Path.nameWithoutExtension: String
|
||||
get() = fileName.toString().substringBeforeLast(".")
|
||||
|
||||
|
||||
/**
|
||||
* Copies this path to the given [target] path.
|
||||
*
|
||||
* If some directories on a way to the [target] are missing, then they will be created.
|
||||
* 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.
|
||||
*
|
||||
* If this file 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`.
|
||||
* @throws IOException if any errors occur while copying.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path {
|
||||
val options = if (overwrite) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
|
||||
return copyTo(target, *options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this path to the given [target] path.
|
||||
*
|
||||
* If some directories on a way to the [target] are missing, then they will be created.
|
||||
* 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 file 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.
|
||||
* @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 (target.exists() && StandardCopyOption.REPLACE_EXISTING !in options) {
|
||||
throw FileAlreadyExistsException(toString(), null, "The destination file already exists.")
|
||||
}
|
||||
|
||||
if (this.isDirectory()) {
|
||||
if (target.isDirectory() && Files.newDirectoryStream(target).use { it.firstOrNull() } != null) {
|
||||
throw FileAlreadyExistsException(toString(), null, "The destination file 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this file exists.
|
||||
*
|
||||
* @param options Options to control how symbolic links are handled.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options)
|
||||
|
||||
/**
|
||||
* Check if this path is a file.
|
||||
*
|
||||
* @param options Options to control how symbolic links are handled.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options)
|
||||
|
||||
/**
|
||||
* Check if this path is a directory.
|
||||
*
|
||||
* By default, symbolic links in the path are followed.
|
||||
*
|
||||
* @param options Options to control how symbolic links are handled.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options)
|
||||
|
||||
/**
|
||||
* Check if this path exists and is a symbolic link.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isSymbolicLink(): Boolean = Files.isSymbolicLink(this)
|
||||
|
||||
/**
|
||||
* Check if this path exists and is executable.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isExecutable(): Boolean = Files.isExecutable(this)
|
||||
|
||||
/**
|
||||
* Check if 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.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isHidden(): Boolean = Files.isHidden(this)
|
||||
|
||||
/**
|
||||
* Check if this path exists and is readable.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isReadable(): Boolean = Files.isReadable(this)
|
||||
|
||||
/**
|
||||
* Check that this path exists and is writable.
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isWritable(): Boolean = Files.isWritable(this)
|
||||
|
||||
/**
|
||||
* Check if this path points to the same file or directory as [other].
|
||||
*/
|
||||
@SinceKotlin("1.4")
|
||||
@ExperimentalStdlibApi
|
||||
@kotlin.internal.InlineOnly
|
||||
public inline fun Path.isSameFile(other: Path): Boolean = Files.isSameFile(this, other)
|
||||
|
||||
/**
|
||||
* Return a list of the files and directories 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> {
|
||||
return Files.newDirectoryStream(this).use { it.toList() }
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.jdk7.test
|
||||
|
||||
import java.nio.file.*
|
||||
import kotlin.test.*
|
||||
|
||||
class PathExtensionsTest {
|
||||
|
||||
@Test
|
||||
fun extension() {
|
||||
assertEquals("bbb", Paths.get("aaa.bbb").extension)
|
||||
assertEquals("", Paths.get("aaa").extension)
|
||||
assertEquals("", Paths.get("aaa.").extension)
|
||||
// maybe we should think that such files have name .bbb and no extension
|
||||
assertEquals("bbb", Paths.get(".bbb").extension)
|
||||
assertEquals("", Paths.get("/my.dir/log").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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCopyTo() {
|
||||
val srcFile = Files.createTempFile(null, null)
|
||||
val dstFile = Files.createTempFile(null, null)
|
||||
try {
|
||||
srcFile.writeText("Hello, World!")
|
||||
assertFailsWith(FileAlreadyExistsException::class, "copy do not overwrite existing file") {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
|
||||
var dst = srcFile.copyTo(dstFile, overwrite = true)
|
||||
assertSame(dst, dstFile)
|
||||
compareFiles(srcFile, dst, "copy with overwrite over existing file")
|
||||
|
||||
assertTrue(Files.deleteIfExists(dstFile))
|
||||
dst = srcFile.copyTo(dstFile)
|
||||
compareFiles(srcFile, dst, "copy to new file")
|
||||
|
||||
assertTrue(Files.deleteIfExists(dstFile))
|
||||
Files.createDirectory(dstFile)
|
||||
val child = dstFile.resolve("child")
|
||||
Files.createFile(child)
|
||||
assertFailsWith(DirectoryNotEmptyException::class, "copy with overwrite do not overwrite non-empty dir") {
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
}
|
||||
Files.delete(child)
|
||||
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
assertEquals(srcFile.readText(), dstFile.readText(), "copy with overwrite over empty dir")
|
||||
|
||||
assertTrue(Files.deleteIfExists(srcFile))
|
||||
assertTrue(Files.deleteIfExists(dstFile))
|
||||
|
||||
assertFailsWith(NoSuchFileException::class) {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
|
||||
Files.createDirectory(srcFile)
|
||||
srcFile.resolve("somefile").writeText("some content")
|
||||
dstFile.writeText("")
|
||||
assertFailsWith(FileAlreadyExistsException::class, "copy dir do not overwrite file") {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
assertTrue(dstFile.isDirectory())
|
||||
assertTrue(dstFile.listFiles().isEmpty(), "only directory is copied, but not its content")
|
||||
|
||||
assertFailsWith(FileAlreadyExistsException::class, "copy dir do not overwrite dir") {
|
||||
srcFile.copyTo(dstFile)
|
||||
}
|
||||
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
assertTrue(dstFile.isDirectory())
|
||||
assertTrue(dstFile.listFiles().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") {
|
||||
srcFile.copyTo(dstFile, overwrite = true)
|
||||
}
|
||||
} finally {
|
||||
srcFile.toFile().deleteRecursively()
|
||||
dstFile.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun copyToNameWithoutParent() {
|
||||
val currentDir = Paths.get("").toAbsolutePath()
|
||||
val srcFile = Files.createTempFile(null, null)
|
||||
val dstFile = Files.createTempFile(currentDir, null, null)
|
||||
try {
|
||||
srcFile.writeText("Hello, World!", Charsets.UTF_8)
|
||||
Files.delete(dstFile)
|
||||
|
||||
val dstRelative = Paths.get(dstFile.fileName.toString())
|
||||
|
||||
srcFile.copyTo(dstRelative)
|
||||
|
||||
assertEquals(srcFile.readText(), dstFile.readText())
|
||||
} finally {
|
||||
Files.delete(dstFile)
|
||||
Files.delete(srcFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compareFiles(src: Path, dst: Path, message: String? = null) {
|
||||
assertTrue(dst.exists())
|
||||
assertEquals(src.isFile(), dst.isFile(), message)
|
||||
if (dst.isFile()) {
|
||||
assertTrue(src.readBytes().contentEquals(dst.readBytes()), message)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBufferedReader() {
|
||||
val file = Files.createTempFile(null, null)
|
||||
val lines = listOf("line1", "line2")
|
||||
Files.write(file, lines)
|
||||
|
||||
assertEquals(file.bufferedReader().use { it.readLines() }, lines)
|
||||
assertEquals(file.bufferedReader(StandardOpenOption.READ).use { it.readLines() }, lines)
|
||||
assertEquals(file.bufferedReader(1024, StandardOpenOption.READ).use { it.readLines() }, lines)
|
||||
assertEquals(file.bufferedReader(Charsets.UTF_8, StandardOpenOption.READ).use { it.readLines() }, lines)
|
||||
assertEquals(file.bufferedReader(Charsets.UTF_8, 1024, StandardOpenOption.READ).use { it.readLines() }, lines)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBufferedWriter() {
|
||||
val file = Files.createTempFile(null, null)
|
||||
|
||||
file.bufferedWriter().use { it.write("line1\n") }
|
||||
file.bufferedWriter(StandardOpenOption.APPEND).use { it.write("line2\n") }
|
||||
file.bufferedWriter(Charsets.UTF_8, StandardOpenOption.APPEND).use { it.write("line3\n") }
|
||||
file.bufferedWriter(1024, StandardOpenOption.APPEND).use { it.write("line4\n") }
|
||||
file.bufferedWriter(Charsets.UTF_8, 1024, StandardOpenOption.APPEND).use { it.write("line5\n") }
|
||||
|
||||
assertEquals(Files.readAllLines(file), listOf("line1", "line2", "line3", "line4", "line5"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrintWriter() {
|
||||
val file = Files.createTempFile(null, null)
|
||||
|
||||
val writer = file.printWriter()
|
||||
val str1 = "Hello, world!"
|
||||
val str2 = "Everything is wonderful!"
|
||||
writer.println(str1)
|
||||
writer.println(str2)
|
||||
writer.close()
|
||||
|
||||
val writer2 = file.printWriter(StandardOpenOption.APPEND)
|
||||
val str3 = "Hello again!"
|
||||
writer2.println(str3)
|
||||
writer2.close()
|
||||
|
||||
val writer3 = file.printWriter(Charsets.UTF_8, StandardOpenOption.APPEND)
|
||||
val str4 = "Hello one last time!"
|
||||
writer3.println(str4)
|
||||
writer3.close()
|
||||
|
||||
val reader = file.bufferedReader()
|
||||
assertEquals(str1, reader.readLine())
|
||||
assertEquals(str2, reader.readLine())
|
||||
assertEquals(str3, reader.readLine())
|
||||
assertEquals(str4, reader.readLine())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWriteBytes() {
|
||||
val file = Files.createTempFile(null, null)
|
||||
file.writeBytes("Hello".encodeToByteArray())
|
||||
file.appendBytes(" world!".encodeToByteArray())
|
||||
assertEquals(file.readText(), "Hello world!")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAttributeGetters() {
|
||||
val file = Files.createTempFile(null, null)
|
||||
assertTrue(file.exists())
|
||||
assertTrue(file.isFile())
|
||||
assertFalse(file.isDirectory())
|
||||
assertFalse(file.isSymbolicLink())
|
||||
assertTrue(file.isReadable())
|
||||
assertTrue(file.isWritable())
|
||||
assertTrue(file.isSameFile(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()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListFiles() {
|
||||
val dir = Files.createTempDirectory(null)
|
||||
assertEquals(dir.listFiles().size, 0)
|
||||
|
||||
val file = dir.resolve("f1")
|
||||
Files.createFile(file)
|
||||
assertEquals(dir.listFiles().size, 1)
|
||||
|
||||
assertFailsWith<NotDirectoryException> { file.listFiles() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.jdk7.test
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.util.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class PathReadWriteTest {
|
||||
@Test
|
||||
fun testAppendText() {
|
||||
val file = Files.createTempFile(null, null)
|
||||
file.writeText("Hello\n")
|
||||
file.appendText("World\n")
|
||||
file.appendText("Again")
|
||||
|
||||
assertEquals("Hello\nWorld\nAgain", file.readText())
|
||||
assertEquals(listOf("Hello", "World", "Again"), file.readLines(Charsets.UTF_8))
|
||||
file.toFile().deleteOnExit()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun file() {
|
||||
val file = Files.createTempFile(null, null)
|
||||
val writer = file.outputStream().writer().buffered()
|
||||
|
||||
writer.write("Hello")
|
||||
writer.newLine()
|
||||
writer.write("World")
|
||||
writer.close()
|
||||
|
||||
file.forEachBlock { arr: ByteArray, size: Int ->
|
||||
assertTrue(size in 11..12, size.toString())
|
||||
assertTrue(arr.contains('W'.toByte()))
|
||||
}
|
||||
val list = ArrayList<String>()
|
||||
file.forEachLine(StandardOpenOption.READ, charset = Charsets.UTF_8) {
|
||||
list.add(it)
|
||||
}
|
||||
assertEquals(arrayListOf("Hello", "World"), list)
|
||||
|
||||
assertEquals(arrayListOf("Hello", "World"), file.readLines())
|
||||
|
||||
file.useLines {
|
||||
assertEquals(arrayListOf("Hello", "World"), it.toList())
|
||||
}
|
||||
|
||||
val text = file.inputStream().reader().readText()
|
||||
assertTrue(text.contains("Hello"))
|
||||
assertTrue(text.contains("World"))
|
||||
|
||||
file.writeText("")
|
||||
var c = 0
|
||||
file.forEachLine { c++ }
|
||||
assertEquals(0, c)
|
||||
|
||||
file.writeText(" ")
|
||||
file.forEachLine { c++ }
|
||||
assertEquals(1, c)
|
||||
|
||||
file.writeText(" \n")
|
||||
c = 0
|
||||
file.forEachLine { c++ }
|
||||
assertEquals(1, c)
|
||||
|
||||
file.writeText(" \n ")
|
||||
c = 0
|
||||
file.forEachLine { c++ }
|
||||
assertEquals(2, c)
|
||||
|
||||
file.toFile().deleteOnExit()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user