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

wvlet.airframe.msgpack.spi.Value.scala Maven / Gradle / Ivy

There is a newer version: 24.11.0
Show newest version
/*
 * 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 wvlet.airframe.msgpack.spi

import java.math.BigInteger
import java.time.Instant
import java.util
import java.util.Base64

import wvlet.airframe.msgpack.spi.MessageException.*

/**
  */
trait Value {
  override def toString = toJson
  def toJson: String

  /**
    * Unlike toJson, toUnquotedString does not quote string/timestamp values.
    * @return
    */
  def toUnquotedString: String = toJson
  def valueType: ValueType

  /**
    * Write the value to target Packer
    *
    * @param packer
    * @return
    */
  def writeTo(packer: Packer): Unit

  def toMsgpack: Array[Byte] = {
    val p = MessagePack.newBufferPacker
    writeTo(p)
    p.toByteArray
  }
}

object Value {
  case object NilValue extends Value {
    override def toJson               = "null"
    override def valueType: ValueType = ValueType.NIL
    override def writeTo(packer: Packer): Unit = {
      packer.packNil
    }
  }
  case class BooleanValue(v: Boolean) extends Value {
    override def toJson               = if (v) "true" else "false"
    override def valueType: ValueType = ValueType.BOOLEAN
    override def writeTo(packer: Packer): Unit = {
      packer.packBoolean(v)
    }
  }

  trait IntegerValue extends Value {
    override def valueType: ValueType = ValueType.INTEGER
    def isValidByte: Boolean
    def isValidShort: Boolean
    def isValidInt: Boolean
    def isValidLong: Boolean
    def mostSuccinctMessageFormat: MessageFormat = {
      if (isValidByte) {
        MessageFormat.INT8
      } else if (isValidShort) {
        MessageFormat.INT16
      } else if (isValidInt) {
        MessageFormat.INT32
      } else if (isValidLong) {
        MessageFormat.INT64
      } else {
        MessageFormat.UINT64
      }
    }
  }

  case class LongValue(v: Long) extends IntegerValue {
    override def toJson = v.toString
    override def writeTo(packer: Packer): Unit = {
      packer.packLong(v)
    }

    def isValidByte: Boolean  = v.isValidByte
    def isValidShort: Boolean = v.isValidShort
    def isValidInt: Boolean   = v.isValidInt
    def isValidLong: Boolean  = v.isValidLong

    def asByte: Byte             = if (isValidByte) v.toByte else throw overflowU64(v)
    def asShort: Short           = if (isValidShort) v.toShort else throw overflowU64(v)
    def asInt: Int               = if (isValidInt) v.toInt else throw overflowU64(v)
    def asLong: Long             = v
    def asBigInteger: BigInteger = BigInteger.valueOf(v)
  }

  private val BYTE_MIN  = BigInteger.valueOf(Byte.MinValue.toLong)
  private val BYTE_MAX  = BigInteger.valueOf(Byte.MaxValue.toLong)
  private val SHORT_MIN = BigInteger.valueOf(Short.MinValue.toLong)
  private val SHORT_MAX = BigInteger.valueOf(Short.MaxValue.toLong)
  private val INT_MIN   = BigInteger.valueOf(Int.MinValue.toLong)
  private val INT_MAX   = BigInteger.valueOf(Int.MaxValue.toLong)
  private val LONG_MIN  = BigInteger.valueOf(Long.MinValue.toLong)
  private val LONG_MAX  = BigInteger.valueOf(Long.MaxValue.toLong)

  case class BigIntegerValue(v: BigInteger) extends IntegerValue {
    override def toJson                                           = v.toString
    private def within(min: BigInteger, max: BigInteger): Boolean = v.compareTo(min) >= 0 && v.compareTo(max) <= 0

    def isValidByte: Boolean  = within(BYTE_MIN, BYTE_MAX)
    def isValidShort: Boolean = within(SHORT_MIN, SHORT_MAX)
    def isValidInt: Boolean   = within(INT_MIN, INT_MAX)
    def isValidLong: Boolean  = within(LONG_MIN, LONG_MAX)

    def asByte: Byte             = if (isValidByte) v.byteValue() else throw overflow(v)
    def asShort: Short           = if (isValidShort) v.shortValue() else throw overflow(v)
    def asInt: Int               = if (isValidInt) v.intValue() else throw overflow(v)
    def asLong: Long             = if (isValidLong) v.longValue() else throw overflow(v)
    def asBigInteger: BigInteger = v
    override def writeTo(packer: Packer): Unit = {
      packer.packBigInteger(v)
    }
  }

  case class DoubleValue(v: Double) extends Value {
    override def toJson               = if (v.isNaN || v.isInfinite) "null" else v.toString
    override def valueType: ValueType = ValueType.FLOAT
    override def writeTo(packer: Packer): Unit = {
      packer.packDouble(v)
    }
  }

  abstract class RawValue extends Value {
    override def toJson = {
      val b = new StringBuilder
      appendJsonString(b, toRawString)
      b.result()
    }
    def toRawString: String
  }

  case class StringValue(v: String) extends RawValue {
    override def toString: String         = v
    override def toUnquotedString: String = v
    override def toRawString: String      = v
    override def valueType: ValueType     = ValueType.STRING
    override def writeTo(packer: Packer): Unit = {
      packer.packString(v)
    }
  }

