|
| 1 | +package kotlinx.serialization.json.internal |
| 2 | + |
| 3 | +import java.io.* |
| 4 | +import java.nio.* |
| 5 | +import java.nio.charset.* |
| 6 | + |
| 7 | +internal class CharsetReader( |
| 8 | + private val inputStream: InputStream, |
| 9 | + private val charset: Charset |
| 10 | +) { |
| 11 | + private val decoder: CharsetDecoder |
| 12 | + private val byteBuffer: ByteBuffer |
| 13 | + |
| 14 | + // Surrogate-handling in cases when a single char is requested, but two were read |
| 15 | + private var hasLeftoverPotentiallySurrogateChar = false |
| 16 | + private var leftoverChar = 0.toChar() |
| 17 | + |
| 18 | + init { |
| 19 | + decoder = charset.newDecoder() |
| 20 | + .onMalformedInput(CodingErrorAction.REPLACE) |
| 21 | + .onUnmappableCharacter(CodingErrorAction.REPLACE) |
| 22 | + byteBuffer = ByteBuffer.wrap(ByteArrayPool8k.take()) |
| 23 | + byteBuffer.flip() // Make empty |
| 24 | + } |
| 25 | + |
| 26 | + @Suppress("NAME_SHADOWING") |
| 27 | + fun read(array: CharArray, offset: Int, length: Int): Int { |
| 28 | + if (length == 0) return 0 |
| 29 | + require(offset in 0 until array.size && length >= 0 && offset + length <= array.size) { |
| 30 | + "Unexpected arguments: $offset, $length, ${array.size}" |
| 31 | + } |
| 32 | + |
| 33 | + var offset = offset |
| 34 | + var length = length |
| 35 | + var bytesRead = 0 |
| 36 | + if (hasLeftoverPotentiallySurrogateChar) { |
| 37 | + array[offset] = leftoverChar |
| 38 | + offset++ |
| 39 | + length-- |
| 40 | + hasLeftoverPotentiallySurrogateChar = false |
| 41 | + bytesRead = 1 |
| 42 | + if (length == 0) return bytesRead |
| 43 | + } |
| 44 | + if (length == 1) { |
| 45 | + // Treat single-character array reads just like read() |
| 46 | + val c = oneShotReadSlowPath() |
| 47 | + if (c == -1) return if (bytesRead == 0) -1 else bytesRead |
| 48 | + array[offset] = c.toChar() |
| 49 | + return bytesRead + 1 |
| 50 | + } |
| 51 | + return doRead(array, offset, length) + bytesRead |
| 52 | + } |
| 53 | + |
| 54 | + private fun doRead(array: CharArray, offset: Int, length: Int): Int { |
| 55 | + var charBuffer = CharBuffer.wrap(array, offset, length) |
| 56 | + if (charBuffer.position() != 0) { |
| 57 | + charBuffer = charBuffer.slice() |
| 58 | + } |
| 59 | + var isEof = false |
| 60 | + while (true) { |
| 61 | + val cr = decoder.decode(byteBuffer, charBuffer, isEof) |
| 62 | + if (cr.isUnderflow) { |
| 63 | + if (isEof) break |
| 64 | + if (!charBuffer.hasRemaining()) break |
| 65 | + val n = fillByteBuffer() |
| 66 | + if (n < 0) { |
| 67 | + isEof = true |
| 68 | + if (charBuffer.position() == 0 && !byteBuffer.hasRemaining()) break |
| 69 | + decoder.reset() |
| 70 | + } |
| 71 | + continue |
| 72 | + } |
| 73 | + if (cr.isOverflow) { |
| 74 | + assert(charBuffer.position() > 0) |
| 75 | + break |
| 76 | + } |
| 77 | + cr.throwException() |
| 78 | + } |
| 79 | + if (isEof) decoder.reset() |
| 80 | + return if (charBuffer.position() == 0) -1 |
| 81 | + else charBuffer.position() |
| 82 | + } |
| 83 | + |
| 84 | + private fun fillByteBuffer(): Int { |
| 85 | + byteBuffer.compact() |
| 86 | + try { |
| 87 | + // Read from the input stream, and then update the buffer |
| 88 | + val limit = byteBuffer.limit() |
| 89 | + val position = byteBuffer.position() |
| 90 | + val remaining = if (position <= limit) limit - position else 0 |
| 91 | + val bytesRead = inputStream.read(byteBuffer.array(), byteBuffer.arrayOffset() + position, remaining) |
| 92 | + if (bytesRead < 0) return bytesRead |
| 93 | + byteBuffer.position(position + bytesRead) |
| 94 | + } finally { |
| 95 | + byteBuffer.flip() |
| 96 | + } |
| 97 | + return byteBuffer.remaining() |
| 98 | + } |
| 99 | + |
| 100 | + private fun oneShotReadSlowPath(): Int { |
| 101 | + // Return the leftover char, if there is one |
| 102 | + if (hasLeftoverPotentiallySurrogateChar) { |
| 103 | + hasLeftoverPotentiallySurrogateChar = false |
| 104 | + return leftoverChar.code |
| 105 | + } |
| 106 | + |
| 107 | + val array = CharArray(2) |
| 108 | + val bytesRead = read(array, 0, 2) |
| 109 | + return when (bytesRead) { |
| 110 | + -1 -> -1 |
| 111 | + 1 -> array[0].code |
| 112 | + 2 -> { |
| 113 | + leftoverChar = array[1] |
| 114 | + hasLeftoverPotentiallySurrogateChar = true |
| 115 | + array[0].code |
| 116 | + } |
| 117 | + else -> error("Unreachable state: $bytesRead") |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + public fun release() { |
| 122 | + ByteArrayPool8k.release(byteBuffer.array()) |
| 123 | + } |
| 124 | +} |
0 commit comments