diff --git a/idea/testData/completion/basic/java/NoDuplicationForRuntimeClass.kt b/idea/testData/completion/basic/java/NoDuplicationForRuntimeClass.kt index 4b0ee96eb83..e2b811d0958 100644 --- a/idea/testData/completion/basic/java/NoDuplicationForRuntimeClass.kt +++ b/idea/testData/completion/basic/java/NoDuplicationForRuntimeClass.kt @@ -1,5 +1,5 @@ -val x = LinesStream +val x = LinesSequence // INVOCATION_COUNT: 2 -// EXIST: { lookupString:"LinesStream", tailText:" (kotlin.io)" } +// EXIST: { lookupString:"LinesSequence", tailText:" (kotlin.io)" } // NUMBER: 1 diff --git a/libraries/stdlib/src/kotlin/io/IOStreams.kt b/libraries/stdlib/src/kotlin/io/IOStreams.kt index 89ea6900817..55c941cedb5 100644 --- a/libraries/stdlib/src/kotlin/io/IOStreams.kt +++ b/libraries/stdlib/src/kotlin/io/IOStreams.kt @@ -62,12 +62,6 @@ public fun InputStream.reader(charset: String): InputStreamReader = InputStreamR /** Creates a buffered reader on this input stream using the specified [charset]. */ public fun InputStream.bufferedReader(charset: String): BufferedReader = reader(charset).buffered() -/** Creates a reader on this input stream using the specified [decoder]. */ -public fun InputStream.reader(decoder: CharsetDecoder): InputStreamReader = InputStreamReader(this, decoder) - -/** Creates a reader on this input stream using the specified [decoder]. */ -public fun InputStream.bufferedReader(decoder: CharsetDecoder): BufferedReader = reader(decoder).buffered() - /** * Creates a buffered output stream wrapping this stream. * @param bufferSize the buffer size to use. @@ -78,11 +72,14 @@ public fun OutputStream.buffered(bufferSize: Int = defaultBufferSize): BufferedO /** Creates a writer on this output stream using UTF-8 or the specified [charset]. */ public fun OutputStream.writer(charset: Charset = Charsets.UTF_8): OutputStreamWriter = OutputStreamWriter(this, charset) +/** Creates a buffered writer on this output stream using UTF-8 or the specified [charset]. */ +public fun OutputStream.bufferedWriter(charset: Charset = Charsets.UTF_8): BufferedWriter = writer(charset).buffered() + /** Creates a writer on this output stream using the specified [charset]. */ public fun OutputStream.writer(charset: String): OutputStreamWriter = OutputStreamWriter(this, charset) -/** Creates a writer on this output stream using the specified [encoder]. */ -public fun OutputStream.writer(encoder: CharsetEncoder): OutputStreamWriter = OutputStreamWriter(this, encoder) +/** Creates a buffered writer on this output stream using the specified [charset]. */ +public fun OutputStream.bufferedWriter(charset: String): BufferedWriter = writer(charset).buffered() /** * Copies this stream to the given output stream, returning the number of bytes copied @@ -112,16 +109,3 @@ public fun InputStream.readBytes(estimatedSize: Int = defaultBufferSize): ByteAr return buffer.toByteArray() } -/** - * Constructs a new FileInputStream of this file and returns it as a result. - */ -public fun File.inputStream(): InputStream { - return FileInputStream(this) -} - -/** - * Constructs a new FileOutputStream of this file and returns it as a result. - */ -public fun File.outputStream(): OutputStream { - return FileOutputStream(this) -} diff --git a/libraries/stdlib/src/kotlin/io/ReadWrite.kt b/libraries/stdlib/src/kotlin/io/ReadWrite.kt index 1c4bbe5a1af..d29e71d14b9 100644 --- a/libraries/stdlib/src/kotlin/io/ReadWrite.kt +++ b/libraries/stdlib/src/kotlin/io/ReadWrite.kt @@ -33,49 +33,34 @@ public fun File.bufferedWriter(bufferSize: Int = defaultBufferSize): BufferedWri /** * Returns a new [PrintWriter] for writing the content of this file. */ -public fun File.printWriter(): PrintWriter = PrintWriter(writer().buffered()) +public fun File.printWriter(): PrintWriter = PrintWriter(bufferedWriter()) /** - * Reads the entire content of this file as a byte array. + * 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 */ -public fun File.readBytes(): ByteArray { - return FileInputStream(this).use { it.readBytes(length().toInt()) } -} +public fun File.readBytes(): ByteArray = FileInputStream(this).use { it.readBytes(length().toInt()) } /** - * Replaces the content of this file with [data]. + * Sets the content of this file as an [array] of bytes. + * If this file already exists, it becomes overwritten. * - * @param data byte array to write into this file + * @param array byte array to write into this file */ -deprecated("Use replaceBytes instead") -public fun File.writeBytes(data: ByteArray): Unit { - return FileOutputStream(this).use { it.write(data) } -} +public fun File.writeBytes(array: ByteArray): Unit = FileOutputStream(this).use { it.write(array) } /** - * Replaces the content of this file with [data]. + * Appends an [array] of bytes to the content of this file. * - * @param data byte array to write into this file + * @param array byte array to append to this file */ -public fun File.replaceBytes(data: ByteArray): Unit { - return FileOutputStream(this).use { it.write(data) } -} +public fun File.appendBytes(array: ByteArray): Unit = FileOutputStream(this, true).use { it.write(array) } /** - * Appends [data] to the content of this file. - * - * @param data byte array to append to this file - */ -public fun File.appendBytes(data: ByteArray): Unit { - return FileOutputStream(this, true).use { it.write(data) } -} - -/** - * Reads the entire content of this file as a String using specified [charset]. + * Gets the entire content of this file as a String using specified [charset]. * * This method is not recommended on huge files. It has an internal limitation of 2 GB file size * @@ -85,7 +70,7 @@ public fun File.appendBytes(data: ByteArray): Unit { public fun File.readText(charset: String): String = readBytes().toString(charset) /** - * Reads the entire content of this file as a String using UTF-8 or specified [charset]. + * 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 * @@ -95,47 +80,22 @@ public fun File.readText(charset: String): String = readBytes().toString(charset public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset) /** - * Replaces the content of this file with [text] encoded using the specified [charset]. + * Sets the content of this file as [text] encoded using the specified [charset]. + * If this file exists, it becomes overwritten. * * @param text text to write into file * @param charset character set to use */ -deprecated("Use replaceText instead") -public fun File.writeText(text: String, charset: String): Unit { - replaceBytes(text.toByteArray(charset)) -} +public fun File.writeText(text: String, charset: String): Unit = writeBytes(text.toByteArray(charset)) /** - * Replaces the content of this file with [text] encoded using the specified [charset]. + * 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 */ -deprecated("There is a same function with default parameter value") -public fun File.replaceText(text: String, charset: String): Unit { - replaceBytes(text.toByteArray(charset)) -} - -/** - * Replaces the content of this file with [text] encoded using UTF-8 or specified [charset]. - * - * @param text text to write into file - * @param charset character set to use - */ -deprecated("Use replaceText instead") -public fun File.writeText(text: String, charset: Charset = Charsets.UTF_8): Unit { - replaceBytes(text.toByteArray(charset)) -} - -/** - * Replaces the content of this file with [text] encoded using UTF-8 or specified [charset]. - * - * @param text text to write into file - * @param charset character set to use - */ -public fun File.replaceText(text: String, charset: Charset = Charsets.UTF_8): Unit { - replaceBytes(text.toByteArray(charset)) -} +public fun File.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]. @@ -143,9 +103,7 @@ public fun File.replaceText(text: String, charset: Charset = Charsets.UTF_8): Un * @param text text to append to file * @param charset character set to use */ -public fun File.appendText(text: String, charset: Charset = Charsets.UTF_8): Unit { - appendBytes(text.toByteArray(charset)) -} +public fun File.appendText(text: String, charset: Charset = Charsets.UTF_8): Unit = appendBytes(text.toByteArray(charset)) /** * Appends [text] to the content of the file using the specified [charset]. @@ -153,31 +111,29 @@ public fun File.appendText(text: String, charset: Charset = Charsets.UTF_8): Uni * @param text text to append to file * @param charset character set to use */ -public fun File.appendText(text: String, charset: String): Unit { - appendBytes(text.toByteArray(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. + * Reads file by byte blocks and calls [operation] for each block read. * Block has default size which is implementation-dependent. - * This functions passes the byte array and amount of bytes in the array to the [closure] function. + * This functions passes the byte array and amount of bytes in the array to the [operation] function. * * You can use this function for huge files. * - * @param closure function to proceed file blocks + * @param operation function to process file blocks */ -public fun File.forEachBlock(closure: (ByteArray, Int) -> Unit): Unit = forEachBlock(closure, defaultBlockSize) +public fun File.forEachBlock(operation: (ByteArray, Int) -> Unit): Unit = forEachBlock(operation, defaultBlockSize) /** - * Reads file by byte blocks and calls [closure] for each block read. - * This functions passes the byte array and amount of bytes in the array to the [closure] function. + * Reads file by byte blocks and calls [operation] for each block read. + * This functions passes the byte array and amount of bytes in the array to the [operation] function. * * You can use this function for huge files. * - * @param closure function to proceed file blocks + * @param operation function to process file blocks * @param blockSize size of a block, replaced by 512 if it's less, 4096 by default */ -public fun File.forEachBlock(closure: (ByteArray, Int) -> Unit, blockSize: Int): Unit { +public fun File.forEachBlock(operation: (ByteArray, Int) -> Unit, blockSize: Int): Unit { val arr = ByteArray(if (blockSize < minimumBlockSize) minimumBlockSize else blockSize) val fis = FileInputStream(this) @@ -187,7 +143,7 @@ public fun File.forEachBlock(closure: (ByteArray, Int) -> Unit, blockSize: Int): if (size <= 0) { break } else { - closure(arr, size) + operation(arr, size) } } while (true) } finally { @@ -196,28 +152,28 @@ public fun File.forEachBlock(closure: (ByteArray, Int) -> Unit, blockSize: Int): } /** - * Reads this file line by line using the specified [charset] and calls [closure] for each line. + * Reads this file line by line using the specified [charset] and calls [operation] for each line. * Default charset is UTF-8. * * You may use this function on huge files. * * @param charset character set to use - * @param closure function to proceed file lines + * @param operation function to process file lines */ -public fun File.forEachLine(charset: Charset = Charsets.UTF_8, closure: (line: String) -> Unit): Unit { +public fun File.forEachLine(charset: Charset = Charsets.UTF_8, operation: (line: String) -> Unit): Unit { // Note: close is called at forEachLine - BufferedReader(InputStreamReader(FileInputStream(this), charset)).forEachLine(closure) + BufferedReader(InputStreamReader(FileInputStream(this), charset)).forEachLine(operation) } /** - * Reads this file line by line using the specified [charset] and calls [closure] for each line. + * Reads this file line by line using the specified [charset] and calls [operation] for each line. * * You may use this function on huge files. * * @param charset character set to use - * @param closure function to proceed file lines + * @param operation function to process file lines */ -public fun File.forEachLine(charset: String, closure: (line: String) -> Unit): Unit = forEachLine(Charset.forName(charset), closure) +public fun File.forEachLine(charset: String, operation: (line: String) -> Unit): Unit = forEachLine(Charset.forName(charset), operation) /** * Reads the file content as a list of lines, using the specified [charset]. @@ -229,6 +185,20 @@ public fun File.forEachLine(charset: String, closure: (line: String) -> Unit): U */ public fun File.readLines(charset: String): List = readLines(Charset.forName(charset)) +/** + * Constructs a new FileInputStream of this file and returns it as a result. + */ +public fun File.inputStream(): InputStream { + return FileInputStream(this) +} + +/** + * Constructs a new FileOutputStream of this file and returns it as a result. + */ +public fun File.outputStream(): OutputStream { + return FileOutputStream(this) +} + /** * Reads the file content as a list of lines. By default uses UTF-8 charset. * @@ -255,7 +225,7 @@ public fun Writer.buffered(bufferSize: Int = defaultBufferSize): BufferedWriter * Iterates through each line of this reader, calls [block] for each line read * and closes the [Reader] when it's completed * - * @param block function to proceed file lines + * @param block function to process file lines */ public fun Reader.forEachLine(block: (String) -> Unit): Unit = useLines { it.forEach(block) } @@ -278,12 +248,12 @@ public inline fun Reader.useLines(block: (Sequence) -> T): T = * * @return a sequence of corresponding file lines */ -public fun BufferedReader.lines(): Sequence = LinesStream(this) +public fun BufferedReader.lines(): Sequence = LinesSequence(this) deprecated("Use lines() function which returns Sequence") public fun BufferedReader.lineIterator(): Iterator = lines().iterator() -private class LinesStream(private val reader: BufferedReader) : Sequence { +private class LinesSequence(private val reader: BufferedReader) : Sequence { override public fun iterator(): Iterator { return object : Iterator { private var nextValue: String? = null @@ -312,7 +282,7 @@ private class LinesStream(private val reader: BufferedReader) : Sequence /** * Reads this reader completely as a String * - * *Note*: It is the caller's responsibility to close this resource. + * *Note*: It is the caller's responsibility to close this reader. * * @return the string with corresponding file content */ @@ -364,19 +334,19 @@ public fun URL.readText(charset: String): String = readBytes().toString(charset) public fun URL.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset) /** - * Reads the entire content of the URL as bytes + * Reads the entire content of the URL as byte array * * This method is not recommended on huge files. * * @return a byte array with this URL entire content */ -public fun URL.readBytes(): ByteArray = openStream().use{ it.readBytes() } +public fun URL.readBytes(): ByteArray = openStream().use { it.readBytes() } /** * Executes the given [block] function on this resource and then closes it down correctly whether an exception * is thrown or not * - * @param block a function to proceed this closable resource + * @param block a function to process this closable resource * @return the result of [block] function on this closable resource */ public inline fun T.use(block: (T) -> R): R { diff --git a/libraries/stdlib/src/kotlin/io/files/FilePathComponents.kt b/libraries/stdlib/src/kotlin/io/files/FilePathComponents.kt index 695f1d32173..16ce99396f0 100644 --- a/libraries/stdlib/src/kotlin/io/files/FilePathComponents.kt +++ b/libraries/stdlib/src/kotlin/io/files/FilePathComponents.kt @@ -1,8 +1,22 @@ package kotlin.io import java.io.File +import java.util.Collections import java.util.NoSuchElementException +/** + * Estimation of a root name by a given file name. + * + * This implementation is able to find /, Drive:/, Drive: or + * //network.name/root as possible root names. + * / denotes File.separator here so \ can be used instead. + * All other possible roots cannot be identified by this implementation. + * It's also not guaranteed (but possible) that function will be able to detect a root + * which is incorrect for current OS. For instance, in Unix function cannot detect + * network root names like //network.name/root, but can detect Windows roots like C:/. + * + * @return string representing the root for this file, or empty string is this file name is relative + */ private fun String.getRootName(): String { // Note: separators should be already replaced to system ones var first = indexOf(File.separatorChar, 0) @@ -36,9 +50,17 @@ private fun String.getRootName(): String { } /** - * Returns a string representation of root component of this abstract name, like / from /home/user, or C:\ from C:\file.tmp, - * or //my.host/home for //my.host/home/user, - * or empty string if this name is relative, like bar/gav + * Estimation of a root name for this file. + * + * This implementation is able to find /, Drive:/, Drive: or + * //network.name/root as possible root names. + * / denotes File.separator here so \ can be used instead. + * All other possible roots cannot be identified by this implementation. + * It's also not guaranteed (but possible) that function will be able to detect a root + * which is incorrect for current OS. For instance, in Unix function cannot detect + * network root names like //network.name/root, but can detect Windows roots like C:/. + * + * @return string representing the root for this file, or empty string is this file name is relative */ public val File.rootName: String get() = separatorsToSystem().getRootName() @@ -55,23 +77,13 @@ public val File.root: File? } public data class FilePathComponents(public val rootName: String, public val fileList: List) { - public val size: Int = fileList.size() + public fun size(): Int = fileList.size() public fun subPath(beginIndex: Int, endIndex: Int): File { - if (beginIndex < 0 || beginIndex >= endIndex || endIndex > size) + if (beginIndex < 0 || beginIndex > endIndex || endIndex > size()) throw IllegalArgumentException() - var res = "" - var first = true - for (elem in fileList.subList(beginIndex, endIndex)) { - if (!first) { - res += File.separatorChar - } else { - first = false - } - res += elem.toString() - } - return File(res) + return File(fileList.subList(beginIndex, endIndex).joinToString(File.separator)) } } @@ -82,6 +94,7 @@ public fun File.filePathComponents(): FilePathComponents { // if: a special case when we have only root component // Split not only by / or \, but also by //, ///, \\, \\\, etc. val list = if (rootName.length() > 0 && subPath.isEmpty()) listOf() else + // Looks awful but we split just by /+ or \+ depending on OS subPath.split("""\Q${File.separatorChar}\E+""").toList().map { it -> File(it) } return FilePathComponents(rootName, list) } @@ -94,6 +107,6 @@ public fun File.filePathComponents(): FilePathComponents { * number count-1 belongs to a component farthest from the root * @throws IllegalArgumentException if [beginIndex] is negative, * or [endIndex] is greater than existing number of components, -* or [beginIndex] is greater than or equals to [endIndex] +* or [beginIndex] is greater than [endIndex] */ public fun File.subPath(beginIndex: Int, endIndex: Int): File = filePathComponents().subPath(beginIndex, endIndex) diff --git a/libraries/stdlib/src/kotlin/io/files/FileTreeWalk.kt b/libraries/stdlib/src/kotlin/io/files/FileTreeWalk.kt index 0a7d1a9013e..f7d77124e85 100644 --- a/libraries/stdlib/src/kotlin/io/files/FileTreeWalk.kt +++ b/libraries/stdlib/src/kotlin/io/files/FileTreeWalk.kt @@ -8,9 +8,9 @@ import java.util.NoSuchElementException import java.util.Stack /** - * Created by Mikhail.Glukhikh on 13/03/2015. + * Created by Mikhail Glukhikh on 13/03/2015. * - * An alternative version of file walker + * An implementation of file walker */ /** @@ -31,6 +31,15 @@ public enum class FileWalkDirection { * It allows to iterate through all files inside [start] directory. * If [start] is just a file, walker iterates only it. * If [start] does not exist, walker does not do any iterations at all. + * + * @param start directory to walk into + * @param direction selects top-down or bottom-up order (in other words, parents first or children first). + * @param enter is called on any entered directory before its files are visited and before it is visited itself + * @param leave is called on any left directory after its files are visited and after it is visited itself + * @param fail is called on a directory when it's impossible to get its file list + * @param filter is called just before visiting a file, and if false is returned, file is not visited + * @param maxDepth is maximum walking depth, it must be positive. With a value of 1, + * walker visits [start] and all its children, with a value of 2 also grandchildren, etc. */ public class FileTreeWalk(private val start: File, private val direction: FileWalkDirection = FileWalkDirection.TOP_DOWN, @@ -41,15 +50,18 @@ public class FileTreeWalk(private val start: File, private val maxDepth: Int = Int.MAX_VALUE ) : Sequence { + /** Abstract class that encapsulates file visiting in some order, beginning from a given [rootDir] */ private abstract class DirectoryState(public val rootDir: File) { init { if (!rootDir.isDirectory()) throw IllegalArgumentException("Directory is needed") } + /** Call of this function proceeds to a next file for visiting and returns it */ abstract public fun step(): File? } + /** Visiting in bottom-up order */ private inner class BottomUpDirectoryState(rootDir: File) : DirectoryState(rootDir) { private var rootVisited = false @@ -60,6 +72,7 @@ public class FileTreeWalk(private val start: File, private var failed = false + /** First all children, then root directory */ override public fun step(): File? { if (!failed && fileList == null) { enter(rootDir) @@ -84,6 +97,7 @@ public class FileTreeWalk(private val start: File, } } + /** Visiting in top-down order */ private inner class TopDownDirectoryState(rootDir: File) : DirectoryState(rootDir) { private var rootVisited = false @@ -92,6 +106,7 @@ public class FileTreeWalk(private val start: File, private var fileIndex = 0 + /** First root directory, then all children */ override public fun step(): File? { if (!rootVisited) { // First visit root @@ -167,10 +182,10 @@ public class FileTreeWalk(private val start: File, if (!filter(file)) return gotoNext() if (file == topState.rootDir || !file.isDirectory() || state.size() >= maxDepth) { - // Proceed a root directory or a simple file + // Proceed to a root directory or a simple file return file } else { - // Proceed a sub-directory + // Proceed to a sub-directory pushState(file) return gotoNext() } @@ -178,42 +193,42 @@ public class FileTreeWalk(private val start: File, } /** - * Sets enter directory function. - * Enter function is called BEFORE the corresponding directory and its files are visited + * Sets enter directory [function]. + * Enter [function] is called BEFORE the corresponding directory and its files are visited */ - public fun enter(f: (File) -> Unit): FileTreeWalk { - return FileTreeWalk(start, direction, f, leave, fail, filter, maxDepth) + public fun enter(function: (File) -> Unit): FileTreeWalk { + return FileTreeWalk(start, direction, function, leave, fail, filter, maxDepth) } /** - * Sets leave directory function. - * Leave function is called AFTER the corresponding directory and its files are visited + * Sets leave directory [function]. + * Leave [function] is called AFTER the corresponding directory and its files are visited */ - public fun leave(f: (File) -> Unit): FileTreeWalk { - return FileTreeWalk(start, direction, enter, f, fail, filter, maxDepth) + public fun leave(function: (File) -> Unit): FileTreeWalk { + return FileTreeWalk(start, direction, enter, function, fail, filter, maxDepth) } /** - * Set fail entering directory function. - * Fail function is called when walker is unable to get list of directory files. + * Set fail entering directory [function]. + * Fail [function] is called when walker is unable to get list of directory files. * Enter and leave functions are called even in this case. */ - public fun fail(f: (File, IOException) -> Unit): FileTreeWalk { - return FileTreeWalk(start, direction, enter, leave, f, filter, maxDepth) + public fun fail(function: (File, IOException) -> Unit): FileTreeWalk { + return FileTreeWalk(start, direction, enter, leave, function, filter, maxDepth) } /** - * Sets filter function. - * Filter function is called before visiting files and entering directories. + * Sets filter [predicate]. + * Filter [predicate] function is called before visiting files and entering directories. * If it returns false, file is not visited, directory is not entered and all its content is also not visited. * If it returns true, everything goes in the regular way */ - public fun filter(f: (File) -> Boolean): FileTreeWalk { - return FileTreeWalk(start, direction, enter, leave, fail, f, maxDepth) + public fun filter(predicate: (File) -> Boolean): FileTreeWalk { + return FileTreeWalk(start, direction, enter, leave, fail, predicate, maxDepth) } /** - * Sets maximum depth of walk. Int.MAX_VALUE is used for unlimited. + * Sets maximum [depth] of walk. Int.MAX_VALUE is used for unlimited. * Negative and zero values are not allowed */ public fun maxDepth(depth: Int): FileTreeWalk { @@ -222,6 +237,7 @@ public class FileTreeWalk(private val start: File, return FileTreeWalk(start, direction, enter, leave, fail, filter, depth) } + /** An iterator associated with this walker */ private val it = object : Iterator { override public fun hasNext(): Boolean { if (nextFile == null) @@ -242,6 +258,7 @@ public class FileTreeWalk(private val start: File, } } + /** Returns an associated file iterator */ override public fun iterator(): Iterator { return it } @@ -271,9 +288,9 @@ public fun File.walkBottomUp(): FileTreeWalk = walk(FileWalkDirection.BOTTOM_UP) * Recursively process this file and all children with the given block. * Note that if this file doesn't exist, then the block will be executed on it anyway. * - * @param block the function to call on each file + * @param function the function to call on each file */ deprecated("It's recommended to use walkTopDown() / walkBottomUp()") -public fun File.recurse(block: (File) -> Unit): Unit { - walkTopDown().forEach { block(it) } +public fun File.recurse(function: (File) -> Unit): Unit { + walkTopDown().forEach { function(it) } } diff --git a/libraries/stdlib/src/kotlin/io/files/Utils.kt b/libraries/stdlib/src/kotlin/io/files/Utils.kt index 40c7483a019..2bfc236a600 100644 --- a/libraries/stdlib/src/kotlin/io/files/Utils.kt +++ b/libraries/stdlib/src/kotlin/io/files/Utils.kt @@ -22,7 +22,7 @@ public fun createTempDir(prefix: String = "tmp", suffix: String? = null, directo if (dir.mkdir()) { return dir } else { - throw IOException("Unable to create temporary directory") + throw IOException("Unable to create temporary directory $dir") } } @@ -76,9 +76,7 @@ public val File.path: String * Returns the extension of this file (not including the dot), or an empty string if it doesn't have one. */ public val File.extension: String - get() { - return name.substringAfterLast('.', "") - } + get() = name.substringAfterLast('.', "") /** * Replaces all separators in the string used to separate directories with system ones and returns the resulting string. @@ -143,28 +141,28 @@ public fun File.relativeTo(base: File): String { // Check roots val components = filePathComponents() val baseComponents = base.filePathComponents() - if (components.rootName != base.rootName) - throw IllegalArgumentException("this and base files have different roots") + if (components.rootName != baseComponents.rootName) + throw IllegalArgumentException("this and base files have different roots: ${components.rootName} and ${baseComponents.rootName}") var i = 0 - while (i < components.size && i < baseComponents.size && components.fileList[i] == baseComponents.fileList[i]) + while (i < components.size() && i < baseComponents.size() && components.fileList[i] == baseComponents.fileList[i]) i++ val sameCount = i - val baseCount = baseComponents.size + val baseCount = baseComponents.size() // Add all .. - var res = "" - for (j in sameCount + 1..baseCount - 1) - res += (".." + File.separator) + val res = StringBuilder() + for (j in sameCount..baseCount - 2) + res.append("..").append(File.separator) // If .. is the last element, no separator should present if (baseCount > sameCount) { - res += if (sameCount < components.size) ".." + File.separator else ".." + res.append(if (sameCount < components.size()) ".." + File.separator else "..") } // Add remaining this components - if (sameCount < components.size - 1) - res += (components.subPath(sameCount, components.size - 1).toString() + File.separator) + if (sameCount < components.size() - 1) + res.append(components.subPath(sameCount, components.size() - 1)).append(File.separator) // The last one should be without separator - if (sameCount < components.size) - res += components.subPath(components.size - 1, components.size).toString() - return res + if (sameCount < components.size()) + res.append(components.subPath(components.size() - 1, components.size())) + return res.toString() } /** @@ -191,8 +189,8 @@ public fun File.relativePath(descendant: File): String { * Copies this file to the given output [dst], returning the number of bytes copied. * * If some directories on a way to the [dst] are missing, then they will be created. - * If the [dst] file already exists, then this function will fail unless [overwrite] argument is set to true and - * the [dst] file is not a non-empty directory. + * If the [dst] file already exists, then this function will fail unless [overwrite] argument is set to true. + * Otherwise this file overwrites [dst] if it's a file to, or is written into [dst] if it's a directory. * * Note: this function fails if you call it on a directory. * If you want to copy directories, use 'copyRecursively' function instead. @@ -208,7 +206,7 @@ public fun File.copyTo(dst: File, overwrite: Boolean = false, bufferSize: Int = if (!exists()) { throw NoSuchFileException(file = this, reason = "The source file doesn't exist") } else if (isDirectory()) { - throw IllegalArgumentException("Use copyRecursively to copy a directory") + throw IllegalArgumentException("Use copyRecursively to copy a directory $this") } else if (dst.exists()) { if (!overwrite) { throw FileAlreadyExistsException(file = this, @@ -243,6 +241,7 @@ public enum class OnErrorAction { TERMINATE } +/** Private exception class, used to terminate recursive copying */ private class TerminateException(file: File) : FileSystemException(file) {} /** @@ -265,13 +264,14 @@ private class TerminateException(file: File) : FileSystemException(file) {} */ public fun File.copyRecursively(dst: File, onError: (File, IOException) -> OnErrorAction = - { file, e -> throw e } + { file, exception -> throw exception } ): Boolean { if (!exists()) { return onError(this, NoSuchFileException(file = this, reason = "The source file doesn't exist")) != OnErrorAction.TERMINATE } try { + // We cannot break for loop from inside a lambda, so we have to use an exception here for (src in walkTopDown().fail { f, e -> if (onError(f, e) == OnErrorAction.TERMINATE) throw TerminateException(f) }) { if (!src.exists()) { if (onError(src, NoSuchFileException(file = src, reason = "The source file doesn't exist")) == @@ -307,86 +307,68 @@ public fun File.copyRecursively(dst: File, * * @return true if the file or directory is successfully deleted, false otherwise. */ -public fun File.deleteRecursively(): Boolean { - var result = exists() - walkBottomUp().forEach { if (!it.delete()) result = false } - return result -} +public fun File.deleteRecursively(): Boolean = walkBottomUp().fold(exists(), { res, it -> res && it.delete() }) /** * Returns an array of files and directories in the directory that match the specified [filter] * or null if this file does not denote a directory. */ -public fun File.listFiles(filter: (file: File) -> Boolean): Array? = listFiles( - object : FileFilter { - override fun accept(file: File) = filter(file) - } -) +public fun File.listFiles(filter: (file: File) -> Boolean): Array? = listFiles(FileFilter(filter)) /** - * Determines whether this file belongs to the same root as [o] - * and starts with all components of [o] in the same order. - * So if [o] has N components, first N components of `this` must be the same as in [o]. - * For relative [o], `this` can belong to any root. + * Determines whether this file belongs to the same root as [other] + * and starts with all components of [other] in the same order. + * So if [other] has N components, first N components of `this` must be the same as in [other]. + * For relative [other], `this` can belong to any root. * - * @return true if this path starts with [o] path, false otherwise + * @return true if this path starts with [other] path, false otherwise */ -public fun File.startsWith(o: File): Boolean { +public fun File.startsWith(other: File): Boolean { val components = filePathComponents() - val otherComponents = o.filePathComponents() + val otherComponents = other.filePathComponents() if (components.rootName != otherComponents.rootName && otherComponents.rootName != "") return false - if (components.size < otherComponents.size) - return false - for (i in 0..otherComponents.size - 1) { - if (components.fileList[i] != otherComponents.fileList[i]) - return false - } - return true + return if (components.size() < otherComponents.size()) false + else components.fileList.subList(0, otherComponents.size()).equals(otherComponents.fileList) } /** - * Determines whether this file belongs to the same root as [o] - * and starts with all components of [o] in the same order. - * So if [o] has N components, first N components of `this` must be the same as in [o]. - * For relative [o], `this` can belong to any root. + * Determines whether this file belongs to the same root as [other] + * and starts with all components of [other] in the same order. + * So if [other] has N components, first N components of `this` must be the same as in [other]. + * For relative [other], `this` can belong to any root. * - * @return true if this path starts with [o] path, false otherwise + * @return true if this path starts with [other] path, false otherwise */ -public fun File.startsWith(o: String): Boolean = startsWith(File(o)) +public fun File.startsWith(other: String): Boolean = startsWith(File(other)) /** - * Determines whether this file belongs to the same root as [o] - * and ends with all components of [o] in the same order. - * So if [o] has N components, last N components of `this` must be the same as in [o]. - * For relative [o], `this` can belong to any root. + * Determines whether this file belongs to the same root as [other] + * and ends with all components of [other] in the same order. + * So if [other] has N components, last N components of `this` must be the same as in [other]. + * For relative [other], `this` can belong to any root. * - * @return true if this path ends with [o] path, false otherwise + * @return true if this path ends with [other] path, false otherwise */ -public fun File.endsWith(o: File): Boolean { +public fun File.endsWith(other: File): Boolean { val components = filePathComponents() - val otherComponents = o.filePathComponents() + val otherComponents = other.filePathComponents() if (components.rootName != otherComponents.rootName && otherComponents.rootName != "") return false - val shift = components.size - otherComponents.size - if (shift < 0) - return false - for (i in 0..otherComponents.size - 1) { - if (components.fileList[i + shift] != otherComponents.fileList[i]) - return false - } - return true + val shift = components.size() - otherComponents.size() + return if (shift < 0) false + else components.fileList.subList(shift, components.size()).equals(otherComponents.fileList) } /** - * Determines whether this file belongs to the same root as [o] - * and ends with all components of [o] in the same order. - * So if [o] has N components, last N components of `this` must be the same as in [o]. - * For relative [o], `this` can belong to any root. + * Determines whether this file belongs to the same root as [other] + * and ends with all components of [other] in the same order. + * So if [other] has N components, last N components of `this` must be the same as in [other]. + * For relative [other], `this` can belong to any root. * - * @return true if this path ends with [o] path, false otherwise + * @return true if this path ends with [other] path, false otherwise */ -public fun File.endsWith(o: String): Boolean = endsWith(File(o)) +public fun File.endsWith(other: String): Boolean = endsWith(File(other)) /** * Removes all . and resolves all possible .. in this file name. @@ -397,77 +379,62 @@ public fun File.endsWith(o: String): Boolean = endsWith(File(o)) public fun File.normalize(): File { val components = filePathComponents() val rootName = components.rootName - val list: MutableList = components.fileList.filter { it.toString() != "." }.map { it.toString() }.toLinkedList() - var first = 0 - var dots = list.subList(first, list.size()).indexOf("..") - while (dots != -1) { - if (dots > 0) { - list.remove(dots + first) - list.remove(dots + first - 1) - } else { - first++ + val list: MutableList = ArrayList() + for (file in components.fileList) { + val name = file.toString() + when (name) { + "." -> { + } + ".." -> if (!list.isEmpty() && list.get(list.size() - 1) != "..") list.remove(list.size() - 1) else list.add(name) + else -> list.add(name) } - dots = list.subList(first, list.size()).indexOf("..") } - var res = rootName - var addSeparator = rootName != "" && !rootName.endsWith(File.separatorChar) - for (elem in list) { - if (addSeparator) - res += File.separatorChar - res += elem - addSeparator = true - } - return File(res) + return File(list.joinToString(File.separator, rootName)) } /** - * Adds relative [o] to this, considering this as a directory. - * If [o] has a root, [o] is returned back. + * Adds [relative] file to this, considering this as a directory. + * If [relative] has a root, [relative] is returned back. * For instance, File("/foo/bar").resolve(File("gav")) is File("/foo/bar/gav"). * This function is complementary with (File.relativeTo), * so f.resolve(g.relativeTo(f)) == g should be always true except for different roots case. * - * @return concatenated this and [o] paths, or just [o] if it's absolute + * @return concatenated this and [relative] paths, or just [relative] if it's absolute */ -public fun File.resolve(o: File): File { - if (o.root != null) - return o +public fun File.resolve(relative: File): File { + if (relative.root != null) + return relative val ourName = toString() - return if (ourName.endsWith(File.separatorChar)) File(ourName + o) else File(ourName + File.separatorChar + o) + return if (ourName.endsWith(File.separatorChar)) File(ourName + relative) else File(ourName + File.separatorChar + relative) } /** - * Adds relative [o] to this, considering this as a directory. - * If [o] has a root, [o] is returned back. + * Adds [relative] name to this, considering this as a directory. + * If [relative] has a root, [relative] is returned back. * For instance, File("/foo/bar").resolve("gav") is File("/foo/bar/gav") * - * @return concatenated this and [o] paths, or just [o] if it's absolute + * @return concatenated this and [relative] paths, or just [relative] if it's absolute */ -public fun File.resolve(o: String): File = resolve(File(o)) +public fun File.resolve(relative: String): File = resolve(File(relative)) /** - * Adds relative [o] to this parent directory. - * If [o] has a root or this has no parent directory, [o] is returned back. + * Adds [relative] file to this parent directory. + * If [relative] has a root or this has no parent directory, [relative] is returned back. * For instance, File("/foo/bar").resolveSibling(File("gav")) is File("/foo/gav") * - * @return concatenated this.parent and [o] paths, or just [o] if it's absolute or this has no parent + * @return concatenated this.parent and [relative] paths, or just [relative] if it's absolute or this has no parent */ -public fun File.resolveSibling(o: File): File { +public fun File.resolveSibling(relative: File): File { val components = filePathComponents() val rootName = components.rootName - val parentFile = when (components.size) { - 0 -> null - 1 -> File(rootName) - else -> File(rootName + components.subPath(0, components.size - 1).path) - } - return if (parentFile != null) parentFile.resolve(o) else o + return if (components.size() == 0) relative else File(rootName).resolve(components.subPath(0, components.size() - 1)).resolve(relative) } /** - * Adds relative [o] to this parent directory. - * If [o] has a root or this has no parent directory, [o] is returned back + * Adds [relative] name to this parent directory. + * If [relative] has a root or this has no parent directory, [relative] is returned back * For instance, File("/foo/bar").resolveSibling("gav") is File("/foo/gav") * - * @return concatenated this.parent and [o] paths, or just [o] if it's absolute or this has no parent + * @return concatenated this.parent and [relative] paths, or just [relative] if it's absolute or this has no parent */ -public fun File.resolveSibling(o: String): File = resolveSibling(File(o)) +public fun File.resolveSibling(relative: String): File = resolveSibling(File(relative)) diff --git a/libraries/stdlib/test/io/Files.kt b/libraries/stdlib/test/io/Files.kt index 0e4b93e42c3..6e1ff70c135 100644 --- a/libraries/stdlib/test/io/Files.kt +++ b/libraries/stdlib/test/io/Files.kt @@ -733,21 +733,22 @@ class FilesTest { test fun testCopyTo() { val srcFile = createTempFile() val dstFile = createTempFile() - srcFile.replaceText("Hello, World!") + srcFile.writeText("Hello, World!", "UTF8") try { srcFile.copyTo(dstFile) assert(false) } catch (e: FileAlreadyExistsException) { + println(e.getMessage()) } var len = srcFile.copyTo(dstFile, overwrite = true) assertEquals(13L, len) - assertEquals(srcFile.readText(), dstFile.readText()) + assertEquals(srcFile.readText(), dstFile.readText("UTF8")) assert(dstFile.delete()) len = srcFile.copyTo(dstFile) assertEquals(13L, len) - assertEquals(srcFile.readText(), dstFile.readText()) + assertEquals(srcFile.readText("UTF8"), dstFile.readText()) assert(dstFile.delete()) dstFile.mkdir() @@ -811,8 +812,8 @@ class FilesTest { createTempDir(prefix = "d1_", directory = subDir1) val file1 = createTempFile(prefix = "f1_", directory = src) val file2 = createTempFile(prefix = "f2_", directory = subDir1) - file1.replaceText("hello") - file2.replaceText("wazzup") + file1.writeText("hello") + file2.writeText("wazzup") createTempDir(prefix = "d1_", directory = subDir2) assert(src.copyRecursively(dst)) diff --git a/libraries/stdlib/test/io/ReadWrite.kt b/libraries/stdlib/test/io/ReadWrite.kt index da19e384eda..1d9d5ee03a3 100644 --- a/libraries/stdlib/test/io/ReadWrite.kt +++ b/libraries/stdlib/test/io/ReadWrite.kt @@ -15,10 +15,12 @@ fun sample(): Reader = StringReader("Hello\nWorld"); class ReadWriteTest { test fun testAppendText() { val file = File.createTempFile("temp", System.nanoTime().toString()) - file.replaceText("Hello\n") - file.appendText("World") + file.writeText("Hello\n", "UTF8") + file.appendText("World\n", "UTF8") + file.appendText("Again") - assertEquals("Hello\nWorld", file.readText()) + assertEquals("Hello\nWorld\nAgain", file.readText()) + assertEquals(listOf("Hello", "World", "Again"), file.readLines("UTF8")) file.deleteOnExit() } @@ -71,34 +73,34 @@ class ReadWriteTest { writer.close() //file.replaceText("Hello\nWorld") - file.forEachBlock {(arr: ByteArray, size: Int) -> + file.forEachBlock { arr: ByteArray, size: Int -> assertTrue(size >= 11 && size <= 12, size.toString()) assertTrue(arr.contains('W'.toByte())) } val list = ArrayList() - file.forEachLine { + file.forEachLine("UTF8", { list.add(it) - } + }) assertEquals(arrayListOf("Hello", "World"), list) val text = file.inputStream().reader().readText() assertTrue(text.contains("Hello")) assertTrue(text.contains("World")) - file.replaceText("") + file.writeText("") var c = 0 file.forEachLine { c++ } assertEquals(0, c) - file.replaceText(" ") + file.writeText(" ") file.forEachLine { c++ } assertEquals(1, c) - file.replaceText(" \n") + file.writeText(" \n") c = 0 file.forEachLine { c++ } assertEquals(1, c) - file.replaceText(" \n ") + file.writeText(" \n ") c = 0 file.forEachLine { c++ } assertEquals(2, c) @@ -167,5 +169,7 @@ class ReadWriteTest { val url = URL("http://kotlinlang.org") val text = url.readText() assertFalse(text.isEmpty()) + val text2 = url.readText("UTF8") + assertFalse(text2.isEmpty()) } }