diff --git a/libraries/stdlib/jdk7/java9/module-info.java b/libraries/stdlib/jdk7/java9/module-info.java index b786aa09f83..103cb93e9d9 100644 --- a/libraries/stdlib/jdk7/java9/module-info.java +++ b/libraries/stdlib/jdk7/java9/module-info.java @@ -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; diff --git a/libraries/stdlib/jdk7/src/kotlin/io/PathReadWrite.kt b/libraries/stdlib/jdk7/src/kotlin/io/PathReadWrite.kt new file mode 100644 index 00000000000..7fa4044910c --- /dev/null +++ b/libraries/stdlib/jdk7/src/kotlin/io/PathReadWrite.kt @@ -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 { + 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 Path.useLines(charset: Charset = Charsets.UTF_8, block: (Sequence) -> T): T { + return bufferedReader(charset).use { block(it.lineSequence()) } +} diff --git a/libraries/stdlib/jdk7/src/kotlin/io/PathUtils.kt b/libraries/stdlib/jdk7/src/kotlin/io/PathUtils.kt new file mode 100644 index 00000000000..e427bab2a2d --- /dev/null +++ b/libraries/stdlib/jdk7/src/kotlin/io/PathUtils.kt @@ -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 { + return Files.newDirectoryStream(this).use { it.toList() } +} diff --git a/libraries/stdlib/jdk7/test/PathExtensionsTest.kt b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt new file mode 100644 index 00000000000..1ff78d990ca --- /dev/null +++ b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt @@ -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 { file.listFiles() } + } +} diff --git a/libraries/stdlib/jdk7/test/PathReadWriteTest.kt b/libraries/stdlib/jdk7/test/PathReadWriteTest.kt new file mode 100644 index 00000000000..772a0e7926c --- /dev/null +++ b/libraries/stdlib/jdk7/test/PathReadWriteTest.kt @@ -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() + 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() + } +} +