blob: 1de897137242f0f56e89f20266b885c4aec981fd (
plain)
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
|
/*
* Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization.json.okio.internal
import kotlinx.serialization.json.internal.*
import okio.*
// Copied from kotlinx/serialization/json/internal/StringOps.kt
private fun toHexChar(i: Int) : Char {
val d = i and 0xf
return if (d < 10) (d + '0'.code).toChar()
else (d - 10 + 'a'.code).toChar()
}
// Copied from kotlinx/serialization/json/internal/StringOps.kt
private val ESCAPE_STRINGS: Array<String?> = arrayOfNulls<String>(93).apply {
for (c in 0..0x1f) {
val c1 = toHexChar(c shr 12)
val c2 = toHexChar(c shr 8)
val c3 = toHexChar(c shr 4)
val c4 = toHexChar(c)
this[c] = "\\u$c1$c2$c3$c4"
}
this['"'.code] = "\\\""
this['\\'.code] = "\\\\"
this['\t'.code] = "\\t"
this['\b'.code] = "\\b"
this['\n'.code] = "\\n"
this['\r'.code] = "\\r"
this[0x0c] = "\\f"
}
internal class JsonToOkioStreamWriter(private val sink: BufferedSink) : InternalJsonWriter {
override fun writeLong(value: Long) {
write(value.toString())
}
override fun writeChar(char: Char) {
sink.writeUtf8CodePoint(char.code)
}
override fun write(text: String) {
sink.writeUtf8(text)
}
override fun writeQuoted(text: String) {
sink.writeUtf8CodePoint('"'.code)
var lastPos = 0
for (i in text.indices) {
val c = text[i].code
if (c < ESCAPE_STRINGS.size && ESCAPE_STRINGS[c] != null) {
sink.writeUtf8(text, lastPos, i) // flush prev
sink.writeUtf8(ESCAPE_STRINGS[c]!!)
lastPos = i + 1
}
}
if (lastPos != 0) sink.writeUtf8(text, lastPos, text.length)
else sink.writeUtf8(text)
sink.writeUtf8CodePoint('"'.code)
}
override fun release() {
// no-op, see https://github.com/Kotlin/kotlinx.serialization/pull/1982#discussion_r915043700
}
}
// Max value for a code point placed in one Char
private const val SINGLE_CHAR_MAX_CODEPOINT = Char.MAX_VALUE.code
// Value added to the high UTF-16 surrogate after shifting
private const val HIGH_SURROGATE_HEADER = 0xd800 - (0x010000 ushr 10)
// Value added to the low UTF-16 surrogate after masking
private const val LOW_SURROGATE_HEADER = 0xdc00
internal class OkioSerialReader(private val source: BufferedSource): InternalJsonReader {
/*
A sequence of code points is read from UTF-8, some of it can take 2 characters.
In case the last code point requires 2 characters, and the array is already full, we buffer the second character
*/
private var bufferedChar: Char? = null
override fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int {
var i = 0
if (bufferedChar != null) {
buffer[bufferOffset + i] = bufferedChar!!
i++
bufferedChar = null
}
while (i < count && !source.exhausted()) {
val codePoint = source.readUtf8CodePoint()
if (codePoint <= SINGLE_CHAR_MAX_CODEPOINT) {
buffer[bufferOffset + i] = codePoint.toChar()
i++
} else {
// an example of working with surrogates is taken from okio library with minor changes, see https://github.com/square/okio
// UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
// UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits)
// Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
val upChar = ((codePoint ushr 10) + HIGH_SURROGATE_HEADER).toChar()
val lowChar = ((codePoint and 0x03ff) + LOW_SURROGATE_HEADER).toChar()
buffer[bufferOffset + i] = upChar
i++
if (i < count) {
buffer[bufferOffset + i] = lowChar
i++
} else {
// if char array is full - buffer lower surrogate
bufferedChar = lowChar
}
}
}
return if (i > 0) i else -1
}
}
|