Skip to content

Commit

Permalink
feat(buffer,json): handle scala Duration json serde
Browse files Browse the repository at this point in the history
  • Loading branch information
yankun1992 committed Aug 3, 2024
1 parent b5fb596 commit 1c350a3
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 97 deletions.
58 changes: 57 additions & 1 deletion buffer/src/cc/otavia/buffer/BufferUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@

package cc.otavia.buffer

import cc.otavia.buffer.constant.DurationConstants

import java.math.{BigInteger, MathContext}
import java.nio.charset.StandardCharsets
import java.time.format.DateTimeParseException
import java.time.{Duration as JDuration, *}
import java.util.UUID
import scala.annotation.switch
import scala.concurrent.duration.Duration
import scala.concurrent.duration.*
import scala.jdk.CollectionConverters.*
import scala.language.unsafeNulls

Expand Down Expand Up @@ -1498,6 +1500,60 @@ object BufferUtils {
JDuration.ofSeconds(seconds, nano.toLong)
}

final def writeDurationAsString(buffer: Buffer, duration: Duration): Unit = {
if (duration eq Duration.Undefined) buffer.writeBytes(DurationConstants.Undefined)
else if (duration == Duration.Inf) buffer.writeBytes(DurationConstants.Inf)
else if (duration == Duration.MinusInf) buffer.writeBytes(DurationConstants.MinusInf)
else {
writeLongAsString(buffer, duration.length)
buffer.writeByte(' ')
duration.unit match
case DAYS => buffer.writeBytes(DurationConstants.DAY_BYTES)
case HOURS => buffer.writeBytes(DurationConstants.HOUR_BYTES)
case MINUTES => buffer.writeBytes(DurationConstants.MINUTE_BYTES)
case SECONDS => buffer.writeBytes(DurationConstants.SECOND_BYTES)
case MILLISECONDS => buffer.writeBytes(DurationConstants.MILLISECOND_BYTES)
case MICROSECONDS => buffer.writeBytes(DurationConstants.MICROSECOND_BYTES)
case NANOSECONDS => buffer.writeBytes(DurationConstants.NANOSECOND_BYTES)
if (duration.length != 1) buffer.writeByte('s')
}
}

/** Parse String into Duration. Format is `"<length><unit>"`, where whitespace is allowed before, between and after
* the parts. Infinities are designated by `"Inf"`, `"PlusInf"`, `"+Inf"`, `"Duration.Inf"` and `"-Inf"`,
* `"MinusInf"` or `"Duration.MinusInf"`. Undefined is designated by `"Duration.Undefined"`.
*
* @throws NumberFormatException
* if format is not parsable
*/
final def readStringAsDuration(buffer: Buffer): Duration = {
while (buffer.skipIfNextIs(' ')) {}

if (buffer.nextInRange('0', '9')) { // parse FiniteDuration
val length = readStringAsLong(buffer)
while (buffer.skipIfNextIs(' ')) {}
val timeUnit =
if (buffer.skipIfNextAre(DurationConstants.DAY_BYTES)) DAYS
else if (buffer.skipIfNextAre(DurationConstants.HOUR_BYTES)) HOURS
else if (buffer.skipIfNextAre(DurationConstants.MINUTE_BYTES)) MINUTES
else if (buffer.skipIfNextAre(DurationConstants.SECOND_BYTES)) SECONDS
else if (buffer.skipIfNextAre(DurationConstants.MILLISECOND_BYTES)) MILLISECONDS
else if (buffer.skipIfNextAre(DurationConstants.MICROSECOND_BYTES)) MICROSECONDS
else if (buffer.skipIfNextAre(DurationConstants.NANOSECOND_BYTES)) NANOSECONDS
else throw new NumberFormatException(s"Duration format error at buffer index ${buffer.readerOffset}")

if (length != 1) buffer.skipIfNextIs('s')
Duration(length, timeUnit)
} else { // parse Infinite Duration
if (buffer.skipIfNextAre(DurationConstants.Undefined)) Duration.Undefined
else if (buffer.skipIfNextAre(DurationConstants.Inf)) Duration.Inf
else if (buffer.skipIfNextAre(DurationConstants.MinusInf)) Duration.MinusInf
else if (DurationConstants.InfArray.exists(bts => buffer.skipIfNextAre(bts))) Duration.Inf
else if (DurationConstants.MinusInfArray.exists(bts => buffer.skipIfNextAre(bts))) Duration.MinusInf
else throw new NumberFormatException(s"Duration format error at buffer index ${buffer.readerOffset}")
}
}

private def write2Digits(buffer: Buffer, q0: Int, ds: Array[Short]): Unit =
buffer.writeShortLE(ds(q0))

Expand Down
45 changes: 45 additions & 0 deletions buffer/src/cc/otavia/buffer/constant/DurationConstants.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2022 Yan Kun <yan_kun_1992@foxmail.com>
*
* 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 cc.otavia.buffer.constant

import java.nio.charset.StandardCharsets
import scala.language.unsafeNulls

