KStringReader.kt
package org.knio.core.io
import kotlinx.coroutines.sync.withLock
import org.knio.core.context.KnioContext
import org.knio.core.context.getKnioContext
import org.knio.core.lang.toCharBuffer
import java.io.IOException
import java.nio.CharBuffer
import kotlin.math.max
import kotlin.math.min
class KStringReader private constructor (
str: String,
context: KnioContext
): KReader(context) {
companion object {
suspend fun open(str: String): KStringReader {
return KStringReader(str, getKnioContext())
}
}
private var str: CharBuffer? = str.toCharBuffer()
private var mark = 0
/** Check to make sure that the stream has not been closed */
@Throws(IOException::class)
private fun ensureOpen() {
if (str == null) throw IOException("Stream closed")
}
@Throws(IOException::class)
override suspend fun read(b: CharBuffer): Int = lock.withLock {
ensureOpen()
read0(b)
}
private suspend fun read0(b: CharBuffer): Int {
val str = this.str!!
val read = minOf(b.remaining(), str.remaining())
if(read == 0) return -1
b.put(b.position(), str, str.position(), read)
str.position(str.position() + read)
b.position(b.position() + read)
return read
}
/**
* Skips the specified number of characters in the stream. Returns
* the number of characters that were skipped.
*
*
* The `ns` parameter may be negative, even though the
* `skip` method of the [Reader] superclass throws
* an exception in this case. Negative values of `ns` cause the
* stream to skip backwards. Negative return values indicate a skip
* backwards. It is not possible to skip backwards past the beginning of
* the string.
*
*
* If the entire string has been read or skipped, then this method has
* no effect and always returns 0.
*
* @exception IOException If an I/O error occurs
*/
@Throws(IOException::class)
override suspend fun skip(ns: Long): Long = lock.withLock {
ensureOpen()
return skip0(ns)
}
private suspend fun skip0(ns: Long): Long {
val str = this.str!!
val next = str.position()
val length = str.limit()
if (next >= length) return 0
// Bound skip by beginning and end of the source
var n = min((length - next).toDouble(), ns.toDouble()).toLong()
n = max(-next.toDouble(), n.toDouble()).toLong()
str.position((next + n).toInt())
return n
}
/**
* Tells whether this stream is ready to be read.
*
* @return True if the next read() is guaranteed not to block for input
*
* @exception IOException If the stream is closed
*/
@Throws(IOException::class)
override suspend fun ready(): Boolean = lock.withLock {
ensureOpen()
return true
}
/**
* Tells whether this stream supports the mark() operation, which it does.
*/
override suspend fun markSupported(): Boolean {
return true
}
/**
* Marks the present position in the stream. Subsequent calls to reset()
* will reposition the stream to this point.
*
* @param readLimit Limit on the number of characters that may be
* read while still preserving the mark. Because
* the stream's input comes from a string, there
* is no actual limit, so this argument must not
* be negative, but is otherwise ignored.
*
* @exception IllegalArgumentException If `readAheadLimit < 0`
* @exception IOException If an I/O error occurs
*/
@Throws(IOException::class)
override suspend fun mark(readLimit: Int) {
require(readLimit >= 0) { "Read-ahead limit < 0" }
lock.withLock(lock) {
ensureOpen()
mark = str!!.position()
}
}
/**
* Resets the stream to the most recent mark, or to the beginning of the
* string if it has never been marked.
*
* @exception IOException If an I/O error occurs
*/
@Throws(IOException::class)
override suspend fun reset():Unit = lock.withLock {
ensureOpen()
str!!.position(mark)
}
/**
* Closes the stream and releases any system resources associated with
* it. Once the stream has been closed, further read(),
* ready(), mark(), or reset() invocations will throw an IOException.
* Closing a previously closed stream has no effect. This method will block
* while there is another thread blocking on the reader.
*/
override suspend fun close() =lock.withLock {
str = null
}
}