blob: 3223dd47683fa8fc032282637c46c1511ae9b050 [file] [log] [blame]
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.serialization
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Test
import java.io.PrintWriter
import java.io.Reader
import java.io.StringReader
import java.io.StringWriter
import kotlin.reflect.KClass
class SerializeZooTest {
@Test
fun testZoo() {
// save to string
val sw = StringWriter()
val out = KeyValueOutput(PrintWriter(sw))
out.write(Zoo, zoo)
// load from string
val str = sw.toString()
val inp = KeyValueInput(Parser(StringReader(str)))
val other = inp.read(Zoo)
// assert we've got it back from string
assertEquals(zoo, other)
assertFalse(zoo === other)
}
// Test data -- Zoo of types
enum class Attitude { POSITIVE, NEUTRAL, NEGATIVE }
@Serializable
data class IntData(val intV: Int)
@Serializable
data class Tree(val name: String, val left: Tree? = null, val right: Tree? = null)
@Serializable
data class Zoo(
val unit: Unit,
val boolean: Boolean,
val byte: Byte,
val short: Short,
val int: Int,
val long: Long,
val float: Float,
val double: Double,
val char: Char,
val string: String,
val enum: Attitude,
val intData: IntData,
val unitN: Unit?,
val booleanN: Boolean?,
val byteN: Byte?,
val shortN: Short?,
val intN: Int?,
val longN: Long?,
val floatN: Float?,
val doubleN: Double?,
val charN: Char?,
val stringN: String?,
val enumN: Attitude?,
val intDataN: IntData?,
val listInt: List<Int>,
val listIntN: List<Int?>,
val listNInt: List<Int>?,
val listNIntN: List<Int?>?,
val listListEnumN: List<List<Attitude?>>,
val listIntData: List<IntData>,
val listIntDataN: List<IntData?>,
val tree: Tree,
val mapStringInt: Map<String,Int>,
val mapIntStringN: Map<Int,String?>,
val arrays: ZooWithArrays
)
@Serializable data class ZooWithArrays(
val arrByte: Array<Byte>,
val arrInt: Array<Int>,
val arrIntN: Array<Int?>,
val arrIntData: Array<IntData>
) {
override fun equals(o: Any?) = o is ZooWithArrays &&
arrByte.contentEquals(o.arrByte) &&
arrInt.contentEquals(o.arrInt) &&
arrIntN.contentEquals(o.arrIntN) &&
arrIntData.contentEquals(o.arrIntData)
}
val zoo = Zoo(
Unit, true, 10, 20, 30, 40, 50f, 60.0, 'A', "Str0", Attitude.POSITIVE, IntData(70),
null, null, 11, 21, 31, 41, 51f, 61.0, 'B', "Str1", Attitude.NEUTRAL, null,
listOf(1, 2, 3),
listOf(4, 5, null),
listOf(6, 7, 8),
listOf(null, 9, 10),
listOf(listOf(Attitude.NEGATIVE, null)),
listOf(IntData(1), IntData(2), IntData(3)),
listOf(IntData(1), null, IntData(3)),
Tree("root", Tree("left"), Tree("right", Tree("right.left"), Tree("right.right"))),
mapOf("one" to 1, "two" to 2, "three" to 3),
mapOf(0 to null, 1 to "first", 2 to "second"),
ZooWithArrays(
arrayOf(1, 2, 3),
arrayOf(100, 200, 300),
arrayOf(null, -1, -2),
arrayOf(IntData(1), IntData(2))
)
)
// KeyValue Input/Output
class KeyValueOutput(val out: PrintWriter) : ElementValueOutput() {
override fun writeBegin(desc: KSerialClassDesc, vararg typeParams: KSerializer<*>): KOutput {
out.print('{')
return this
}
override fun writeEnd(desc: KSerialClassDesc) = out.print('}')
override fun writeElement(desc: KSerialClassDesc, index: Int): Boolean {
if (index > 0) out.print(", ")
out.print(desc.getElementName(index));
out.print(':')
return true
}
override fun writeNullValue() = out.print("null")
override fun writeValue(value: Any) = out.print(value)
override fun writeStringValue(value: String) {
out.print('"')
out.print(value)
out.print('"')
}
override fun writeCharValue(value: Char) = writeStringValue(value.toString())
}
class KeyValueInput(val inp: Parser) : ElementValueInput() {
override fun readBegin(desc: KSerialClassDesc, vararg typeParams: KSerializer<*>): KInput {
inp.expectAfterWhiteSpace('{')
return this
}
override fun readEnd(desc: KSerialClassDesc) = inp.expectAfterWhiteSpace('}')
override fun readElement(desc: KSerialClassDesc): Int {
inp.skipWhitespace(',')
val name = inp.nextUntil(':', '}')
if (name.isEmpty())
return READ_DONE
val index = desc.getElementIndex(name)
inp.expect(':')
return index
}
private fun readToken(): String {
inp.skipWhitespace()
return inp.nextUntil(' ', ',', '}')
}
override fun readNotNullMark(): Boolean {
inp.skipWhitespace()
if (inp.cur != 'n'.toInt()) return true
return false
}
override fun readNullValue(): Nothing? {
check(readToken() == "null") { "'null' expected" }
return null
}
override fun readBooleanValue(): Boolean = readToken().toBoolean()
override fun readByteValue(): Byte = readToken().toByte()
override fun readShortValue(): Short = readToken().toShort()
override fun readIntValue(): Int = readToken().toInt()
override fun readLongValue(): Long = readToken().toLong()
override fun readFloatValue(): Float = readToken().toFloat()
override fun readDoubleValue(): Double = readToken().toDouble()
override fun <T : Enum<T>> readEnumValue(enumClass: KClass<T>): T {
return java.lang.Enum.valueOf(enumClass.java, readToken())
}
override fun readStringValue(): String {
inp.expectAfterWhiteSpace('"')
val value = inp.nextUntil('"')
inp.expect('"')
return value
}
override fun readCharValue(): Char = readStringValue().single()
}
// Parser
// Very simple char-by-char parser
class Parser(private val inp: Reader) {
var cur: Int = inp.read()
fun next() {
cur = inp.read()
}
fun skipWhitespace(vararg c: Char) {
while (cur >= 0 && (cur.toChar().isWhitespace() || cur.toChar() in c))
next()
}
fun expect(c: Char) {
check(cur == c.toInt()) { "Expected '$c'" }
next()
}
fun expectAfterWhiteSpace(c: Char) {
skipWhitespace()
expect(c)
}
fun nextUntil(vararg c: Char): String {
val sb = StringBuilder()
while (cur >= 0 && cur.toChar() !in c) {
sb.append(cur.toChar())
next()
}
return sb.toString()
}
}
}