Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BigInt/BigDecimal native support in Input/Output #69

Merged
merged 4 commits into from
Jun 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ object DummyInput extends Input {
def readList() = ignored
def readBoolean() = ignored
def readDouble() = ignored
def readBigInt() = ignored
def readBigDecimal() = ignored
def skip() = ()
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ class CirceJsonOutput(consumer: Json => Any) extends Output {
def writeInt(int: Int): Unit = consumer(Json.fromInt(int))
def writeLong(long: Long): Unit = consumer(Json.fromLong(long))
def writeDouble(double: Double): Unit = consumer(Json.fromDoubleOrString(double))
def writeBinary(binary: Array[Byte]): Unit = ???
def writeBigInt(bigInt: BigInt): Unit = consumer(Json.fromBigInt(bigInt))
def writeBigDecimal(bigDecimal: BigDecimal): Unit = consumer(Json.fromBigDecimal(bigDecimal))
def writeBinary(binary: Array[Byte]): Unit = consumer(Json.fromValues(binary.map(Json.fromInt(_))))
def writeList(): ListOutput = new CirceJsonListOutput(consumer)
def writeObject(): ObjectOutput = new CirceJsonObjectOutput(consumer)
override def writeFloat(float: Float): Unit = consumer(Json.fromFloatOrString(float))
Expand Down Expand Up @@ -67,7 +69,10 @@ class CirceJsonInput(json: Json) extends Input {
def readInt(): Int = asNumber.toInt.getOrElse(failNot("int"))
def readLong(): Long = asNumber.toLong.getOrElse(failNot("long"))
def readDouble(): Double = asNumber.toDouble
def readBinary(): Array[Byte] = ???
def readBigInt(): BigInt = asNumber.toBigInt.getOrElse(failNot("bigInteger"))
def readBigDecimal(): BigDecimal = asNumber.toBigDecimal.getOrElse(failNot("bigDecimal"))
def readBinary(): Array[Byte] = json.asArray.getOrElse(failNot("array")).iterator
.map(_.asNumber.flatMap(_.toByte).getOrElse(failNot("byte"))).toArray
def readList(): ListInput = new CirceJsonListInput(json.asArray.getOrElse(failNot("array")))
def readObject(): ObjectInput = new CirceJsonObjectInput(json.asObject.getOrElse(failNot("object")))
def skip(): Unit = ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
implicit lazy val LongCodec: GenCodec[Long] = create(_.readLong(), _ writeLong _)
implicit lazy val FloatCodec: GenCodec[Float] = create(_.readFloat(), _ writeFloat _)
implicit lazy val DoubleCodec: GenCodec[Double] = create(_.readDouble(), _ writeDouble _)
implicit lazy val BigIntCodec: GenCodec[BigInt] = createNullable(i => BigInt(i.readString()), (o, v) => o.writeString(v.toString))
implicit lazy val BigDecimalCodec: GenCodec[BigDecimal] = createNullable(i => BigDecimal(i.readString()), (o, v) => o.writeString(v.toString))
implicit lazy val BigIntCodec: GenCodec[BigInt] = createNullable(_.readBigInt(), _ writeBigInt _)
implicit lazy val BigDecimalCodec: GenCodec[BigDecimal] = createNullable(_.readBigDecimal(), _ writeBigDecimal _)

implicit lazy val JBooleanCodec: GenCodec[JBoolean] = createNullable(_.readBoolean(), _ writeBoolean _)
implicit lazy val JCharacterCodec: GenCodec[JCharacter] = createNullable(_.readChar(), _ writeChar _)
Expand All @@ -298,8 +298,10 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs {
implicit lazy val JLongCodec: GenCodec[JLong] = createNullable(_.readLong(), _ writeLong _)
implicit lazy val JFloatCodec: GenCodec[JFloat] = createNullable(_.readFloat(), _ writeFloat _)
implicit lazy val JDoubleCodec: GenCodec[JDouble] = createNullable(_.readDouble(), _ writeDouble _)
implicit lazy val JBigIntegerCodec: GenCodec[JBigInteger] = createNullable(i => new JBigInteger(i.readString()), (o, v) => o.writeString(v.toString))
implicit lazy val JBigDecimalCodec: GenCodec[JBigDecimal] = createNullable(i => new JBigDecimal(i.readString()), (o, v) => o.writeString(v.toString))
implicit lazy val JBigIntegerCodec: GenCodec[JBigInteger] =
createNullable(_.readBigInt().bigInteger, (o, v) => o.writeBigInt(BigInt(v)))
implicit lazy val JBigDecimalCodec: GenCodec[JBigDecimal] =
createNullable(_.readBigDecimal().bigDecimal, (o, v) => o.writeBigDecimal(BigDecimal(v)))

implicit lazy val JDateCodec: GenCodec[JDate] = createNullable(i => new JDate(i.readTimestamp()), (o, d) => o.writeTimestamp(d.getTime))
implicit lazy val StringCodec: GenCodec[String] = createNullable(_.readString(), _ writeString _)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ trait Output extends Any {
def writeTimestamp(millis: Long): Unit = writeLong(millis)
def writeFloat(float: Float): Unit = writeDouble(float)
def writeDouble(double: Double): Unit
def writeBigInt(bigInt: BigInt): Unit
def writeBigDecimal(bigDecimal: BigDecimal): Unit
def writeBinary(binary: Array[Byte]): Unit
def writeList(): ListOutput
def writeObject(): ObjectOutput
Expand Down Expand Up @@ -139,6 +141,8 @@ trait Input extends Any {
def readTimestamp(): Long = readLong()
def readFloat(): Float = readDouble().toFloat
def readDouble(): Double
def readBigInt(): BigInt
def readBigDecimal(): BigDecimal
def readBinary(): Array[Byte]
def readList(): ListInput
def readObject(): ObjectInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ object SimpleValueOutput {
* - `Int`
* - `Long`
* - `Double`
* - `BigInt`
* - `BigDecimal`
* - `Boolean`
* - `String`
* - `Array[Byte]`
Expand All @@ -49,27 +51,27 @@ class SimpleValueOutput(
def this(consumer: Any => Unit) =
this(consumer, new MHashMap[String, Any], new ListBuffer[Any])

def writeBinary(binary: Array[Byte]) = consumer(binary)
def writeString(str: String) = consumer(str)
def writeDouble(double: Double) = consumer(double)
def writeInt(int: Int) = consumer(int)

def writeList() = new ListOutput {
def writeNull(): Unit = consumer(null)
def writeBoolean(boolean: Boolean): Unit = consumer(boolean)
def writeString(str: String): Unit = consumer(str)
def writeInt(int: Int): Unit = consumer(int)
def writeLong(long: Long): Unit = consumer(long)
def writeDouble(double: Double): Unit = consumer(double)
def writeBigInt(bigInt: BigInt): Unit = consumer(bigInt)
def writeBigDecimal(bigDecimal: BigDecimal): Unit = consumer(bigDecimal)
def writeBinary(binary: Array[Byte]): Unit = consumer(binary)

def writeList(): ListOutput = new ListOutput {
private val buffer = newListRepr
def writeElement() = new SimpleValueOutput(buffer += _, newObjectRepr, newListRepr)
def finish() = consumer(buffer.result())
def finish(): Unit = consumer(buffer.result())
}

def writeBoolean(boolean: Boolean) = consumer(boolean)

def writeObject() = new ObjectOutput {
def writeObject(): ObjectOutput = new ObjectOutput {
private val result = newObjectRepr
def writeField(key: String) = new SimpleValueOutput(v => result += ((key, v)), newObjectRepr, newListRepr)
def finish() = consumer(result)
def finish(): Unit = consumer(result)
}

def writeLong(long: Long) = consumer(long)
def writeNull() = consumer(null)
}

object SimpleValueInput {
Expand All @@ -91,41 +93,42 @@ class SimpleValueInput(value: Any) extends Input {
case _ => throw new ReadFailure(s"Expected ${classTag[B].runtimeClass} but got ${value.getClass}")
}

def inputType = value match {
def inputType: InputType = value match {
case null => InputType.Null
case _: BSeq[Any] => InputType.List
case _: BMap[_, Any] => InputType.Object
case _ => InputType.Simple
}

def readBinary() = doRead[Array[Byte]]
def readLong() = doReadUnboxed[Long, JLong]
def readNull() = if (value == null) null else throw new ReadFailure("not null")
def readObject() =
def readNull(): Null = if (value == null) null else throw new ReadFailure("not null")
def readBoolean(): Boolean = doReadUnboxed[Boolean, JBoolean]
def readString(): String = doRead[String]
def readInt(): Int = doReadUnboxed[Int, JInteger]
def readLong(): Long = doReadUnboxed[Long, JLong]
def readDouble(): Double = doReadUnboxed[Double, JDouble]
def readBigInt(): BigInt = doRead[JBigInteger]
def readBigDecimal(): BigDecimal = doRead[JBigDecimal]
def readBinary(): Array[Byte] = doRead[Array[Byte]]

def readObject(): ObjectInput =
new ObjectInput {
private val map = doRead[BMap[String, Any]]
private val it = map.iterator.map {
case (k, v) => new SimpleValueFieldInput(k, v)
}
def nextField() = it.next()
override def peekField(name: String) = map.getOpt(name).map(new SimpleValueFieldInput(name, _))
def hasNext = it.hasNext
def nextField(): SimpleValueFieldInput = it.next()
override def peekField(name: String): Opt[SimpleValueFieldInput] = map.getOpt(name).map(new SimpleValueFieldInput(name, _))
def hasNext: Boolean = it.hasNext
}

def readInt() = doReadUnboxed[Int, JInteger]
def readString() = doRead[String]

def readList() =
def readList(): ListInput =
new ListInput {
private val it = doRead[BSeq[Any]].iterator.map(new SimpleValueInput(_))
def nextElement() = it.next()
def hasNext = it.hasNext
def nextElement(): SimpleValueInput = it.next()
def hasNext: Boolean = it.hasNext
}

def readBoolean() = doReadUnboxed[Boolean, JBoolean]
def readDouble() = doReadUnboxed[Double, JDouble]

def skip() = ()
def skip(): Unit = ()
}

class SimpleValueFieldInput(val fieldName: String, value: Any)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ private object FormatConstants {
final val ObjectStartMarker: Byte = 11
final val ListEndMarker: Byte = 12
final val ObjectEndMarker: Byte = 13
final val BitIntMarker: Byte = 14
final val BigDecimalMarker: Byte = 15
}

import com.avsystem.commons.serialization.FormatConstants._
Expand All @@ -47,53 +49,68 @@ class StreamInput(is: DataInputStream) extends Input {
InputType.Simple
}

def readNull(): Null = if (markerByte == NullMarker)
null
else
throw new ReadFailure(s"Expected null, but $markerByte found")

def readString(): String = if (markerByte == StringMarker)
is.readUTF()
else
throw new ReadFailure(s"Expected string, but $markerByte found")

def readBoolean(): Boolean = if (markerByte == BooleanMarker)
is.readBoolean()
else
throw new ReadFailure(s"Expected boolean, but $markerByte found")

def readInt(): Int = if (markerByte == IntMarker)
is.readInt()
else
throw new ReadFailure(s"Expected int, but $markerByte found")

def readLong(): Long = if (markerByte == LongMarker)
is.readLong()
else
throw new ReadFailure(s"Expected long, but $markerByte found")

def readDouble(): Double = if (markerByte == DoubleMarker)
is.readDouble()
else
throw new ReadFailure(s"Expected double, but $markerByte found")

def readBinary(): Array[Byte] = if (markerByte == ByteArrayMarker) {
val binary = Array.ofDim[Byte](is.readInt())
is.readFully(binary)
binary
} else {
throw new ReadFailure(s"Expected binary array, but $markerByte found")
}
def readNull(): Null =
if (markerByte == NullMarker) null
else throw new ReadFailure(s"Expected null but $markerByte found")

def readString(): String =
if (markerByte == StringMarker) is.readUTF()
else throw new ReadFailure(s"Expected string but $markerByte found")

def readBoolean(): Boolean =
if (markerByte == BooleanMarker) is.readBoolean()
else throw new ReadFailure(s"Expected boolean but $markerByte found")

def readInt(): Int =
if (markerByte == IntMarker) is.readInt()
else throw new ReadFailure(s"Expected int but $markerByte found")

def readLong(): Long =
if (markerByte == LongMarker) is.readLong()
else throw new ReadFailure(s"Expected long but $markerByte found")

def readDouble(): Double =
if (markerByte == DoubleMarker) is.readDouble()
else throw new ReadFailure(s"Expected double but $markerByte found")

def readBigInt(): BigInt =
if (markerByte == BitIntMarker) {
val len = is.readInt()
val bytes = new Array[Byte](len)
is.read(bytes)
BigInt(bytes)
} else {
throw new ReadFailure(s"Expected big integer but $markerByte found")
}

def readList(): ListInput = if (markerByte == ListStartMarker)
new StreamListInput(is)
else
throw new ReadFailure(s"Expected list, but $markerByte found")
def readBigDecimal(): BigDecimal =
if (markerByte == BigDecimalMarker) {
val len = is.readInt()
val bytes = new Array[Byte](len)
is.read(bytes)
val unscaled = BigInt(bytes)
val scale = is.readInt()
BigDecimal(unscaled, scale)
} else {
throw new ReadFailure(s"Expected big decimal but $markerByte found")
}

def readObject(): ObjectInput = if (markerByte == ObjectStartMarker)
new StreamObjectInput(is)
else
throw new ReadFailure(s"Expected object, but $markerByte found")
def readBinary(): Array[Byte] =
if (markerByte == ByteArrayMarker) {
val binary = Array.ofDim[Byte](is.readInt())
is.readFully(binary)
binary
} else {
throw new ReadFailure(s"Expected binary array but $markerByte found")
}

def readList(): ListInput =
if (markerByte == ListStartMarker) new StreamListInput(is)
else throw new ReadFailure(s"Expected list but $markerByte found")

def readObject(): ObjectInput =
if (markerByte == ObjectStartMarker) new StreamObjectInput(is)
else throw new ReadFailure(s"Expected object but $markerByte found")

def skip(): Unit = markerByte match {
case NullMarker =>
Expand Down Expand Up @@ -121,6 +138,10 @@ class StreamInput(is: DataInputStream) extends Input {
new StreamListInput(is).skipRemaining()
case ObjectStartMarker =>
new StreamObjectInput(is).skipRemaining()
case BitIntMarker =>
is.skipBytes(is.readInt())
case BigDecimalMarker =>
is.skipBytes(is.readInt() + Integer.BYTES)
case unexpected =>
throw new ReadFailure(s"Unexpected marker byte: $unexpected")
}
Expand Down Expand Up @@ -182,14 +203,16 @@ private object StreamObjectInput {
case class EmptyFieldInput(name: String) extends FieldInput {
private def nope: Nothing = throw new ReadFailure(s"Something went horribly wrong ($name)")

def fieldName: String = nope
def inputType: InputType = nope
def fieldName: String = nope
def readNull(): Null = nope
def readString(): String = nope
def readBoolean(): Boolean = nope
def readInt(): Int = nope
def readLong(): Long = nope
def readDouble(): Double = nope
def readBigInt(): BigInt = nope
def readBigDecimal(): BigDecimal = nope
def readBinary(): Array[Byte] = nope
def readList(): ListInput = nope
def readObject(): ObjectInput = nope
Expand Down Expand Up @@ -232,6 +255,21 @@ class StreamOutput(os: DataOutputStream) extends Output {
os.writeDouble(double)
}

def writeBigInt(bigInt: BigInt): Unit = {
os.writeByte(BitIntMarker)
val bytes = bigInt.toByteArray
os.writeInt(bytes.length)
os.write(bytes)
}

def writeBigDecimal(bigDecimal: BigDecimal): Unit = {
os.writeByte(BigDecimalMarker)
val bytes = bigDecimal.bigDecimal.unscaledValue.toByteArray
os.writeInt(bytes.length)
os.write(bytes)
os.writeInt(bigDecimal.scale)
}

def writeBinary(binary: Array[Byte]): Unit = {
os.writeByte(ByteArrayMarker)
os.writeInt(binary.length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class JsonStringInput(reader: JsonReader, callback: AfterElement = AfterElementN
case _ => afterElement()
}

private def expectedError(tpe: JsonType) = throw new ReadFailure(s"Expected $tpe but got ${reader.jsonType}: ${reader.currentValue}")
private def expectedError(tpe: JsonType) =
throw new ReadFailure(s"Expected $tpe but got ${reader.jsonType}: ${reader.currentValue}")

private def checkedValue[T](jsonType: JsonType): T = {
if (reader.jsonType != jsonType) expectedError(jsonType)
Expand Down Expand Up @@ -64,6 +65,8 @@ class JsonStringInput(reader: JsonReader, callback: AfterElement = AfterElementN
def readInt(): Int = matchNumericString(_.toInt)
def readLong(): Long = matchNumericString(_.toLong)
def readDouble(): Double = matchNumericString(_.toDouble)
def readBigInt(): BigInt = matchNumericString(BigInt(_))
def readBigDecimal(): BigDecimal = matchNumericString(BigDecimal(_))
def readBinary(): Array[Byte] = {
val hex = checkedValue[String](JsonType.string)
val result = new Array[Byte](hex.length / 2)
Expand Down Expand Up @@ -213,7 +216,7 @@ final class JsonReader(val json: String) {
private def readHex(): Int =
fromHex(read())

private def parseNumber(): Any = {
private def parseNumber(): String = {
val start = i

if (isNext('-')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ final class JsonStringOutput(builder: JStringBuilder) extends BaseJsonOutput wit
writeString(double.toString)
else builder.append(double.toString)
}
def writeBigInt(bigInt: BigInt): Unit = builder.append(bigInt.toString)
def writeBigDecimal(bigDecimal: BigDecimal): Unit = builder.append(bigDecimal.toString)
def writeBinary(binary: Array[Byte]): Unit = {
builder.append('"')
var i = 0
Expand Down
Loading