diff --git a/libraries/stdlib/jdk7/src/kotlin/io/PathReadWrite.kt b/libraries/stdlib/jdk7/src/kotlin/io/PathReadWrite.kt index 7fa4044910c..ebd950dd38a 100644 --- a/libraries/stdlib/jdk7/src/kotlin/io/PathReadWrite.kt +++ b/libraries/stdlib/jdk7/src/kotlin/io/PathReadWrite.kt @@ -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 diff --git a/libraries/stdlib/jdk7/src/kotlin/io/PathUtils.kt b/libraries/stdlib/jdk7/src/kotlin/io/PathUtils.kt index e427bab2a2d..2d34729e147 100644 --- a/libraries/stdlib/jdk7/src/kotlin/io/PathUtils.kt +++ b/libraries/stdlib/jdk7/src/kotlin/io/PathUtils.kt @@ -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 { +public fun Path.listDirectoryEntries(): List { 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 Path.useDirectoryEntries(block: (Sequence) -> 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) } +} diff --git a/libraries/stdlib/jdk7/test/PathExtensionsTest.kt b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt index 1ff78d990ca..2a3ed420bee 100644 --- a/libraries/stdlib/jdk7/test/PathExtensionsTest.kt +++ b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt @@ -1,23 +1,26 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ package kotlin.jdk7.test +import java.io.IOException import java.nio.file.* import kotlin.test.* class PathExtensionsTest { + private val isCaseInsensitiveFileSystem = Paths.get("C:/") == Paths.get("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) - // 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) + assertEquals("", Paths.get("/").extension) } @Test @@ -27,6 +30,8 @@ class PathExtensionsTest { 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) } @Test @@ -74,7 +79,7 @@ class PathExtensionsTest { } srcFile.copyTo(dstFile, overwrite = true) assertTrue(dstFile.isDirectory()) - assertTrue(dstFile.listFiles().isEmpty(), "only directory is copied, but not its content") + assertTrue(dstFile.listDirectoryEntries().isEmpty(), "only directory is copied, but not its content") assertFailsWith(FileAlreadyExistsException::class, "copy dir do not overwrite dir") { srcFile.copyTo(dstFile) @@ -82,7 +87,7 @@ class PathExtensionsTest { srcFile.copyTo(dstFile, overwrite = true) assertTrue(dstFile.isDirectory()) - assertTrue(dstFile.listFiles().isEmpty(), "only directory is copied, but not its content") + assertTrue(dstFile.listDirectoryEntries().isEmpty(), "only directory is copied, but not its content") dstFile.resolve("somefile2").writeText("some content2") assertFailsWith(FileAlreadyExistsException::class, "copy dir do not overwrite non-empty dir") { @@ -116,79 +121,18 @@ class PathExtensionsTest { private fun compareFiles(src: Path, dst: Path, message: String? = null) { assertTrue(dst.exists()) - assertEquals(src.isFile(), dst.isFile(), message) - if (dst.isFile()) { + assertEquals(src.isRegularFile(), dst.isRegularFile(), message) + if (dst.isRegularFile()) { 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() { + fun testAttributeGettersOnFile() { val file = Files.createTempFile(null, null) assertTrue(file.exists()) - assertTrue(file.isFile()) + assertFalse(file.notExists()) + assertTrue(file.isRegularFile()) assertFalse(file.isDirectory()) assertFalse(file.isSymbolicLink()) assertTrue(file.isReadable()) @@ -202,14 +146,191 @@ class PathExtensionsTest { } @Test - fun testListFiles() { + fun testAttributeGettersOnDirectory() { + val file = Files.createTempDirectory(null) + assertTrue(file.exists()) + assertFalse(file.notExists()) + assertFalse(file.isRegularFile()) + assertTrue(file.isDirectory()) + assertFalse(file.isSymbolicLink()) + assertTrue(file.isReadable()) + assertTrue(file.isWritable()) + assertTrue(file.isSameFile(file)) + + file.isExecutable() + file.isHidden() + } + + @Test + fun testAttributeGettersOnNonExistentPath() { + val file = Files.createTempDirectory(null).resolve("foo") + assertFalse(file.exists()) + assertTrue(file.notExists()) + assertFalse(file.isRegularFile()) + assertFalse(file.isDirectory()) + assertFalse(file.isSymbolicLink()) + assertFalse(file.isReadable()) + assertFalse(file.isWritable()) + assertTrue(file.isSameFile(file)) + + file.isExecutable() + // This function will either throw an exception or return false, + // depending on the operating system. + try { + assertFalse(file.isHidden()) + } catch (e: IOException) { + } + } + + @Test + fun testListDirectoryEntries() { val dir = Files.createTempDirectory(null) - assertEquals(dir.listFiles().size, 0) + assertEquals(0, dir.listDirectoryEntries().size) val file = dir.resolve("f1") Files.createFile(file) - assertEquals(dir.listFiles().size, 1) + assertEquals(listOf(file), dir.listDirectoryEntries()) - assertFailsWith { file.listFiles() } + assertFailsWith { file.listDirectoryEntries() } + } + + @Test + fun testUseDirectoryEntries() { + val dir = Files.createTempDirectory(null) + assertEquals(0, dir.useDirectoryEntries { it.toList() }.size) + + val file = dir.resolve("f1") + Files.createFile(file) + assertEquals(listOf(file), dir.useDirectoryEntries { it.toList() }) + + assertFailsWith { file.useDirectoryEntries { it.toList() } } + } + + @Test + fun testForEachDirectoryEntry() { + val dir = Files.createTempDirectory(null) + val entries = mutableListOf() + + dir.forEachDirectoryEntry { entries.add(it) } + assertTrue(entries.isEmpty()) + + val file = dir.resolve("f1") + Files.createFile(file) + dir.forEachDirectoryEntry { entries.add(it) } + assertEquals(listOf(file), entries) + + assertFailsWith { file.forEachDirectoryEntry { } } + } + + @Test + fun relativeToRooted() { + val file1 = Paths.get("/foo/bar/baz") + val file2 = Paths.get("/foo/baa/ghoo") + + assertEquals("../../bar/baz", file1.relativeTo(file2).invariantSeparatorsPath) + + val file3 = Paths.get("/foo/bar") + + assertEquals("baz", file1.relativeTo(file3).toString()) + assertEquals("..", file3.relativeTo(file1).toString()) + + val file4 = Paths.get("/foo/bar/") + + assertEquals("baz", file1.relativeTo(file4).toString()) + assertEquals("..", file4.relativeTo(file1).toString()) + assertEquals("", file3.relativeTo(file4).toString()) + assertEquals("", file4.relativeTo(file3).toString()) + + val file5 = Paths.get("/foo/baran") + + assertEquals("../bar", file3.relativeTo(file5).invariantSeparatorsPath) + assertEquals("../baran", file5.relativeTo(file3).invariantSeparatorsPath) + assertEquals("../bar", file4.relativeTo(file5).invariantSeparatorsPath) + assertEquals("../baran", file5.relativeTo(file4).invariantSeparatorsPath) + + if (isBackslashSeparator) { + val file6 = Paths.get("C:\\Users\\Me") + val file7 = Paths.get("C:\\Users\\Me\\Documents") + + assertEquals("..", file6.relativeTo(file7).toString()) + assertEquals("Documents", file7.relativeTo(file6).toString()) + + val file8 = Paths.get("""\\my.host\home/user/documents/vip""") + val file9 = Paths.get("""\\my.host\home/other/images/nice""") + + assertEquals("../../../user/documents/vip", file8.relativeTo(file9).invariantSeparatorsPath) + assertEquals("../../../other/images/nice", file9.relativeTo(file8).invariantSeparatorsPath) + } + + if (isCaseInsensitiveFileSystem) { + assertEquals("bar", Paths.get("C:/bar").relativeTo(Paths.get("c:/")).toString()) + } + } + + @Test + fun relativeToRelative() { + val nested = Paths.get("foo/bar") + val base = Paths.get("foo") + + assertEquals("bar", nested.relativeTo(base).toString()) + assertEquals("..", base.relativeTo(nested).toString()) + + val current = Paths.get(".") + val parent = Paths.get("..") + val outOfRoot = Paths.get("../bar") + + assertEquals(Paths.get("../../bar"), outOfRoot.relativeTo(base)) + assertEquals("bar", outOfRoot.relativeTo(parent).toString()) + assertEquals("..", parent.relativeTo(outOfRoot).toString()) + + val root = Paths.get("/root") + val files = listOf(nested, base, outOfRoot, current, parent) + val bases = listOf(nested, base, current) + + for (file in files) + assertEquals("", file.relativeTo(file).toString(), "file should have empty path relative to itself: $file") + + for (file in files) { + @Suppress("NAME_SHADOWING") + for (base in bases) { + val rootedFile = root.resolve(file) + val rootedBase = root.resolve(base) + assertEquals(file.relativeTo(base), rootedFile.relativeTo(rootedBase), "nested: $file, base: $base") + } + } + } + + @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""") + + fun assertFailsRelativeTo(file: Path, base: Path) { + val e = assertFailsWith("file: $file, base: $base") { file.relativeTo(base) } + assertNotNull(e.message) + } + + val allFiles = listOf(absolute, relative) + if (isBackslashSeparator) listOf(networkShare1, networkShare2) else emptyList() + for (file in allFiles) { + for (base in allFiles) { + if (file != base) assertFailsRelativeTo(file, base) + } + } + + if (isBackslashSeparator) { + val fileOnC = Paths.get("C:/dir1") + val fileOnD = Paths.get("D:/dir2") + assertFailsRelativeTo(fileOnC, fileOnD) + } + } + + @Test + fun relativeTo() { + assertEquals("kotlin", Paths.get("src/kotlin").relativeTo(Paths.get("src")).toString()) + assertEquals("", Paths.get("dir").relativeTo(Paths.get("dir")).toString()) + assertEquals("..", Paths.get("dir").relativeTo(Paths.get("dir/subdir")).toString()) + assertEquals(Paths.get("../../test"), Paths.get("test").relativeTo(Paths.get("dir/dir"))) } } diff --git a/libraries/stdlib/jdk7/test/PathReadWriteTest.kt b/libraries/stdlib/jdk7/test/PathReadWriteTest.kt index 772a0e7926c..bbfa8406486 100644 --- a/libraries/stdlib/jdk7/test/PathReadWriteTest.kt +++ b/libraries/stdlib/jdk7/test/PathReadWriteTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -8,9 +8,7 @@ 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 +import kotlin.test.* class PathReadWriteTest { @Test @@ -18,7 +16,7 @@ class PathReadWriteTest { val file = Files.createTempFile(null, null) file.writeText("Hello\n") file.appendText("World\n") - file.appendText("Again") + file.writeText("Again", Charsets.US_ASCII, StandardOpenOption.APPEND) assertEquals("Hello\nWorld\nAgain", file.readText()) assertEquals(listOf("Hello", "World", "Again"), file.readLines(Charsets.UTF_8)) @@ -35,25 +33,21 @@ class PathReadWriteTest { 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() - file.forEachLine(StandardOpenOption.READ, charset = Charsets.UTF_8) { + file.forEachLine(charset = Charsets.UTF_8, options = arrayOf(StandardOpenOption.READ)) { list.add(it) } - assertEquals(arrayListOf("Hello", "World"), list) + assertEquals(listOf("Hello", "World"), list) - assertEquals(arrayListOf("Hello", "World"), file.readLines()) + assertEquals(listOf("Hello", "World"), file.readLines()) file.useLines { - assertEquals(arrayListOf("Hello", "World"), it.toList()) + assertEquals(listOf("Hello", "World"), it.toList()) } val text = file.inputStream().reader().readText() - assertTrue(text.contains("Hello")) - assertTrue(text.contains("World")) + assertTrue("Hello" in text) + assertTrue("World" in text) file.writeText("") var c = 0 @@ -76,5 +70,62 @@ class PathReadWriteTest { file.toFile().deleteOnExit() } + + @Test + fun testBufferedReader() { + val file = Files.createTempFile(null, null) + val lines = listOf("line1", "line2") + Files.write(file, lines, Charsets.UTF_8) + + assertEquals(file.bufferedReader().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(Charsets.UTF_8, 1024, StandardOpenOption.APPEND).use { it.write("line2\n") } + + assertEquals(Files.readAllLines(file, Charsets.UTF_8), listOf("line1", "line2")) + } + + @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(options = arrayOf(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() + + file.bufferedReader().use { reader -> + 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!") + } }