object DurationConstants {

val Undefined: Array[Byte] = "Duration.Undefined".getBytes(StandardCharsets.US_ASCII)
val Inf: Array[Byte] = "Duration.Inf".getBytes(StandardCharsets.US_ASCII)
val MinusInf: Array[Byte] = "Duration.MinusInf".getBytes(StandardCharsets.US_ASCII)

val DAY_BYTES: Array[Byte] = "day".getBytes(StandardCharsets.US_ASCII)
val HOUR_BYTES: Array[Byte] = "hour".getBytes(StandardCharsets.US_ASCII)
val MINUTE_BYTES: Array[Byte] = "minute".getBytes(StandardCharsets.US_ASCII)
val SECOND_BYTES: Array[Byte] = "second".getBytes(StandardCharsets.US_ASCII)
val MILLISECOND_BYTES: Array[Byte] = "millisecond".getBytes(StandardCharsets.US_ASCII)
val MICROSECOND_BYTES: Array[Byte] = "microsecond".getBytes(StandardCharsets.US_ASCII)
val NANOSECOND_BYTES: Array[Byte] = "nanosecond".getBytes(StandardCharsets.US_ASCII)

val InfArray: Array[Array[Byte]] = Array(
"Inf".getBytes(StandardCharsets.US_ASCII),
"PlusInf".getBytes(StandardCharsets.US_ASCII),
"+Inf".getBytes(StandardCharsets.US_ASCII)
)

val MinusInfArray: Array[Array[Byte]] =
Array("MinusInf".getBytes(StandardCharsets.US_ASCII), "-Inf".getBytes(StandardCharsets.US_ASCII))

}
27 changes: 27 additions & 0 deletions buffer/test/src/cc/otavia/buffer/BufferUtilsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,33 @@ class BufferUtilsSuite extends AnyFunSuiteLike {

}

test("Duration") {
val buffer = allocator.allocate()

val undefined = Duration.Undefined
BufferUtils.writeDurationAsString(buffer, undefined)
assert(BufferUtils.readStringAsDuration(buffer) eq undefined)
buffer.compact()
assert(buffer.readableBytes == 0)

val durations = Seq(Duration.Zero, Duration.MinusInf, Duration.Inf)
for (duration <- durations) {
BufferUtils.writeDurationAsString(buffer, duration)
assert(BufferUtils.readStringAsDuration(buffer) == duration)
buffer.compact()
assert(buffer.readableBytes == 0)
}

0 to 10000 foreach { i =>
val duration = Duration.fromNanos(Random.nextLong(10000000000L))
BufferUtils.writeDurationAsString(buffer, duration)
assert(BufferUtils.readStringAsDuration(buffer) == duration)
buffer.compact()
assert(buffer.readableBytes == 0)
}

}

test("Period") {
val buffer = allocator.allocate()

Expand Down
20 changes: 19 additions & 1 deletion serde-json/src/cc/otavia/json/JsonHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ private[json] object JsonHelper {
out.writeByte('\"')
}

final def serializeDuration(duration: Duration, out: Buffer): Unit = {
out.writeByte('\"')
BufferUtils.writeDurationAsString(out, duration)
out.writeByte('\"')
}

final def serializeInstant(instant: Instant, out: Buffer): Unit = {
out.writeByte('\"')
BufferUtils.writeInstantAsString(out, instant)
Expand Down Expand Up @@ -278,7 +284,19 @@ private[json] object JsonHelper {
duration
}

final def deserializeDuration(in: Buffer): Duration = ???
/** Reads a JSON string value into a [[Duration]] instance.
*
* @param in
* The [[Buffer]] to read.
* @return
* a [[Duration]] instance of the parsed JSON value.
*/
final def deserializeDuration(in: Buffer): Duration = {
assert(in.skipIfNextIs('\"'), s"except \" but get ${in.readByte.toChar}")
val duration = BufferUtils.readStringAsDuration(in)
assert(in.skipIfNextIs('\"'), s"except \" but get ${in.readByte.toChar}")
duration
}

final def deserializeInstant(in: Buffer): Instant = {
assert(in.skipIfNextIs('\"'), s"except \" but get ${in.readByte.toChar}")
Expand Down
8 changes: 5 additions & 3 deletions serde-json/src/cc/otavia/json/JsonSerde.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ trait JsonSerde[A] extends Serde[A] with SerdeOps {
this
}

override protected def serializeDuration(duration: Duration, out: Buffer): JsonSerde.this.type = {
JsonHelper.serializeDuration(duration, out)
this
}

override final protected def serializeInstant(instant: Instant, out: Buffer): this.type = {
JsonHelper.serializeInstant(instant, out)
this
Expand Down Expand Up @@ -302,9 +307,6 @@ object JsonSerde {
given JsonSerde[ZoneId] = ZoneIdJsonSerde
given JsonSerde[ZoneOffset] = ZoneOffsetJsonSerde
given JsonSerde[UUID] = UUIDJsonSerde
given JsonSerde[Locale] = LocaleJsonSerde
given JsonSerde[Currency] = CurrencyJsonSerde
given JsonSerde[Money] = MoneyJsonSerde
given JsonSerde[String] = StringJsonSerde

/** Derives a [[JsonSerde]] for JSON values for the specified type [[T]].
Expand Down
31 changes: 0 additions & 31 deletions serde-json/src/cc/otavia/json/types/CurrencyJsonSerde.scala

This file was deleted.

31 changes: 0 additions & 31 deletions serde-json/src/cc/otavia/json/types/LocaleJsonSerde.scala

This file was deleted.

30 changes: 0 additions & 30 deletions serde-json/src/cc/otavia/json/types/MoneyJsonSerde.scala

This file was deleted.

11 changes: 11 additions & 0 deletions serde/src/cc/otavia/serde/SerdeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,17 @@ trait SerdeOps {
*/
protected def serializeJDuration(duration: JDuration, out: Buffer): this.type

/** Serialize [[Duration]] value to [[Buffer]].
*
* @param duration
* Value.
* @param out
* Output [[Buffer]].
* @return
* This [[Serde]] instance.
*/
protected def serializeDuration(duration: Duration, out: Buffer): this.type

/** Serialize [[Instant]] value to [[Buffer]].
*
* @param instant
Expand Down

0 comments on commit 1c350a3

Please sign in to comment.