All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.microsoft.thrifty.protocol.SimpleJsonProtocol.kt Maven / Gradle / Ivy

The newest version!
/*
 * Thrifty
 *
 * Copyright (c) Microsoft Corporation
 *
 * All rights reserved.
 *
 * 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
 *
 * THIS CODE IS PROVIDED ON AN  *AS IS* BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE,
 * FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
 *
 * See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
 */
package com.microsoft.thrifty.protocol

import com.microsoft.thrifty.internal.ProtocolException
import com.microsoft.thrifty.transport.Transport
import okio.Buffer
import okio.ByteString
import okio.IOException

/**
 * A protocol that maps Thrift data to idiomatic JSON.
 *
 * "Idiomatic" here means that structs map to JSON maps, with field names
 * for keys.  Field tags are not included, and precise type information is not
 * preserved.  For this reason, SimpleJsonProtocol *does not support round-
 * tripping* - it is write-only.
 *
 * Note that, as of the initial release, this Protocol does not guarantee
 * that all emitted data is strictly valid JSON.  In particular, map keys are
 * not guaranteed to to be strings.
 */
class SimpleJsonProtocol(transport: Transport?) : BaseProtocol(transport!!) {
    /**
     * Indicates how [binary][ByteString] data is serialized when
     * written as JSON.
     */
    enum class BinaryOutputMode {
        /**
         * Write binary data as a hex-encoded string.
         */
        HEX,

        /**
         * Write binary data as a base-64-encoded string.
         */
        BASE_64,

        /**
         * Write binary data using Unicode escape syntax.
         */
        UNICODE
    }

    private open class WriteContext {
        @Throws(IOException::class)
        open fun beforeWrite() {
        }

        @Throws(IOException::class)
        open fun onPop() {
            // Fine
        }
    }

    private inner class ListWriteContext : WriteContext() {
        private var hasWritten = false
        @Throws(IOException::class)
        override fun beforeWrite() {
            if (hasWritten) {
                transport.write(COMMA)
            } else {
                hasWritten = true
            }
        }
    }

    private inner class MapWriteContext : WriteContext() {
        private var hasWritten = false
        private var mode = MODE_KEY
        @Throws(IOException::class)
        override fun beforeWrite() {
            if (hasWritten) {
                if (mode == MODE_KEY) {
                    transport.write(COMMA)
                } else {
                    transport.write(COLON)
                }
            } else {
                hasWritten = true
            }
            mode = !mode
        }

        @Throws(IOException::class)
        override fun onPop() {
            if (mode == MODE_VALUE) {
                throw ProtocolException("Incomplete JSON map, expected a value")
            }
        }
    }

    companion object {
        private const val MODE_KEY = false
        private const val MODE_VALUE = true
        
        private val ESCAPES: Array = arrayOfNulls(128)
        private val TRUE = byteArrayOf('t'.code.toByte(), 'r'.code.toByte(), 'u'.code.toByte(), 'e'.code.toByte())
        private val FALSE = byteArrayOf('f'.code.toByte(), 'a'.code.toByte(), 'l'.code.toByte(), 's'.code.toByte(), 'e'.code.toByte())
        private val COMMA = byteArrayOf(','.code.toByte())
        private val COLON: ByteArray = byteArrayOf(':'.code.toByte())
        private val LBRACKET = byteArrayOf('['.code.toByte())
        private val RBRACKET = byteArrayOf(']'.code.toByte())
        private val LBRACE = byteArrayOf('{'.code.toByte())
        private val RBRACE = byteArrayOf('}'.code.toByte())

        init {
            for (i in 0..31) {
                // Control chars must be escaped
                val chars = CharArray(6)
                chars[0] = '\\'
                chars[1] = 'u'
                chars[2] = '0'
                chars[3] = '0'
                chars[4] = "0123456789abcdef"[(i shr 4) and 0xF]
                chars[5] = "0123456789abcdef"[i and 0xF]
                ESCAPES[i] = chars
            }
            ESCAPES['\\'.code] = charArrayOf('\\', '\\')
            ESCAPES['\"'.code] = charArrayOf('\\', '"')
            ESCAPES['\b'.code] = charArrayOf('\\', 'b')
            ESCAPES['\u000C'.code] = charArrayOf('\\', 'f')
            ESCAPES['\r'.code] = charArrayOf('\\', 'r')
            ESCAPES['\n'.code] = charArrayOf('\\', 'n')
            ESCAPES['\t'.code] = charArrayOf('\\', 't')
        }
    }

    private val defaultWriteContext: WriteContext = object : WriteContext() {
        @Throws(IOException::class)
        override fun beforeWrite() {
            // nothing
        }
    }
    private val writeStack = ArrayDeque()
    private var binaryOutputMode = BinaryOutputMode.HEX
    fun withBinaryOutputMode(mode: BinaryOutputMode): SimpleJsonProtocol {
        binaryOutputMode = mode
        return this
    }

    @Throws(IOException::class)
    override fun writeMessageBegin(name: String, typeId: Byte, seqId: Int) {
        writeMapBegin(typeId, typeId, 0) // values are ignored here
        writeString("name")
        writeString(name)
        writeString("value")
    }

    @Throws(IOException::class)
    override fun writeMessageEnd() {
        writeMapEnd()
    }

