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

com.twitter.finatra.json.internal.streaming.JsonArrayChunker.scala Maven / Gradle / Ivy

package com.twitter.finatra.json.internal.streaming

import com.twitter.finatra.conversions.buf._
import com.twitter.finatra.json.internal.streaming.ParsingState._
import com.twitter.inject.Logging
import com.twitter.io.Buf
import java.nio.ByteBuffer
import scala.collection.mutable.ArrayBuffer

// General logic copied from:
// https://github.com/netty/netty/blob/master/codec/src/main/java/io/netty/handler/codec/json/JsonObjectDecoder.java
private[finatra] class JsonArrayChunker extends Logging {

  private[finatra] var parsingState: ParsingState = Normal
  private[finatra] var done = false
  private[finatra] var openBraces = 0
  private var byteBuffer = ByteBuffer.allocate(0)

  /* Public */

  def decode(inputBuf: Buf): Seq[Buf] = {
    assertDecode(inputBuf)
    byteBuffer = ByteBufferUtils.append(byteBuffer, inputBuf)

    val result = ArrayBuffer[Buf]()

    while (byteBuffer.hasRemaining) {
      ByteBufferUtils.debugBuffer(byteBuffer)
      val currByte = byteBuffer.get

      if (!arrayFound && currByte == '[') {
        debug("ArrayFound. Openbraces = 1")
        parsingState = InsideArray
        openBraces = 1
        byteBuffer = byteBuffer.slice()
      }
      else if (!arrayFound && Character.isWhitespace(currByte.toChar)) {
        debug("Skip space")
      }
      else {
        decodeByteAndUpdateState(currByte, byteBuffer)
        if (!insideString && (openBraces == 1 && currByte == ',' || openBraces == 0 && currByte == ']')) {
          result += extractBuf()

          if (currByte == ']') {
            debug("Done")
            done = true
          }
        }
      }
    }

    result
  }

  /* Private */

  private def decodeByteAndUpdateState(c: Byte, in: ByteBuffer) {
    debug("decode '" + c.toChar + "'")
    if ((c == '{' || c == '[') && !insideString) {
      openBraces += 1
      debug("openBraces = " + openBraces)
    }
    else if ((c == '}' || c == ']') && !insideString) {
      openBraces -= 1
      debug("openBraces = " + openBraces)
    }
    else if (c == '"') {
      // start of a new JSON string. It's necessary to detect strings as they may
      // also contain braces/brackets and that could lead to incorrect results.
      if (!insideString) {
        debug("State = InsideString")
        parsingState = InsideString
      }
      // If the double quote wasn't escaped then this is the end of a string.
      else if (in.get(in.position - 1) != '\\') {
        debug("State = Parsing")
        parsingState = Normal
      }
    }
  }

  //TODO: Optimize
  private def extractBuf(): Buf = {
    val copy = byteBuffer.duplicate()
    copy.position(0)
    copy.limit(byteBuffer.position - 1)
    val copyBuf = Buf.ByteBuffer.Shared(copy)

    byteBuffer = byteBuffer.slice()
    debug("Extract result " + copyBuf.utf8str)
    copyBuf
  }

  private def assertDecode(inputBuf: Buf): Unit = {
    debug("Decode called with \"" + inputBuf.utf8str + "\"")
    if (done) {
      throw new scala.Exception("End array already found")
    }
  }

  private def insideString = parsingState == InsideString

  private def arrayFound = parsingState == InsideArray

  private[finatra] def copiedByteBuffer = byteBuffer.duplicate()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy