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

org.json4s.ParserUtil.scala Maven / Gradle / Ivy

There is a newer version: 4.1.0-M8
Show newest version
package org.json4s

import scala.annotation.{switch, tailrec}

object ParserUtil {

  class ParseException(message: String, cause: Exception) extends Exception(message, cause)

  private[this] val EOF = -1.asInstanceOf[Char]

  def quote(s: String, alwaysEscapeUnicode: Boolean): String =
    quote(
      s = s,
      appender = new java.lang.StringBuilder,
      alwaysEscapeUnicode = alwaysEscapeUnicode
    ).toString

  private[json4s] def quote[T <: java.lang.Appendable](s: String, appender: T, alwaysEscapeUnicode: Boolean): T = { // hot path
    var i = 0
    val l = s.length
    while (i < l) {
      (s(i): @annotation.switch) match {
        case '"' => appender.append("\\\"")
        case '\\' => appender.append("\\\\")
        case '\b' => appender.append("\\b")
        case '\f' => appender.append("\\f")
        case '\n' => appender.append("\\n")
        case '\r' => appender.append("\\r")
        case '\t' => appender.append("\\t")
        case c =>
          val shouldEscape = if (alwaysEscapeUnicode) {
            c >= 0x80
          } else {
            (c >= '\u0000' && c <= '\u001f') || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')
          }
          if (shouldEscape)
            appender.append("\\u%04X".format(c: Int))
          else appender.append(c)
      }
      i += 1
    }
    appender
  }

  def unquote(string: String): String =
    unquote(new Buffer(new java.io.StringReader(string), false))

  private[json4s] def unquote(buf: Buffer): String = {
    def unquote0(buf: Buffer, base: String): String = {
      val s = new java.lang.StringBuilder(base)
      var c = '\\'
      while (c != '"') {
        if (c == '\\') {
          (buf.next(): @switch) match {
            case '"' => s.append('"')
            case '\\' => s.append('\\')
            case '/' => s.append('/')
            case 'b' => s.append('\b')
            case 'f' => s.append('\f')
            case 'n' => s.append('\n')
            case 'r' => s.append('\r')
            case 't' => s.append('\t')
            case 'u' =>
              val chars = Array(buf.next(), buf.next(), buf.next(), buf.next())
              val codePoint = Integer.parseInt(new String(chars), 16)
              s.appendCodePoint(codePoint)
            case _ => s.append('\\')
          }
        } else s.append(c)
        c = buf.next()
      }
      s.toString
    }

    buf.eofIsFailure = true
    buf.mark()
    var c = buf.next()
    while (c != '"') {
      if (c == '\\') {
        val s = unquote0(buf, buf.substring)
        buf.eofIsFailure = false
        return s
      }
      c = buf.next()
    }
    buf.eofIsFailure = false
    buf.substring
  }

  /* Buffer used to parse JSON.
   * Buffer is divided to one or more segments (preallocated in Segments pool).
   */
  private[json4s] class Buffer(in: java.io.Reader, closeAutomatically: Boolean) {
    private[this] var offset = 0
    private[this] var curMark = -1
    private[this] var curMarkSegment = -1
    var eofIsFailure = false
    private[this] var segments: Vector[Segment] = Vector(Segments.apply())
    private[this] var segment: Array[Char] = segments.head.seg
    private[this] var cur = 0 // Pointer which points current parsing location
    private[this] var curSegmentIdx = 0 // Pointer which points current segment

    def mark(): Unit = { curMark = cur; curMarkSegment = curSegmentIdx }
    def back(): Unit = cur = cur - 1

    def next(): Char = {
      if (cur == offset && read() < 0) {
        if (eofIsFailure) throw new ParseException("unexpected eof", null) else EOF
      } else {
        val c = segment(cur)
        cur += 1
        c
      }
    }

    def substring: String = {
      if (curSegmentIdx == curMarkSegment) new String(segment, curMark, cur - curMark - 1)
      else { // slower path for case when string is in two or more segments
        var parts: List[(Int, Int, Array[Char])] = Nil
        var i = curSegmentIdx
        while (i >= curMarkSegment) {
          val s = segments(i).seg
          val start = if (i == curMarkSegment) curMark else 0
          val end = if (i == curSegmentIdx) cur else s.length + 1
          parts = (start, end, s) :: parts
          i = i - 1
        }
        val len = parts.map(p => p._2 - p._1 - 1).sum
        val chars = new Array[Char](len)

        @tailrec
        def loop(xs: List[(Int, Int, Array[Char])], pos: Int): Unit = {
          xs match {
            case (start, end, b) :: tail =>
              val partLen = end - start - 1
              System.arraycopy(b, start, chars, pos, partLen)
              loop(tail, pos + partLen)
            case _ =>
          }
        }
        loop(parts, 0)
        new String(chars)
      }
    }

    def near: String = new String(segment, (cur - 20) max 0, 20 min cur)

    def release(): Unit = segments.foreach(Segments.release)

    private[json4s] def automaticClose(): Unit = if (closeAutomatically) in.close()

    private[this] def read(): Int = {
      if (offset >= segment.length) {
        val newSegment = Segments.apply()
        offset = 0
        segment = newSegment.seg
        segments = segments :+ newSegment
        curSegmentIdx = segments.length - 1
      }

      val length = in.read(segment, offset, segment.length - offset)
      cur = offset
      offset += length
      length
    }
  }

  private[json4s] def defaultSegmentSize: Int = 1000

  private[this] val BrokenDouble = BigDecimal("2.2250738585072012e-308")
  private[json4s] def parseDouble(s: String) = {
    val d = BigDecimal(s)
    if (d == BrokenDouble) sys.error("Error parsing 2.2250738585072012e-308")
    else d.doubleValue
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy