From 1a4c11cb98699dfb23a03cffa257eccadf0db7c9 Mon Sep 17 00:00:00 2001 From: Denis Mekhanikov Date: Wed, 15 Oct 2014 16:17:21 +0400 Subject: [PATCH] stdlib: rearrange IO functions --- libraries/stdlib/src/kotlin/io/Closable.kt | 31 -- libraries/stdlib/src/kotlin/io/Console.kt | 140 +++++++++ libraries/stdlib/src/kotlin/io/Files.kt | 133 --------- libraries/stdlib/src/kotlin/io/IOStreams.kt | 71 +++++ libraries/stdlib/src/kotlin/io/JIO.kt | 314 -------------------- libraries/stdlib/src/kotlin/io/ReadWrite.kt | 270 +++++++++++++++++ 6 files changed, 481 insertions(+), 478 deletions(-) delete mode 100644 libraries/stdlib/src/kotlin/io/Closable.kt create mode 100644 libraries/stdlib/src/kotlin/io/Console.kt create mode 100644 libraries/stdlib/src/kotlin/io/IOStreams.kt delete mode 100644 libraries/stdlib/src/kotlin/io/JIO.kt create mode 100644 libraries/stdlib/src/kotlin/io/ReadWrite.kt diff --git a/libraries/stdlib/src/kotlin/io/Closable.kt b/libraries/stdlib/src/kotlin/io/Closable.kt deleted file mode 100644 index eab1f333822..00000000000 --- a/libraries/stdlib/src/kotlin/io/Closable.kt +++ /dev/null @@ -1,31 +0,0 @@ -package kotlin - -import java.io.Closeable - -/** Uses the given resource then closes it down correctly whether an exception is thrown or not */ -public inline fun T.use(block: (T) -> R): R { - var closed = false - try { - return block(this) - } catch (e: Exception) { - closed = true - try { - this.close() - } catch (closeException: Exception) { - // eat the closeException as we are already throwing the original cause - // and we don't want to mask the real exception - - // TODO on Java 7 we should call - // e.addSuppressed(closeException) - // to work like try-with-resources - // http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html#suppressed-exceptions - } - throw e - } finally { - if (!closed) { - this.close() - } - } -} - - diff --git a/libraries/stdlib/src/kotlin/io/Console.kt b/libraries/stdlib/src/kotlin/io/Console.kt new file mode 100644 index 00000000000..5d9aa6c5659 --- /dev/null +++ b/libraries/stdlib/src/kotlin/io/Console.kt @@ -0,0 +1,140 @@ +package kotlin.io + +import java.io.InputStream +import java.io.InputStreamReader +import java.io.BufferedReader + +/** + * Returns the default buffer size when working with buffered streams + */ +public val defaultBufferSize: Int = 64 * 1024 + +/** Prints the given message to [[System.out]] */ +public fun print(message: Any?) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Int) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Long) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Byte) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Short) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Char) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Boolean) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Float) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: Double) { + System.out.print(message) +} +/** Prints the given message to [[System.out]] */ +public fun print(message: CharArray) { + System.out.print(message) +} + +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Any?) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Int) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Long) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Byte) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Short) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Char) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Boolean) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Float) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: Double) { + System.out.println(message) +} +/** Prints the given message and newline to [[System.out]] */ +public fun println(message: CharArray) { + System.out.println(message) +} +/** Prints a newline t[[System.out]] */ +public fun println() { + System.out.println() +} + +// Since System.in can change its value on the course of program running, +// we should always delegate to current value and cannot just pass it to InputStreamReader constructor. +// We could use "by" implementation, but we can only use "by" with traits and InputStream is abstract class. +private val stdin: BufferedReader = BufferedReader(InputStreamReader(object : InputStream() { + public override fun read(): Int { + return System.`in`.read() + } + + public override fun reset() { + System.`in`.reset() + } + + public override fun read(b: ByteArray): Int { + return System.`in`.read(b) + } + + public override fun close() { + System.`in`.close() + } + + public override fun mark(readlimit: Int) { + System.`in`.mark(readlimit) + } + + public override fun skip(n: Long): Long { + return System.`in`.skip(n) + } + + public override fun available(): Int { + return System.`in`.available() + } + + public override fun markSupported(): Boolean { + return System.`in`.markSupported() + } + + public override fun read(b: ByteArray, off: Int, len: Int): Int { + return System.`in`.read(b, off, len) + } +})) + +/** Reads a line of input from [[System.in]] */ +public fun readLine(): String? = stdin.readLine() diff --git a/libraries/stdlib/src/kotlin/io/Files.kt b/libraries/stdlib/src/kotlin/io/Files.kt index 51cc4b8004a..a276d91edfe 100644 --- a/libraries/stdlib/src/kotlin/io/Files.kt +++ b/libraries/stdlib/src/kotlin/io/Files.kt @@ -78,76 +78,6 @@ public fun File.relativePath(descendant: File): String { } } -/** - * Creates a new [[FileReader]] for this file - */ -public fun File.reader(): FileReader = FileReader(this) - -/** - * Reads the entire content of the file as bytes - * - * This method is not recommended on huge files. - */ -public fun File.readBytes(): ByteArray { - return FileInputStream(this).use { it.readBytes(length().toInt()) } -} - -/** - * Writes the bytes as the contents of the file - */ -public fun File.writeBytes(data: ByteArray): Unit { - return FileOutputStream(this).use { it.write(data) } -} - -/** - * Appends bytes to the contents of the file. - */ -public fun File.appendBytes(data: ByteArray): Unit { - return FileOutputStream(this, true).use { it.write(data) } -} - -/** - * Reads the entire content of the file as a String using specified charset. - * - * This method is not recommended on huge files. - */ -public fun File.readText(charset: String): String = readBytes().toString(charset) - -/** - * Reads the entire content of the file as a String using UTF-8 or specified charset. - * - * This method is not recommended on huge files. - */ -public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset) - -/** - * Writes the text as the contents of the file using specified charset. - */ -public fun File.writeText(text: String, charset: String): Unit { - writeBytes(text.toByteArray(charset)) -} - -/** - * Writes the text as the contents of the file using UTF-8 or specified charset. - */ -public fun File.writeText(text: String, charset: Charset = Charsets.UTF_8): Unit { - writeBytes(text.toByteArray(charset)) -} - -/** - * Appends text to the contents of the file using UTF-8 or specified charset. - */ -public fun File.appendText(text: String, charset: Charset = Charsets.UTF_8): Unit { - appendBytes(text.toByteArray(charset)) -} - -/** - * Appends text to the contents of the file using specified charset. - */ -public fun File.appendText(text: String, charset: String): Unit { - appendBytes(text.toByteArray(charset)) -} - /** * Copies this file to the given output file, returning the number of bytes copied */ @@ -162,69 +92,6 @@ public fun File.copyTo(file: File, bufferSize: Int = defaultBufferSize): Long { } } -/** - * Reads file by byte blocks and calls closure for each block read. Block size depends on implementation but never less than 512. - * This functions passes byte array and amount of bytes in this buffer to the closure function. - * - * You can use this function for huge files - */ -public fun File.forEachBlock(closure: (ByteArray, Int) -> Unit): Unit { - val arr = ByteArray(4096) - val fis = FileInputStream(this) - - try { - do { - val size = fis.read(arr) - if (size == -1) { - break - } else if (size > 0) { - closure(arr, size) - } - } while (true) - } finally { - fis.close() - } -} - -/** - * Reads file line by line using specified [charset]. Default charset is UTF-8. - * - * You may use this function on huge files - */ -public fun File.forEachLine(charset: Charset = Charsets.UTF_8, closure: (line: String) -> Unit): Unit { - val reader = BufferedReader(InputStreamReader(FileInputStream(this), charset)) - try { - reader.forEachLine(closure) - } finally { - reader.close() - } -} - -/** - * Reads file line by line using the specified [charset]. - * - * You may use this function on huge files - */ -public fun File.forEachLine(charset: String, closure: (line: String) -> Unit): Unit = forEachLine(Charset.forName(charset), closure) - -/** - * Reads file content into list of lines using specified [charset] - * - * Do not use this function for huge files. - */ -public fun File.readLines(charset: String): List = readLines(Charset.forName(charset)) - -/** - * Reads file content as strings list. By default uses UTF-8 charset. - * - * Do not use this function for huge files. - */ -public fun File.readLines(charset: Charset = Charsets.UTF_8): List { - val result = ArrayList() - forEachLine(charset) { result.add(it); } - return result -} - /** * Returns an array of files and directories in the directory that satisfy the specified filter. */ diff --git a/libraries/stdlib/src/kotlin/io/IOStreams.kt b/libraries/stdlib/src/kotlin/io/IOStreams.kt new file mode 100644 index 00000000000..816ca83d1b9 --- /dev/null +++ b/libraries/stdlib/src/kotlin/io/IOStreams.kt @@ -0,0 +1,71 @@ +package kotlin.io + +import java.io.* +import java.nio.charset.Charset +import java.nio.charset.CharsetDecoder +import java.nio.charset.CharsetEncoder + +/** Returns an [Iterator] of bytes over an input stream */ +public fun InputStream.iterator(): ByteIterator = + object: ByteIterator() { + override fun hasNext(): Boolean = available() > 0 + + public override fun nextByte(): Byte = read().toByte() + } + +/** Creates a buffered input stream */ +public fun InputStream.buffered(bufferSize: Int = defaultBufferSize): InputStream + = if (this is BufferedInputStream) + this +else + BufferedInputStream(this, bufferSize) + +/** Creates a reader on an input stream using UTF-8 or specified charset. */ +public fun InputStream.reader(charset: Charset = Charsets.UTF_8): InputStreamReader = InputStreamReader(this, charset) + +/** Creates a reader on an input stream using specified *charset* */ +public fun InputStream.reader(charset: String): InputStreamReader = InputStreamReader(this, charset) + +/** Creates a reader on an input stream using specified *decoder* */ +public fun InputStream.reader(decoder: CharsetDecoder): InputStreamReader = InputStreamReader(this, decoder) + +/** Creates a buffered output stream */ +public fun OutputStream.buffered(bufferSize: Int = defaultBufferSize): BufferedOutputStream + = if (this is BufferedOutputStream) this else BufferedOutputStream(this, bufferSize) + +/** Creates a writer on an output stream using UTF-8 or specified charset. */ +public fun OutputStream.writer(charset: Charset = Charsets.UTF_8): OutputStreamWriter = OutputStreamWriter(this, charset) + +/** Creates a writer on an output stream using specified *charset* */ +public fun OutputStream.writer(charset: String): OutputStreamWriter = OutputStreamWriter(this, charset) + +/** Creates a writer on an output stream using specified *encoder* */ +public fun OutputStream.writer(encoder: CharsetEncoder): OutputStreamWriter = OutputStreamWriter(this, encoder) + +/** + * Copies this stream to the given output stream, returning the number of bytes copied + * + * **Note** it is the callers responsibility to close both of these resources + */ +public fun InputStream.copyTo(out: OutputStream, bufferSize: Int = defaultBufferSize): Long { + var bytesCopied: Long = 0 + val buffer = ByteArray(bufferSize) + var bytes = read(buffer) + while (bytes >= 0) { + out.write(buffer, 0, bytes) + bytesCopied += bytes + bytes = read(buffer) + } + return bytesCopied +} + +/** + * Reads this stream completely into a byte array + * + * **Note** it is the callers responsibility to close this resource + */ +public fun InputStream.readBytes(estimatedSize: Int = defaultBufferSize): ByteArray { + val buffer = ByteArrayOutputStream(estimatedSize) + this.copyTo(buffer) + return buffer.toByteArray() +} diff --git a/libraries/stdlib/src/kotlin/io/JIO.kt b/libraries/stdlib/src/kotlin/io/JIO.kt deleted file mode 100644 index 178a61ad9f8..00000000000 --- a/libraries/stdlib/src/kotlin/io/JIO.kt +++ /dev/null @@ -1,314 +0,0 @@ -package kotlin.io - -import java.io.* -import java.nio.charset.* -import java.util.NoSuchElementException -import java.net.URL - -/** - * Returns the default buffer size when working with buffered streams - */ -public val defaultBufferSize: Int = 64 * 1024 - -/** Prints the given message to [[System.out]] */ -public fun print(message: Any?) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Int) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Long) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Byte) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Short) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Char) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Boolean) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Float) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: Double) { - System.out.print(message) -} -/** Prints the given message to [[System.out]] */ -public fun print(message: CharArray) { - System.out.print(message) -} - -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Any?) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Int) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Long) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Byte) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Short) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Char) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Boolean) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Float) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: Double) { - System.out.println(message) -} -/** Prints the given message and newline to [[System.out]] */ -public fun println(message: CharArray) { - System.out.println(message) -} -/** Prints a newline t[[System.out]] */ -public fun println() { - System.out.println() -} - -// Since System.in can change its value on the course of program running, -// we should always delegate to current value and cannot just pass it to InputStreamReader constructor. -// We could use "by" implementation, but we can only use "by" with traits and InputStream is abstract class. -private val stdin: BufferedReader = BufferedReader(InputStreamReader(object : InputStream() { - public override fun read(): Int { - return System.`in`.read() - } - - public override fun reset() { - System.`in`.reset() - } - - public override fun read(b: ByteArray): Int { - return System.`in`.read(b) - } - - public override fun close() { - System.`in`.close() - } - - public override fun mark(readlimit: Int) { - System.`in`.mark(readlimit) - } - - public override fun skip(n: Long): Long { - return System.`in`.skip(n) - } - - public override fun available(): Int { - return System.`in`.available() - } - - public override fun markSupported(): Boolean { - return System.`in`.markSupported() - } - - public override fun read(b: ByteArray, off: Int, len: Int): Int { - return System.`in`.read(b, off, len) - } -})) - -/** Reads a line of input from [[System.in]] */ -public fun readLine(): String? = stdin.readLine() - -/** Returns an [Iterator] of bytes over an input stream */ -public fun InputStream.iterator(): ByteIterator = - object: ByteIterator() { - override fun hasNext(): Boolean = available() > 0 - - public override fun nextByte(): Byte = read().toByte() - } - -/** Creates a buffered input stream */ -public fun InputStream.buffered(bufferSize: Int = defaultBufferSize): InputStream - = if (this is BufferedInputStream) - this -else - BufferedInputStream(this, bufferSize) - -/** Creates a reader on an input stream using UTF-8 or specified charset. */ -public fun InputStream.reader(charset: Charset = Charsets.UTF_8): InputStreamReader = InputStreamReader(this, charset) - -/** Creates a reader on an input stream using specified *charset* */ -public fun InputStream.reader(charset: String): InputStreamReader = InputStreamReader(this, charset) - -/** Creates a reader on an input stream using specified *decoder* */ -public fun InputStream.reader(decoder: CharsetDecoder): InputStreamReader = InputStreamReader(this, decoder) - - -/** Creates a buffered output stream */ -public fun OutputStream.buffered(bufferSize: Int = defaultBufferSize): BufferedOutputStream - = if (this is BufferedOutputStream) this else BufferedOutputStream(this, bufferSize) - -/** Creates a writer on an output stream using UTF-8 or specified charset. */ -public fun OutputStream.writer(charset: Charset = Charsets.UTF_8): OutputStreamWriter = OutputStreamWriter(this, charset) - -/** Creates a writer on an output stream using specified *charset* */ -public fun OutputStream.writer(charset: String): OutputStreamWriter = OutputStreamWriter(this, charset) - -/** Creates a writer on an output stream using specified *encoder* */ -public fun OutputStream.writer(encoder: CharsetEncoder): OutputStreamWriter = OutputStreamWriter(this, encoder) - - -/** Creates a buffered reader, or returns self if Reader is already buffered */ -public fun Reader.buffered(bufferSize: Int = defaultBufferSize): BufferedReader - = if (this is BufferedReader) this else BufferedReader(this, bufferSize) - -/** Creates a buffered writer, or returns self if Writer is already buffered */ -public fun Writer.buffered(bufferSize: Int = defaultBufferSize): BufferedWriter - = if (this is BufferedWriter) this else BufferedWriter(this, bufferSize) - -/** - * Iterates through each line of this reader then closing the [[Reader]] when its completed - */ -public fun Reader.forEachLine(block: (String) -> Unit): Unit = useLines { lines -> lines.forEach(block) } - -public inline fun Reader.useLines(block: (Stream) -> T): T = - this.buffered().use { block(it.lines()) } - -/** - * Returns an iterator over each line. - * Note the caller must close the underlying BufferedReader - * when the iteration is finished; as the user may not complete the iteration loop (e.g. using a method like find() or any() on the iterator - * may terminate the iteration early. - *
- * We suggest you try the method useLines() instead which closes the stream when the processing is complete. - */ -public fun BufferedReader.lines(): Stream = LinesStream(this) - -deprecated("Use lines() function which returns Stream") -public fun BufferedReader.lineIterator(): Iterator = lines().iterator() - -private class LinesStream(private val reader: BufferedReader) : Stream { - override fun iterator(): Iterator { - return object : Iterator { - private var nextValue: String? = null - private var done = false - - override fun hasNext(): Boolean { - if (nextValue == null && !done) { - nextValue = reader.readLine() - if (nextValue == null) done = true - } - return nextValue != null - } - - public override fun next(): String { - if (!hasNext()) { - throw NoSuchElementException() - } - val answer = nextValue - nextValue = null - return answer!! - } - } - } -} - - -/** - * Reads this stream completely into a byte array - * - * **Note** it is the callers responsibility to close this resource - */ -public fun InputStream.readBytes(estimatedSize: Int = defaultBufferSize): ByteArray { - val buffer = ByteArrayOutputStream(estimatedSize) - this.copyTo(buffer) - return buffer.toByteArray() -} - -/** - * Reads this reader completely as a String - * - * **Note** it is the callers responsibility to close this resource - */ -public fun Reader.readText(): String { - val buffer = StringWriter() - copyTo(buffer) - return buffer.toString() -} - -/** - * Copies this stream to the given output stream, returning the number of bytes copied - * - * **Note** it is the callers responsibility to close both of these resources - */ -public fun InputStream.copyTo(out: OutputStream, bufferSize: Int = defaultBufferSize): Long { - var bytesCopied: Long = 0 - val buffer = ByteArray(bufferSize) - var bytes = read(buffer) - while (bytes >= 0) { - out.write(buffer, 0, bytes) - bytesCopied += bytes - bytes = read(buffer) - } - return bytesCopied -} - -/** - * Copies this reader to the given output writer, returning the number of bytes copied. - * - * **Note** it is the callers responsibility to close both of these resources - */ -public fun Reader.copyTo(out: Writer, bufferSize: Int = defaultBufferSize): Long { - var charsCopied: Long = 0 - val buffer = CharArray(bufferSize) - var chars = read(buffer) - while (chars >= 0) { - out.write(buffer, 0, chars) - charsCopied += chars - chars = read(buffer) - } - return charsCopied -} - -/** - * Reads the entire content of the URL as a String using specified charset. - * - * This method is not recommended on huge files. - */ -public fun URL.readText(charset: String): String = readBytes().toString(charset) - -/** - * Reads the entire content of the URL as a String using UTF-8 or specified charset.. - * - * This method is not recommended on huge files. - */ -public fun URL.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset) - -/** - * Reads the entire content of the URL as bytes - * - * This method is not recommended on huge files. - */ -public fun URL.readBytes(): ByteArray = this.openStream()!!.use{ it.readBytes() } - diff --git a/libraries/stdlib/src/kotlin/io/ReadWrite.kt b/libraries/stdlib/src/kotlin/io/ReadWrite.kt new file mode 100644 index 00000000000..8ade32eea1b --- /dev/null +++ b/libraries/stdlib/src/kotlin/io/ReadWrite.kt @@ -0,0 +1,270 @@ +package kotlin.io + +import java.io.* +import java.nio.charset.Charset +import java.net.URL +import java.util.ArrayList +import java.util.NoSuchElementException + +/** + * Creates a new [[FileReader]] for this file + */ +public fun File.reader(): FileReader = FileReader(this) + +/** + * Reads the entire content of the file as bytes + * + * This method is not recommended on huge files. + */ +public fun File.readBytes(): ByteArray { + return FileInputStream(this).use { it.readBytes(length().toInt()) } +} + +/** + * Writes the bytes as the contents of the file + */ +public fun File.writeBytes(data: ByteArray): Unit { + return FileOutputStream(this).use { it.write(data) } +} + +/** + * Appends bytes to the contents of the file. + */ +public fun File.appendBytes(data: ByteArray): Unit { + return FileOutputStream(this, true).use { it.write(data) } +} + +/** + * Reads the entire content of the file as a String using specified charset. + * + * This method is not recommended on huge files. + */ +public fun File.readText(charset: String): String = readBytes().toString(charset) + +/** + * Reads the entire content of the file as a String using UTF-8 or specified charset. + * + * This method is not recommended on huge files. + */ +public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset) + +/** + * Writes the text as the contents of the file using specified charset. + */ +public fun File.writeText(text: String, charset: String): Unit { + writeBytes(text.toByteArray(charset)) +} + +/** + * Writes the text as the contents of the file using UTF-8 or specified charset. + */ +public fun File.writeText(text: String, charset: Charset = Charsets.UTF_8): Unit { + writeBytes(text.toByteArray(charset)) +} + +/** + * Appends text to the contents of the file using UTF-8 or specified charset. + */ +public fun File.appendText(text: String, charset: Charset = Charsets.UTF_8): Unit { + appendBytes(text.toByteArray(charset)) +} + +/** + * Appends text to the contents of the file using specified charset. + */ +public fun File.appendText(text: String, charset: String): Unit { + appendBytes(text.toByteArray(charset)) +} + +/** + * Reads file by byte blocks and calls closure for each block read. Block size depends on implementation but never less than 512. + * This functions passes byte array and amount of bytes in this buffer to the closure function. + * + * You can use this function for huge files + */ +public fun File.forEachBlock(closure: (ByteArray, Int) -> Unit): Unit { + val arr = ByteArray(4096) + val fis = FileInputStream(this) + + try { + do { + val size = fis.read(arr) + if (size == -1) { + break + } else if (size > 0) { + closure(arr, size) + } + } while (true) + } finally { + fis.close() + } +} + +/** + * Reads file line by line using specified [charset]. Default charset is UTF-8. + * + * You may use this function on huge files + */ +public fun File.forEachLine(charset: Charset = Charsets.UTF_8, closure: (line: String) -> Unit): Unit { + val reader = BufferedReader(InputStreamReader(FileInputStream(this), charset)) + try { + reader.forEachLine(closure) + } finally { + reader.close() + } +} + +/** + * Reads file line by line using the specified [charset]. + * + * You may use this function on huge files + */ +public fun File.forEachLine(charset: String, closure: (line: String) -> Unit): Unit = forEachLine(Charset.forName(charset), closure) + +/** + * Reads file content into list of lines using specified [charset] + * + * Do not use this function for huge files. + */ +public fun File.readLines(charset: String): List = readLines(Charset.forName(charset)) + +/** + * Reads file content as strings list. By default uses UTF-8 charset. + * + * Do not use this function for huge files. + */ +public fun File.readLines(charset: Charset = Charsets.UTF_8): List { + val result = ArrayList() + forEachLine(charset) { result.add(it); } + return result +} + +/** Creates a buffered reader, or returns self if Reader is already buffered */ +public fun Reader.buffered(bufferSize: Int = defaultBufferSize): BufferedReader + = if (this is BufferedReader) this else BufferedReader(this, bufferSize) + +/** Creates a buffered writer, or returns self if Writer is already buffered */ +public fun Writer.buffered(bufferSize: Int = defaultBufferSize): BufferedWriter + = if (this is BufferedWriter) this else BufferedWriter(this, bufferSize) + +/** + * Iterates through each line of this reader then closing the [[Reader]] when its completed + */ +public fun Reader.forEachLine(block: (String) -> Unit): Unit = useLines { lines -> lines.forEach(block) } + +public inline fun Reader.useLines(block: (Stream) -> T): T = + this.buffered().use { block(it.lines()) } + +/** + * Returns an iterator over each line. + * Note the caller must close the underlying BufferedReader + * when the iteration is finished; as the user may not complete the iteration loop (e.g. using a method like find() or any() on the iterator + * may terminate the iteration early. + *
+ * We suggest you try the method useLines() instead which closes the stream when the processing is complete. + */ +public fun BufferedReader.lines(): Stream = LinesStream(this) + +deprecated("Use lines() function which returns Stream") +public fun BufferedReader.lineIterator(): Iterator = lines().iterator() + +private class LinesStream(private val reader: BufferedReader) : Stream { + override fun iterator(): Iterator { + return object : Iterator { + private var nextValue: String? = null + private var done = false + + override fun hasNext(): Boolean { + if (nextValue == null && !done) { + nextValue = reader.readLine() + if (nextValue == null) done = true + } + return nextValue != null + } + + public override fun next(): String { + if (!hasNext()) { + throw NoSuchElementException() + } + val answer = nextValue + nextValue = null + return answer!! + } + } + } +} + +/** + * Reads this reader completely as a String + * + * **Note** it is the callers responsibility to close this resource + */ +public fun Reader.readText(): String { + val buffer = StringWriter() + copyTo(buffer) + return buffer.toString() +} + +/** + * Copies this reader to the given output writer, returning the number of bytes copied. + * + * **Note** it is the callers responsibility to close both of these resources + */ +public fun Reader.copyTo(out: Writer, bufferSize: Int = defaultBufferSize): Long { + var charsCopied: Long = 0 + val buffer = CharArray(bufferSize) + var chars = read(buffer) + while (chars >= 0) { + out.write(buffer, 0, chars) + charsCopied += chars + chars = read(buffer) + } + return charsCopied +} + +/** + * Reads the entire content of the URL as a String using specified charset. + * + * This method is not recommended on huge files. + */ +public fun URL.readText(charset: String): String = readBytes().toString(charset) + +/** + * Reads the entire content of the URL as a String using UTF-8 or specified charset.. + * + * This method is not recommended on huge files. + */ +public fun URL.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset) + +/** + * Reads the entire content of the URL as bytes + * + * This method is not recommended on huge files. + */ +public fun URL.readBytes(): ByteArray = this.openStream()!!.use{ it.readBytes() } + +/** Uses the given resource then closes it down correctly whether an exception is thrown or not */ +public inline fun T.use(block: (T) -> R): R { + var closed = false + try { + return block(this) + } catch (e: Exception) { + closed = true + try { + this.close() + } catch (closeException: Exception) { + // eat the closeException as we are already throwing the original cause + // and we don't want to mask the real exception + + // TODO on Java 7 we should call + // e.addSuppressed(closeException) + // to work like try-with-resources + // http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html#suppressed-exceptions + } + throw e + } finally { + if (!closed) { + this.close() + } + } +}