  case class BinaryValue(v: Array[Byte]) extends RawValue {
    @transient private var decodedStringCache: String = null
    override def toUnquotedString: String             = toRawString
    override def valueType: ValueType                 = ValueType.BINARY
    override def writeTo(packer: Packer): Unit = {
      packer.packBinaryHeader(v.length)
      packer.writePayload(v)
    }

    // Produces Base64 encoded strings
    override def toRawString: String = {
      synchronized {
        if (decodedStringCache == null) {
          decodedStringCache = Base64.getEncoder.encodeToString(v)
        }
      }
      decodedStringCache
    }
    override def equals(obj: scala.Any): Boolean = {
      obj match {
        case other: BinaryValue =>
          v.sameElements(other.v)
        case _ => false
      }
    }
    override def hashCode(): Int = {
      util.Arrays.hashCode(v)
    }
  }

  case class ExtensionValue(extType: Byte, v: Array[Byte]) extends Value {
    // [extType(int),extBinary(base64)]
    override def toJson = {
      val base64 = Base64.getEncoder.encodeToString(v)
      s"""[${extType.toInt},"${base64}"]"""
    }
    override def valueType: ValueType = ValueType.EXTENSION
    override def writeTo(packer: Packer): Unit = {
      packer.packExtensionTypeHeader(extType, v.length)
      packer.writePayload(v)
    }

    override def equals(obj: scala.Any): Boolean = {
      obj match {
        case other: ExtensionValue =>
          extType == other.extType && v.sameElements(other.v)
        case _ => false
      }
    }
    override def hashCode(): Int = {
      val h = extType * 31 + util.Arrays.hashCode(v)
      h
    }
  }

  case class TimestampValue(v: Instant) extends Value {
    override def toJson: String = {
      val b = new StringBuilder
      appendJsonString(b, toRawString)
      b.result()
    }

    override def toUnquotedString: String = toRawString
    def toRawString                       = v.toString
    override def valueType: ValueType     = ValueType.EXTENSION // ValueType.TIMESTAMP
    override def writeTo(packer: Packer): Unit = {
      packer.packTimestamp(v)
    }
  }

  case class ArrayValue(elems: IndexedSeq[Value]) extends Value {
    def apply(i: Int): Value = elems.apply(i)
    def size: Int            = elems.size

    def isEmpty: Boolean  = elems.isEmpty
    def nonEmpty: Boolean = elems.nonEmpty

    override def toJson: String = {
      s"[${elems.map(_.toJson).mkString(",")}]"
    }
    override def valueType: ValueType = ValueType.ARRAY
    override def writeTo(packer: Packer): Unit = {
      packer.packArrayHeader(elems.length)
      elems.foreach(x => x.writeTo(packer))
    }
  }

  case class MapValue(entries: Map[Value, Value]) extends Value {
    def apply(key: Value): Value       = entries.apply(key)
    def get(key: Value): Option[Value] = entries.get(key)
    def size: Int                      = entries.size

    def isEmpty: Boolean  = entries.isEmpty
    def nonEmpty: Boolean = entries.nonEmpty
    override def toJson: String = {
      entries
        .map { kv =>
          // JSON requires Map key must be a quoted UTF-8 string
          val jsonKey = new StringBuilder()
          appendJsonString(jsonKey, kv._1.toUnquotedString)
          s"""${jsonKey.result()}:${kv._2.toJson}"""
        }.mkString("{", ",", "}")
    }
    override def valueType: ValueType = ValueType.MAP
    override def writeTo(packer: Packer): Unit = {
      packer.packMapHeader(entries.size)
      // Ensure using non-parallel collection
      entries.toIndexedSeq.foreach { x =>
        x._1.writeTo(packer)
        x._2.writeTo(packer)
      }
    }
  }

  private[spi] def appendJsonString(sb: StringBuilder, string: String): Unit = {
    sb.append("\"")
    var i = 0
    while (i < string.length) {
      val ch = string.charAt(i)
      if (ch < 0x20) {
        ch match {
          case '\n' =>
            sb.append("\\n")
          case '\r' =>
            sb.append("\\r")
          case '\t' =>
            sb.append("\\t")
          case '\f' =>
            sb.append("\\f")
          case '\b' =>
            sb.append("\\b")
          case _ =>
            // control chars
            escapeChar(sb, ch)
        }
      } else if (ch <= 0x7f) {
        ch match {
          case '\\' =>
            sb.append("\\\\")
          case '"' =>
            sb.append("\\\"")
          case _ =>
            sb.append(ch)
        }
      } else if (ch >= 0xd800 && ch <= 0xdfff) { // surrogates
        escapeChar(sb, ch)
      } else {
        sb.append(ch)
      }

      i += 1
    }
    sb.append("\"")
  }

  private final val HEX_TABLE = "0123456789abcdef".toCharArray

  private[spi] def escapeChar(sb: StringBuilder, ch: Int): Unit = {
    sb.append("\\u")
    sb.append(HEX_TABLE((ch >> 12) & 0x0f))
    sb.append(HEX_TABLE((ch >> 8) & 0x0f))
    sb.append(HEX_TABLE((ch >> 4) & 0x0f))
    sb.append(HEX_TABLE(ch & 0x0f))
  }
}

object ValueFactory {
  import Value.*
  def newNil                            = NilValue
  def newBoolean(b: Boolean)            = BooleanValue(b)
  def newInteger(i: Int)                = LongValue(i)
  def newInteger(l: Long)               = LongValue(l)
  def newInteger(b: BigInteger)         = BigIntegerValue(b)
  def newFloat(d: Double)               = DoubleValue(d)
  def newString(s: String)              = StringValue(s)
  def newTimestamp(i: Instant)          = TimestampValue(i)
  def newArray(elem: Value*)            = ArrayValue(elem.toIndexedSeq)
  def newMap(kv: (Value, Value)*)       = MapValue(Map(kv: _*))
  def newBinary(b: Array[Byte])         = BinaryValue(b)
  def newExt(tpe: Byte, v: Array[Byte]) = ExtensionValue(tpe, v)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy