IO second review: empty File.subPath() allowed, OutputStream.bufferedWriter() added,
normalize(), startsWith(), endsWith() rewritten, LinesStream --> LinesSequence, File.replaceBytes() / replaceText() rolled back to writeBytes() / writeText(), some exception messages formulated more clearly, refactorings, comments, relativeTo() slightly optimized, additional tests
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
val x = LinesStream<caret>
|
||||
val x = LinesSequence<caret>
|
||||
|
||||
// INVOCATION_COUNT: 2
|
||||
// EXIST: { lookupString:"LinesStream", tailText:" (kotlin.io)" }
|
||||
// EXIST: { lookupString:"LinesSequence", tailText:" (kotlin.io)" }
|
||||
// NUMBER: 1
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<String> = 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 <T> Reader.useLines(block: (Sequence<String>) -> T): T =
|
||||
*
|
||||
* @return a sequence of corresponding file lines
|
||||
*/
|
||||
public fun BufferedReader.lines(): Sequence<String> = LinesStream(this)
|
||||
public fun BufferedReader.lines(): Sequence<String> = LinesSequence(this)
|
||||
|
||||
deprecated("Use lines() function which returns Sequence<String>")
|
||||
public fun BufferedReader.lineIterator(): Iterator<String> = lines().iterator()
|
||||
|
||||
private class LinesStream(private val reader: BufferedReader) : Sequence<String> {
|
||||
private class LinesSequence(private val reader: BufferedReader) : Sequence<String> {
|
||||
override public fun iterator(): Iterator<String> {
|
||||
return object : Iterator<String> {
|
||||
private var nextValue: String? = null
|
||||
@@ -312,7 +282,7 @@ private class LinesStream(private val reader: BufferedReader) : Sequence<String>
|
||||
/**
|
||||
* 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<InputStream, ByteArray>{ 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 : Closeable, R> T.use(block: (T) -> R): R {
|
||||
|
||||
@@ -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<File>) {
|
||||
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)
|
||||
|
||||
@@ -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<File> {
|
||||
|
||||
/** 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<File> {
|
||||
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<File> {
|
||||
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) }
|
||||
}
|
||||
|
||||
@@ -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<File>? = listFiles(
|
||||
object : FileFilter {
|
||||
override fun accept(file: File) = filter(file)
|
||||
}
|
||||
)
|
||||
public fun File.listFiles(filter: (file: File) -> Boolean): Array<File>? = 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<String> = 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<String> = 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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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<String>()
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user