Last active
May 30, 2018 15:20
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] = ' ' | |
// } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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