    @Throws(IOException::class)
    override fun writeStructBegin(structName: String) {
        writeContext().beforeWrite()
        pushWriteContext(MapWriteContext())
        transport.write(LBRACE)
        writeString("__thriftStruct")
        writeString(structName)
    }

    @Throws(IOException::class)
    override fun writeStructEnd() {
        transport.write(RBRACE)
        popWriteContext()
    }

    @Throws(IOException::class)
    override fun writeFieldBegin(fieldName: String, fieldId: Int, typeId: Byte) {
        // TODO: assert that we're in map context
        writeString(fieldName)
    }

    @Throws(IOException::class)
    override fun writeFieldEnd() {
    }

    @Throws(IOException::class)
    override fun writeFieldStop() {
    }

    @Throws(IOException::class)
    override fun writeMapBegin(keyTypeId: Byte, valueTypeId: Byte, mapSize: Int) {
        writeContext().beforeWrite()
        pushWriteContext(MapWriteContext())
        transport.write(LBRACE)
    }

    @Throws(IOException::class)
    override fun writeMapEnd() {
        transport.write(RBRACE)
        popWriteContext()
    }

    @Throws(IOException::class)
    override fun writeListBegin(elementTypeId: Byte, listSize: Int) {
        writeContext().beforeWrite()
        pushWriteContext(ListWriteContext())
        transport.write(LBRACKET)
    }

    @Throws(IOException::class)
    override fun writeListEnd() {
        transport.write(RBRACKET)
        popWriteContext()
    }

    @Throws(IOException::class)
    override fun writeSetBegin(elementTypeId: Byte, setSize: Int) {
        writeContext().beforeWrite()
        pushWriteContext(ListWriteContext())
        transport.write(LBRACKET)
    }

    @Throws(IOException::class)
    override fun writeSetEnd() {
        transport.write(RBRACKET)
        popWriteContext()
    }

    @Throws(IOException::class)
    override fun writeBool(b: Boolean) {
        writeContext().beforeWrite()
        transport.write(if (b) TRUE else FALSE)
    }

    @Throws(IOException::class)
    override fun writeByte(b: Byte) {
        writeContext().beforeWrite()
        val toWrite = b.toString().encodeToByteArray()
        transport.write(toWrite)
    }

    @Throws(IOException::class)
    override fun writeI16(i16: Short) {
        writeContext().beforeWrite()
        transport.write(i16.toString().encodeToByteArray())
    }

    @Throws(IOException::class)
    override fun writeI32(i32: Int) {
        writeContext().beforeWrite()
        transport.write(i32.toString().encodeToByteArray())
    }

    @Throws(IOException::class)
    override fun writeI64(i64: Long) {
        writeContext().beforeWrite()
        transport.write(i64.toString().encodeToByteArray())
    }

    @Throws(IOException::class)
    override fun writeDouble(dub: Double) {
        writeContext().beforeWrite()
        transport.write(dub.toString().encodeToByteArray())
    }

    @Throws(IOException::class)
    override fun writeString(str: String) {
        writeContext().beforeWrite()
        val len = str.length
        val buffer = Buffer()
        buffer.writeUtf8CodePoint('"'.code)
        for (i in 0 until len) {
            val c = str[i]
            if (c.code < 128) {
                val maybeEscape = ESCAPES[c.code]
                if (maybeEscape != null) {
                    maybeEscape.forEach { buffer.writeUtf8CodePoint(it.code) } // These are known to be equivalent
                } else {
                    buffer.writeUtf8CodePoint(c.code)
                }
            } else {
                buffer.writeUtf8("$c") // Not sure how to get code points from a string in MPP
            }
        }
        buffer.writeUtf8CodePoint('"'.code)
        val bs = buffer.readByteArray()
        transport.write(bs, 0, bs.size)
    }

    @Throws(IOException::class)
    override fun writeBinary(buf: ByteString) {
        val out = when (binaryOutputMode) {
            BinaryOutputMode.HEX -> buf.hex()
            BinaryOutputMode.BASE_64 -> buf.base64()
            BinaryOutputMode.UNICODE -> buf.utf8()
        }
        writeString(out)
    }

    private fun pushWriteContext(context: WriteContext) {
        writeStack.addFirst(context)
    }

    private fun writeContext(): WriteContext {
        var top = writeStack.firstOrNull()
        if (top == null) {
            top = defaultWriteContext
        }
        return top
    }

    @Throws(IOException::class)
    private fun popWriteContext() {
        val context = writeStack.removeFirstOrNull()
        if (context == null) {
            throw ProtocolException("stack underflow")
        } else {
            context.onPop()
        }
    }

    @Throws(IOException::class)
    override fun readMessageBegin(): MessageMetadata {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readMessageEnd() {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readStructBegin(): StructMetadata {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readStructEnd() {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readFieldBegin(): FieldMetadata {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readFieldEnd() {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readMapBegin(): MapMetadata {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readMapEnd() {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readListBegin(): ListMetadata {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readListEnd() {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readSetBegin(): SetMetadata {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readSetEnd() {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readBool(): Boolean {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readByte(): Byte {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readI16(): Short {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readI32(): Int {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readI64(): Long {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readDouble(): Double {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readString(): String {
        throw UnsupportedOperationException()
    }

    @Throws(IOException::class)
    override fun readBinary(): ByteString {
        throw UnsupportedOperationException()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy