Skip to content

Instantly share code, notes, and snippets.

@dangets
Last active May 30, 2018 15:20
Show Gist options
  • Save dangets/43f80cb64c3d4d0ce79937ec20cba169 to your computer and use it in GitHub Desktop.
Save dangets/43f80cb64c3d4d0ce79937ec20cba169 to your computer and use it in GitHub Desktop.
java.io.Reader that will drop n lines at end of other reader
package com.dangets
import java.io.IOException
import java.io.Reader
import kotlin.math.max
import kotlin.math.min
/**
* Create a Reader that will drop the last 'numLines' of input.
* This is useful for e.g. when you want to skip the last n lines of footers
* Uses a buffer internally that will auto resize if the input lines are too large.
*/
fun Reader.dropLastLines(numLines: Int, initialCapacity: Int = DropLastLinesReader.INITIAL_CAPACITY): Reader {
if (numLines == 0)
return this
return DropLastLinesReader(this, numLines, initialCapacity)
}
class DropLastLinesReader constructor(private val r: Reader, private val numLines: Int, initialCapacity: Int = DropLastLinesReader.INITIAL_CAPACITY) : Reader() {
companion object {
const val INITIAL_CAPACITY = 1024
}
private var cb = CharArray(max(initialCapacity, 32))
private var wPtr = 0 // < rPtr (on wrap)
private var readToPtr = 0 // < wPtr
private var rPtr = 0 // <= readToPtr
init {
require(numLines > 0) { "numLines must be > 0 (was $numLines)"}
}
override fun close() = r.close()
private fun resizeBuffer() {
val newSize = when {
cb.size < INITIAL_CAPACITY -> INITIAL_CAPACITY
INITIAL_CAPACITY % cb.size != 0 -> ((INITIAL_CAPACITY / cb.size) + 1) * INITIAL_CAPACITY
else -> cb.size + INITIAL_CAPACITY
}
val newCb = CharArray(newSize)
System.arraycopy(cb, rPtr, newCb, 0, wPtr - rPtr)
cb = newCb
wPtr -= rPtr
readToPtr -= rPtr
rPtr = 0
}
private fun shiftBuffer() {
System.arraycopy(cb, rPtr, cb, 0, wPtr - rPtr)
wPtr -= rPtr
readToPtr -= rPtr
rPtr = 0
}
private fun advanceReadToPtr(): Int {
assert(rPtr == readToPtr)
while (true) {
if (wPtr == cb.size) {
if (rPtr == 0) {
resizeBuffer()
} else {
shiftBuffer()
}
}
// --- advance wPtr ------------
val maxBytes = cb.size - wPtr
val numRead = r.read(cb, wPtr, maxBytes)
if (numRead == -1)
return -1
wPtr += numRead
// --- advance readToPtr -------
var i = wPtr - 2
var numNl = numLines
while (i > readToPtr) {
if (cb[i] == '\n') {
numNl -= 1
if (numNl == 0) {
readToPtr = i + 1
break
}
}
i -= 1
}
if (readToPtr > rPtr) {
return 1
}
}
}
override fun read(cbuf: CharArray, off: Int, len: Int): Int {
if (rPtr == readToPtr) {
val a = advanceReadToPtr()
if (a == -1)
return -1
}
val numBytes = min(len, readToPtr - rPtr)
System.arraycopy(cb, rPtr, cbuf, off, numBytes)
rPtr += numBytes
return numBytes
}
// private fun printBuffer() {
// fun printCharArray(a: CharArray) {
// a.map { if (it == '\n') '_' else it }.forEach { print(it) }
// println()
// }
//
// printCharArray(cb)
//
// val pb = CharArray(cb.size + 1)
// pb.fill(' ')
//
// pb[rPtr] = 'r'
// printCharArray(pb)
// pb[rPtr] = ' '
//
// pb[wPtr] = 'w'
// printCharArray(pb)
// pb[wPtr] = ' '
//
// pb[readToPtr] = 'u'
// printCharArray(pb)
// pb[readToPtr] = ' '
// }
}
package com.dangets
import org.junit.Test
import java.io.StringReader
import kotlin.test.assertEquals
class DropLastLinesReaderTest {
@Test
fun `drop 1 input 2`() {
val input = "" +
"0\n" +
"1\n"
StringReader(input).dropLastLines(1, 32).use { r ->
assertEquals('0', r.read().toChar())
assertEquals('\n', r.read().toChar())
assertEquals(-1, r.read())
}
}
@Test
fun `drop 1 input 2 noEndNewline`() {
val input = "" +
"0\n" +
"1"
StringReader(input).dropLastLines(1, 32).use { r ->
assertEquals('0', r.read().toChar())
assertEquals('\n', r.read().toChar())
assertEquals(-1, r.read())
}
}
@Test
fun `drop 1 input 1`() {
val input = "0\n"
StringReader(input).dropLastLines(1, 20).use { r ->
assertEquals(-1, r.read())
}
}
@Test
fun `drop 1 input 1 noEndNewline`() {
val input = "0"
StringReader(input).dropLastLines(1, 20).use { r ->
assertEquals(-1, r.read())
}
}
@Test
fun `drop 2 input 2`() {
val input = "" +
"0\n" +
"1"
StringReader(input).dropLastLines(2, 20).use { r ->
assertEquals(-1, r.read())
}
}
@Test
fun `bufferedReader drop 2 input 5`() {
val input = "" +
"0000000000\n" +
"1111111111\n" +
"2222222222\n" +
"3333333333\n" +
"4444444444\n"
StringReader(input).dropLastLines(2).buffered().use { r ->
assertEquals("0000000000", r.readLine())
assertEquals("1111111111", r.readLine())
assertEquals("2222222222", r.readLine())
assertEquals(null, r.readLine())
}
}
@Test
fun `bufferedReader drop 2 input 5 noEndNewline`() {
val input = "" +
"0000000000\n" +
"1111111111\n" +
"2222222222\n" +
"3333333333\n" +
"4444444444"
StringReader(input).dropLastLines(2).buffered().use { r ->
assertEquals("0000000000", r.readLine())
assertEquals("1111111111", r.readLine())
assertEquals("2222222222", r.readLine())
assertEquals(null, r.readLine())
}
}
@Test
fun `bufferedReader drop 2 input 5 smallBuff`() {
val input = "" +
"000000000\n" +
"111111111\n" +
"222222222\n" +
"333333333\n" +
"444444444\n"
StringReader(input).dropLastLines(2, 30).buffered().use { r ->
assertEquals("000000000", r.readLine())
assertEquals("111111111", r.readLine())
assertEquals("222222222", r.readLine())
assertEquals(null, r.readLine())
}
}
@Test
fun `resize buffer`() {
val input = "" +
"01234567890123456789012345678901234567890123456789\n" +
"01234567890123456789012345678901234567890123456789\n" +
"01234567890123456789012345678901234567890123456789\n"
StringReader(input).dropLastLines(1, 32).buffered().use { r ->
assertEquals("01234567890123456789012345678901234567890123456789", r.readLine())
assertEquals("01234567890123456789012345678901234567890123456789", r.readLine())
assertEquals(null, r.readLine())
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment