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