diff --git a/.sbtopts b/.sbtopts new file mode 100644 index 00000000..29810bec --- /dev/null +++ b/.sbtopts @@ -0,0 +1 @@ +-Dsbt.io.implicit.relative.glob.conversion=allow \ No newline at end of file diff --git a/build.sbt b/build.sbt index 004c1922..e4c6bd9d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,3 @@ -lazy val scala212 = "2.12.20" lazy val scala213 = "2.13.16" lazy val scala3 = "3.3.5" @@ -61,7 +60,7 @@ def crossScalaSettings = { } Seq( - crossScalaVersions := Seq(scala212, scala213, scala3), + crossScalaVersions := Seq(scala213, scala3), Compile / unmanagedSourceDirectories ++= addDirsByScalaVersion( "src/main" ).value, @@ -225,7 +224,7 @@ lazy val `jackson-212` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.12.7" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-213` = project .in(jackson / "jackson-213") @@ -239,7 +238,7 @@ lazy val `jackson-213` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.13.5" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-214` = project .in(jackson / "jackson-214") @@ -253,7 +252,7 @@ lazy val `jackson-214` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.14.3" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-215` = project .in(jackson / "jackson-215") @@ -267,7 +266,7 @@ lazy val `jackson-215` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.15.4" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-216` = project .in(jackson / "jackson-216") @@ -281,7 +280,7 @@ lazy val `jackson-216` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.16.2" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-217` = project .in(jackson / "jackson-217") @@ -295,7 +294,7 @@ lazy val `jackson-217` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.17.3" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-218` = project .in(jackson / "jackson-218") @@ -309,7 +308,7 @@ lazy val `jackson-218` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.18.3" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val benchmarks = project .in(modules / "benchmarks") @@ -327,6 +326,8 @@ lazy val benchmarks = project "io.circe" %% "circe-jackson210" % "0.14.0", "dev.zio" %% "zio-json" % "0.7.1", "com.typesafe.play" %% "play-json" % "2.10.5", + "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.33.1", + "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.33.1", "org.knowm.xchart" % "xchart" % "3.8.2" exclude ("de.erichseifert.vectorgraphics2d", "VectorGraphics2D") withSources () ), scalacOptions ++= { diff --git a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenIterator.scala b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenIterator.scala index 3bbb60fa..a4075c3b 100644 --- a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenIterator.scala +++ b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenIterator.scala @@ -10,6 +10,7 @@ import scala.annotation.switch final class JacksonTokenIterator(jsonParser: JsonParser) extends BaseTokenIterator { private[this] var token: Token = fromId(jsonParser.currentTokenId()) + override def currentToken(): Token = token override def nextToken(): Token = { diff --git a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala index 53497f7f..681a966a 100644 --- a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala +++ b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala @@ -92,4 +92,7 @@ class JacksonTokenWriter(jsonGenerator: JsonGenerator) extends TokenWriter { override def close(): Unit = jsonGenerator.close() override def flush(): Unit = jsonGenerator.flush() + + override def result(): String = + jsonGenerator.getOutputTarget.toString } diff --git a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala index 78720394..58d7c831 100644 --- a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala +++ b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala @@ -1,36 +1,81 @@ package tethys -import java.io.{Reader, Writer} - -import com.fasterxml.jackson.core.JsonFactory +import java.io.Reader +import com.fasterxml.jackson.core.{ + JsonFactory, + JsonFactoryBuilder, + JsonGenerator +} import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.{TokenIterator, TokenIteratorProducer} -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} +import tethys.writers.tokens.{ + TokenWriter, + TokenWriterConfig, + TokenWriterProducer +} package object jackson { - lazy val defaultJsonFactory: JsonFactory = { - val f = new JsonFactory() - f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false) - f + lazy val defaultJsonFactory: JsonFactory = + new JsonFactoryBuilder() + .configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false) + .build() + + class JacksonTokenWriterProducer( + jsonFactory: JsonFactory, + // used for compatibility where tethys.jackson.pretty import is required + modifyConfig: TokenWriterConfig => TokenWriterConfig + ) extends TokenWriterProducer { + + private def updateGeneratorFromConfig( + generator: JsonGenerator, + config: TokenWriterConfig + ): JsonGenerator = { + if (config == TokenWriterConfig.default) + generator + else { + val first = + if (config.indentionStep == 2) + generator.useDefaultPrettyPrinter() + else + generator + + val second = + if (config.escapeUnicode) + first.setHighestNonEscapedChar(127) + else + first + second + } + } + + override def produce(config: TokenWriterConfig): TokenWriter = { + val generator = updateGeneratorFromConfig( + jsonFactory.createGenerator(new java.io.StringWriter()), + modifyConfig(config) + ) + new JacksonTokenWriter(generator) + } } implicit def jacksonTokenWriterProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory - ): TokenWriterProducer = new TokenWriterProducer { - override def forWriter(writer: Writer): TokenWriter = { - new JacksonTokenWriter(jsonFactory.createGenerator(writer)) - } - } + ): JacksonTokenWriterProducer = + new JacksonTokenWriterProducer(jsonFactory, identity) implicit def jacksonTokenIteratorProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory ): TokenIteratorProducer = new TokenIteratorProducer { + override def produce(json: String): Either[ReaderError, TokenIterator] = + ReaderError.catchNonFatal( + JacksonTokenIterator.fromFreshParser(jsonFactory.createParser(json)) + )(FieldName.Root) + override def fromReader( reader: Reader ): Either[ReaderError, TokenIterator] = { ReaderError.catchNonFatal( JacksonTokenIterator.fromFreshParser(jsonFactory.createParser(reader)) - )(FieldName()) + )(FieldName.Root) } } } diff --git a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala index 6bb86bd3..7ce322ae 100644 --- a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala +++ b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala @@ -1,21 +1,18 @@ package tethys.jackson -import java.io.Writer - import com.fasterxml.jackson.core.JsonFactory import tethys.readers.tokens.TokenIteratorProducer -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} package object pretty { + + @deprecated("Provide implicit TokenWriterConfig to `asJson` instead") implicit def prettyJacksonTokenWriterProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory - ): TokenWriterProducer = new TokenWriterProducer { - override def forWriter(writer: Writer): TokenWriter = { - new JacksonTokenWriter( - jsonFactory.createGenerator(writer).useDefaultPrettyPrinter() - ) - } - } + ): JacksonTokenWriterProducer = + new tethys.jackson.JacksonTokenWriterProducer( + jsonFactory, + modifyConfig = _.withDefaultPrettyPrinter + ) implicit def jacksonTokenIteratorProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonReaderSpec.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonReaderSpec.scala new file mode 100644 index 00000000..4cedca23 --- /dev/null +++ b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonReaderSpec.scala @@ -0,0 +1,8 @@ +package tethys.jackson + +import tethys.JsonReaderSpec +import tethys.readers.tokens.TokenIteratorProducer + +class JacksonJsonReaderSpec extends JsonReaderSpec { + def producer: TokenIteratorProducer = jacksonTokenIteratorProducer +} diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonWriterSpec.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonWriterSpec.scala new file mode 100644 index 00000000..7b8365ca --- /dev/null +++ b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonWriterSpec.scala @@ -0,0 +1,169 @@ +package tethys.jackson + +import tethys.writers.tokens.{ + TokenWriterProducer, + TokenWriterConfig, + JsonWriterSpec +} +import tethys._ + +class JacksonJsonWriterSpec extends JsonWriterSpec { + { + import tethys.jackson.pretty._ + import tethys.writers.tokens.TestModels._ + + configurableTestCase( + description = "pretty print complex structures", + config = TokenWriterConfig.default.withDefaultPrettyPrinter, + value = Department( + "Global Engineering", + List( + Employee( + 1L, + Person("John Doe", 30, Some("john@example.com")), + Address("123 Main St", "New York", "USA", Some("10001")), + "Backend", + BigDecimal("100000.00") + ), + Employee( + 2L, + Person("Jane Smith", 28, None), + Address("456 Park Ave", "Boston", "USA", None), + "Frontend", + BigDecimal("95000.50") + ) + ), + Set("global", "engineering", "tech"), + Map( + "headquarters" -> "New York", + "founded" -> "2020", + "status" -> "active" + ) + ), + json = """{ + "name" : "Global Engineering", + "employees" : [ { + "id" : 1, + "person" : { + "name" : "John Doe", + "age" : 30, + "email" : "john@example.com" + }, + "address" : { + "street" : "123 Main St", + "city" : "New York", + "country" : "USA", + "postalCode" : "10001" + }, + "department" : "Backend", + "salary" : 100000.00 + }, { + "id" : 2, + "person" : { + "name" : "Jane Smith", + "age" : 28 + }, + "address" : { + "street" : "456 Park Ave", + "city" : "Boston", + "country" : "USA" + }, + "department" : "Frontend", + "salary" : 95000.50 + } ], + "tags" : [ "global", "engineering", "tech" ], + "metadata" : { + "headquarters" : "New York", + "founded" : "2020", + "status" : "active" + } +}""" + + ) + + configurableTestCase( + description = "escape unicode", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: \\u00A9 \\u20AC \\u2603\"" + ) + + configurableTestCase( + description = "escape unicode emoji", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = "Emoji: 😀 🚀 🌍", + json = "\"Emoji: \\uD83D\\uDE00 \\uD83D\\uDE80 \\uD83C\\uDF0D\"" + ) + + configurableTestCase( + description = "unicode characters in object keys", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Map("café" -> "coffee", "résumé" -> "CV", "über" -> "super"), + json = "{\"caf\\u00E9\":\"coffee\",\"r\\u00E9sum\\u00E9\":\"CV\",\"\\u00FCber\":\"super\"}" + ) + + case class MenuItem(name: String, price: Double) + case class Menu(items: List[MenuItem]) + case class Restaurant(menu: Menu) + + implicit val menuItemWriter: JsonWriter[MenuItem] = JsonWriter.obj[MenuItem] + .addField("name")(_.name) + .addField("price")(_.price) + + implicit val menuWriter: JsonWriter[Menu] = JsonWriter.obj[Menu] + .addField("items")(_.items) + + implicit val restaurantWriter: JsonWriter[Restaurant] = JsonWriter.obj[Restaurant] + .addField("menu")(_.menu) + + configurableTestCase( + description = "unicode characters in nested objects", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Restaurant(Menu(List( + MenuItem("Café au lait", 3.50), + MenuItem("Crème brûlée", 5.75) + ))), + json = "{\"menu\":{\"items\":[{\"name\":\"Caf\\u00E9 au lait\",\"price\":3.5},{\"name\":\"Cr\\u00E8me br\\u00FBl\\u00E9e\",\"price\":5.75}]}}" + ) + + configurableTestCase( + description = "mixed ASCII and unicode in arrays", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = List("hello", "世界", "こんにちは", "안녕하세요"), + json = "[\"hello\",\"\\u4E16\\u754C\",\"\\u3053\\u3093\\u306B\\u3061\\u306F\",\"\\uC548\\uB155\\uD558\\uC138\\uC694\"]" + ) + + configurableTestCase( + description = "control characters that should always be escaped", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Control chars: \u0000\u0001\u0008\u0009\u000A\u000C\u000D\u001F", + json = "\"Control chars: \\u0000\\u0001\\b\\t\\n\\f\\r\\u001F\"" + ) + + configurableTestCase( + description = "unicode characters with escapeUnicode set to false", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: © € ☃\"" + ) + + case class SurrogatePairsTest(text: String, list: List[String], keyValue: (String, String)) + + implicit val surrogatePairsTestWriter: JsonWriter[SurrogatePairsTest] = JsonWriter.obj[SurrogatePairsTest] + .addField("text")(_.text) + .addField("list")(_.list) + .addField("nested")(test => Map(test.keyValue)) + + configurableTestCase( + description = "surrogate pairs in different contexts", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = SurrogatePairsTest( + "Surrogate pairs: 𝄞 𝌆 𝓐", + List("𝄞", "𝌆", "𝓐"), + ("key𝄞", "value𝌆") + ), + json = "{\"text\":\"Surrogate pairs: \\uD834\\uDD1E \\uD834\\uDF06 \\uD835\\uDCD0\",\"list\":[\"\\uD834\\uDD1E\",\"\\uD834\\uDF06\",\"\\uD835\\uDCD0\"],\"nested\":{\"key\\uD834\\uDD1E\":\"value\\uD834\\uDF06\"}}" + ) + } + +} diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenIteratorSpec.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenIteratorSpec.scala new file mode 100644 index 00000000..2c821afa --- /dev/null +++ b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenIteratorSpec.scala @@ -0,0 +1,8 @@ +package tethys.jackson + +import tethys.readers.tokens.{TokenIteratorProducer, TokenIteratorSpec} + +class JacksonTokenIteratorSpec extends TokenIteratorSpec { + override def producer: TokenIteratorProducer = + jacksonTokenIteratorProducer +} diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenIteratorTest.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenIteratorTest.scala deleted file mode 100644 index 060b7b89..00000000 --- a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenIteratorTest.scala +++ /dev/null @@ -1,129 +0,0 @@ -package tethys.jackson - -import tethys._ -import tethys.commons.TokenNode._ -import org.scalatest.matchers.should.Matchers -import org.scalatest.flatspec.AnyFlatSpec - -class JacksonTokenIteratorTest extends AnyFlatSpec with Matchers { - - behavior of "JacksonTokenIterator" - - it should "properly iterate over json string" in { - val json = """{"a":1,"b":["s",true,{"a":null},1.0,false]}""" - val it = json.toTokenIterator.fold(throw _, identity) - it.currentToken().isObjectStart shouldBe true - - it.nextToken().isFieldName shouldBe true - it.fieldName() shouldBe "a" - it.nextToken().isNumberValue shouldBe true - it.number() shouldBe 1 - - it.nextToken().isFieldName shouldBe true - it.fieldName() shouldBe "b" - it.nextToken().isArrayStart shouldBe true - - it.nextToken().isStringValue shouldBe true - it.string() shouldBe "s" - - it.nextToken().isBooleanValue shouldBe true - it.boolean() shouldBe true - - it.nextToken().isObjectStart shouldBe true - it.nextToken().isFieldName shouldBe true - it.fieldName() shouldBe "a" - it.nextToken().isNullValue shouldBe true - it.nextToken().isObjectEnd shouldBe true - - it.nextToken().isNumberValue shouldBe true - it.number() shouldBe 1.0 - - it.nextToken().isBooleanValue shouldBe true - it.boolean() shouldBe false - - it.nextToken().isArrayEnd shouldBe true - it.nextToken().isObjectEnd shouldBe true - - it.nextToken().isEmpty shouldBe true - } - - it should "correctly skip next expressions" in { - val json = """{"a":1,"b":["s",true,{"a":null},1.0,false]}""" - val it = json.toTokenIterator.fold(throw _, identity) - it.currentToken().isObjectStart shouldBe true - - it.nextToken().isFieldName shouldBe true - it.fieldName() shouldBe "a" - it.next().skipExpression() - - it.currentToken().isFieldName shouldBe true - it.fieldName() shouldBe "b" - it.next().skipExpression() - it.currentToken().isObjectEnd shouldBe true - - it.nextToken().isEmpty shouldBe true - } - - it should "correctly collect expressions" in { - val json = """{"a":1,"b":["s",true,{"a":null},1.0,false]}""" - val it = json.toTokenIterator.fold(throw _, identity) - it.currentToken().isObjectStart shouldBe true - - it.nextToken().isFieldName shouldBe true - it.fieldName() shouldBe "a" - - val a = it.next().collectExpression() - a.currentToken().isNumberValue shouldBe true - a.number() shouldBe 1 - a.nextToken().isEmpty shouldBe true - - it.currentToken().isFieldName shouldBe true - it.fieldName() shouldBe "b" - - val b = it.next().collectExpression() - b.currentToken().isArrayStart shouldBe true - - b.nextToken().isStringValue shouldBe true - b.string() shouldBe "s" - - b.nextToken().isBooleanValue shouldBe true - b.boolean() shouldBe true - - b.nextToken().isObjectStart shouldBe true - b.nextToken().isFieldName shouldBe true - b.fieldName() shouldBe "a" - b.nextToken().isNullValue shouldBe true - b.nextToken().isObjectEnd shouldBe true - - b.nextToken().isNumberValue shouldBe true - b.number() shouldBe 1.0 - - b.nextToken().isBooleanValue shouldBe true - b.boolean() shouldBe false - - b.nextToken().isArrayEnd shouldBe true - b.nextToken().isEmpty shouldBe true - - it.currentToken().isObjectEnd shouldBe true - - it.nextToken().isEmpty shouldBe true - - } - - it should "generate proper tokens seq" in { - val json = """{"a":1,"b":["s",true,{"a":null},1.0,false]}""" - - json.jsonAsTokensList shouldBe obj( - "a" -> 1, - "b" -> arr( - "s", - true, - obj( - "a" -> null - ), - 1.0, - false - ) - ) - } -} diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterSpec.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterSpec.scala new file mode 100644 index 00000000..9fbe9a0b --- /dev/null +++ b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterSpec.scala @@ -0,0 +1,6 @@ +package tethys.jackson + +import tethys._ +import tethys.writers.tokens.{TokenWriterSpec, TokenWriterProducer} + +class JacksonTokenWriterSpec extends TokenWriterSpec diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterTest.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterTest.scala deleted file mode 100644 index 290a05d6..00000000 --- a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterTest.scala +++ /dev/null @@ -1,105 +0,0 @@ -package tethys.jackson - -import java.io.StringWriter - -import org.scalatest.matchers.should.Matchers -import org.scalatest.flatspec.AnyFlatSpec -import tethys._ -import tethys.writers.tokens.TokenWriter - -class JacksonTokenWriterTest extends AnyFlatSpec with Matchers { - - def iterate(fun: (TokenWriter) => Unit): String = { - val sw = new StringWriter() - val tokenWriter = sw.toTokenWriter - fun(tokenWriter) - tokenWriter.close() - sw.toString - } - - behavior of "JacksonTokenWriter" - - it should "write String value" in { - iterate(_.writeString("string")) shouldBe """"string"""" - } - - it should "write Byte value" in { - iterate(_.writeNumber(1: Byte)) shouldBe """1""" - } - - it should "write Short value" in { - iterate(_.writeNumber(1: Short)) shouldBe """1""" - } - - it should "write Int value" in { - iterate(_.writeNumber(1: Int)) shouldBe """1""" - } - - it should "write Long value" in { - iterate(_.writeNumber(1: Long)) shouldBe """1""" - } - - it should "write Float value" in { - iterate(_.writeNumber(1: Float)) shouldBe """1.0""" - } - - it should "write Double value" in { - iterate(_.writeNumber(1: Double)) shouldBe """1.0""" - } - - it should "write BitInt value" in { - iterate(_.writeNumber(1: BigInt)) shouldBe """1""" - } - - it should "write BigDecimal value" in { - iterate(_.writeNumber(1: BigDecimal)) shouldBe """1""" - } - - it should "write true value" in { - iterate(_.writeBoolean(true)) shouldBe """true""" - } - - it should "write false value" in { - iterate(_.writeBoolean(false)) shouldBe """false""" - } - - it should "write null value" in { - iterate(_.writeNull()) shouldBe """null""" - } - - it should "write object structure" in { - iterate(_.writeObjectStart().writeObjectEnd()) shouldBe """{}""" - } - - it should "write array structure" in { - iterate(_.writeArrayStart().writeArrayEnd()) shouldBe """[]""" - } - - it should "write raw json" in { - iterate( - _.writeRawJson("""{"some" : "raw json"}""") - ) shouldBe """{"some" : "raw json"}""" - } - - it should "write complex object structure" in { - iterate { - _.writeObjectStart() - .writeFieldName("a") - .writeNumber(1) - .writeFieldName("b") - .writeArrayStart() - .writeString("s") - .writeRawJson("false") - .writeBoolean(true) - .writeObjectStart() - .writeFieldName("a") - .writeNull() - .writeObjectEnd() - .writeArrayEnd() - .writeFieldName("c") - .writeRawJson("""{"some" : "raw json"}""") - .writeObjectEnd() - } shouldBe """{"a":1,"b":["s",false,true,{"a":null}],"c":{"some" : "raw json"}}""" - } - -} diff --git a/modules/benchmarks/README.md b/modules/benchmarks/README.md index 5bb18392..d4042824 100644 --- a/modules/benchmarks/README.md +++ b/modules/benchmarks/README.md @@ -20,12 +20,14 @@ zio-json|2001448.661|255068.591|2001.826|237.499|3.439 name \ size|128b|1kb|128kb|1mb|32mb ---|---|---|---|---|--- -tethys-jackson|1819887.866|403267.403|3265.018|300.502|11.786 -pure-jackson|1109155.693|427421.925|3096.09|392.062|10.441 -circe|1348593.267|177497.011|1255.093|157.518|2.706 -play-json|327191.603|47736.655|360.734|49.417|1.064 -spray-json|1172370.195|148580.554|1053.782|142.164|3.425 -zio-json|2191564.981|266636.44|2092.48|239.598|7.347 +tethys|5623657.356|549741.974|4728.468|582.912|16.223 +tethys-jackson|1778419.803|434317.737|3577.799|499.213|11.343 +pure-jackson|1153651.397|420722.96|3177.921|394.1|9.972 +circe|1393264.287|176604.569|1279.973|156.184|2.192 +jsoniter|6036735.563|892874.569|5576.712|727.844|19.491 +play-json|329380.553|48283.895|375.691|49.423|1.065 +spray-json|1167053.231|149003.266|1109.504|138.309|2.95 +zio-json|2034331.785|277147.012|1934.274|238.862|7.332 ![WritingPerformance](./images/WritingPerformance.png) diff --git a/modules/benchmarks/images/WritingPerformance.png b/modules/benchmarks/images/WritingPerformance.png index a9da918d..d4dfbe88 100644 Binary files a/modules/benchmarks/images/WritingPerformance.png and b/modules/benchmarks/images/WritingPerformance.png differ diff --git a/modules/benchmarks/jmh-reader.json b/modules/benchmarks/jmh-reader.json index 41473838..f39feebb 100644 --- a/modules/benchmarks/jmh-reader.json +++ b/modules/benchmarks/jmh-reader.json @@ -1,1964 +1,4 @@ -[ - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128b", - "processorName" : "tethys-jackson" - }, - "primaryMetric" : { - "score" : 1442640.7423825143, - "scoreError" : 78952.05954556566, - "scoreConfidence" : [ - 1363688.6828369487, - 1521592.80192808 - ], - "scorePercentiles" : { - "0.0" : 1424673.1377489888, - "50.0" : 1447698.8286684984, - "90.0" : 1450492.1744440713, - "95.0" : 1450492.1744440713, - "99.0" : 1450492.1744440713, - "99.9" : 1450492.1744440713, - "99.99" : 1450492.1744440713, - "99.999" : 1450492.1744440713, - "99.9999" : 1450492.1744440713, - "100.0" : 1450492.1744440713 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 1424673.1377489888, - 1445230.597521309, - 1450492.1744440713, - 1450167.059815688 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128b", - "processorName" : "pure-jackson" - }, - "primaryMetric" : { - "score" : 1189799.5938527994, - "scoreError" : 55190.13562201335, - "scoreConfidence" : [ - 1134609.458230786, - 1244989.7294748127 - ], - "scorePercentiles" : { - "0.0" : 1181260.403353757, - "50.0" : 1188999.6753648564, - "90.0" : 1199938.621327727, - "95.0" : 1199938.621327727, - "99.0" : 1199938.621327727, - "99.9" : 1199938.621327727, - "99.99" : 1199938.621327727, - "99.999" : 1199938.621327727, - "99.9999" : 1199938.621327727, - "100.0" : 1199938.621327727 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 1181260.403353757, - 1199938.621327727, - 1184426.1072955476, - 1193573.2434341651 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128b", - "processorName" : "circe-jawn" - }, - "primaryMetric" : { - "score" : 866471.5537783348, - "scoreError" : 5292.831154030518, - "scoreConfidence" : [ - 861178.7226243042, - 871764.3849323653 - ], - "scorePercentiles" : { - "0.0" : 865306.6927368287, - "50.0" : 866678.8023450469, - "90.0" : 867221.9176864165, - "95.0" : 867221.9176864165, - "99.0" : 867221.9176864165, - "99.9" : 867221.9176864165, - "99.99" : 867221.9176864165, - "99.999" : 867221.9176864165, - "99.9999" : 867221.9176864165, - "100.0" : 867221.9176864165 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 866620.5653716372, - 866737.0393184565, - 867221.9176864165, - 865306.6927368287 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128b", - "processorName" : "circe-jackson" - }, - "primaryMetric" : { - "score" : 348613.15950133896, - "scoreError" : 4090.8100080596914, - "scoreConfidence" : [ - 344522.3494932793, - 352703.9695093986 - ], - "scorePercentiles" : { - "0.0" : 347717.6375497792, - "50.0" : 348790.33516368154, - "90.0" : 349154.3301282136, - "95.0" : 349154.3301282136, - "99.0" : 349154.3301282136, - "99.9" : 349154.3301282136, - "99.99" : 349154.3301282136, - "99.999" : 349154.3301282136, - "99.9999" : 349154.3301282136, - "100.0" : 349154.3301282136 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 347717.6375497792, - 348640.8739915605, - 348939.79633580265, - 349154.3301282136 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128b", - "processorName" : "play-json" - }, - "primaryMetric" : { - "score" : 534060.399904814, - "scoreError" : 6799.148614768325, - "scoreConfidence" : [ - 527261.2512900457, - 540859.5485195823 - ], - "scorePercentiles" : { - "0.0" : 533261.5614296852, - "50.0" : 533703.7411456113, - "90.0" : 535572.5558983481, - "95.0" : 535572.5558983481, - "99.0" : 535572.5558983481, - "99.9" : 535572.5558983481, - "99.99" : 535572.5558983481, - "99.999" : 535572.5558983481, - "99.9999" : 535572.5558983481, - "100.0" : 535572.5558983481 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 533437.2341277611, - 533261.5614296852, - 535572.5558983481, - 533970.2481634615 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128b", - "processorName" : "spray-json" - }, - "primaryMetric" : { - "score" : 971689.2127926768, - "scoreError" : 18617.12137459307, - "scoreConfidence" : [ - 953072.0914180836, - 990306.3341672699 - ], - "scorePercentiles" : { - "0.0" : 970126.0228253725, - "50.0" : 970311.7247863426, - "90.0" : 976007.3787726497, - "95.0" : 976007.3787726497, - "99.0" : 976007.3787726497, - "99.9" : 976007.3787726497, - "99.99" : 976007.3787726497, - "99.999" : 976007.3787726497, - "99.9999" : 976007.3787726497, - "100.0" : 976007.3787726497 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 976007.3787726497, - 970222.9429407229, - 970400.5066319621, - 970126.0228253725 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128b", - "processorName" : "zio-json" - }, - "primaryMetric" : { - "score" : 2001448.6610900809, - "scoreError" : 7651.916564135762, - "scoreConfidence" : [ - 1993796.744525945, - 2009100.5776542167 - ], - "scorePercentiles" : { - "0.0" : 1999755.8992002185, - "50.0" : 2001784.124021053, - "90.0" : 2002470.497117998, - "95.0" : 2002470.497117998, - "99.0" : 2002470.497117998, - "99.9" : 2002470.497117998, - "99.99" : 2002470.497117998, - "99.999" : 2002470.497117998, - "99.9999" : 2002470.497117998, - "100.0" : 2002470.497117998 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 2002470.497117998, - 2001973.7530752632, - 2001594.494966843, - 1999755.8992002185 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1kb", - "processorName" : "tethys-jackson" - }, - "primaryMetric" : { - "score" : 160561.91404439212, - "scoreError" : 4174.087416426447, - "scoreConfidence" : [ - 156387.82662796567, - 164736.00146081857 - ], - "scorePercentiles" : { - "0.0" : 160034.28939428605, - "50.0" : 160370.037503851, - "90.0" : 161473.29177558052, - "95.0" : 161473.29177558052, - "99.0" : 161473.29177558052, - "99.9" : 161473.29177558052, - "99.99" : 161473.29177558052, - "99.999" : 161473.29177558052, - "99.9999" : 161473.29177558052, - "100.0" : 161473.29177558052 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 160034.28939428605, - 161473.29177558052, - 160184.15701617362, - 160555.91799152835 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1kb", - "processorName" : "pure-jackson" - }, - "primaryMetric" : { - "score" : 212591.26322673875, - "scoreError" : 820.8636873381179, - "scoreConfidence" : [ - 211770.39953940065, - 213412.12691407686 - ], - "scorePercentiles" : { - "0.0" : 212410.35066246407, - "50.0" : 212623.53671140777, - "90.0" : 212707.62882167546, - "95.0" : 212707.62882167546, - "99.0" : 212707.62882167546, - "99.9" : 212707.62882167546, - "99.99" : 212707.62882167546, - "99.999" : 212707.62882167546, - "99.9999" : 212707.62882167546, - "100.0" : 212707.62882167546 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 212628.8313956589, - 212410.35066246407, - 212618.2420271566, - 212707.62882167546 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1kb", - "processorName" : "circe-jawn" - }, - "primaryMetric" : { - "score" : 112596.09569979832, - "scoreError" : 3097.504155707831, - "scoreConfidence" : [ - 109498.59154409048, - 115693.59985550615 - ], - "scorePercentiles" : { - "0.0" : 112013.72842408167, - "50.0" : 112603.01135833334, - "90.0" : 113164.63165844487, - "95.0" : 113164.63165844487, - "99.0" : 113164.63165844487, - "99.9" : 113164.63165844487, - "99.99" : 113164.63165844487, - "99.999" : 113164.63165844487, - "99.9999" : 113164.63165844487, - "100.0" : 113164.63165844487 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 112718.82671181016, - 112487.19600485654, - 112013.72842408167, - 113164.63165844487 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1kb", - "processorName" : "circe-jackson" - }, - "primaryMetric" : { - "score" : 48776.93307660853, - "scoreError" : 1108.925216517014, - "scoreConfidence" : [ - 47668.00786009152, - 49885.858293125544 - ], - "scorePercentiles" : { - "0.0" : 48577.656124694724, - "50.0" : 48767.20248107644, - "90.0" : 48995.671219586526, - "95.0" : 48995.671219586526, - "99.0" : 48995.671219586526, - "99.9" : 48995.671219586526, - "99.99" : 48995.671219586526, - "99.999" : 48995.671219586526, - "99.9999" : 48995.671219586526, - "100.0" : 48995.671219586526 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 48995.671219586526, - 48784.52668914586, - 48749.87827300703, - 48577.656124694724 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1kb", - "processorName" : "play-json" - }, - "primaryMetric" : { - "score" : 64804.73470720218, - "scoreError" : 409.68292239984754, - "scoreConfidence" : [ - 64395.05178480233, - 65214.41762960203 - ], - "scorePercentiles" : { - "0.0" : 64745.4846006451, - "50.0" : 64796.907385312996, - "90.0" : 64879.639457537625, - "95.0" : 64879.639457537625, - "99.0" : 64879.639457537625, - "99.9" : 64879.639457537625, - "99.99" : 64879.639457537625, - "99.999" : 64879.639457537625, - "99.9999" : 64879.639457537625, - "100.0" : 64879.639457537625 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 64834.42021945477, - 64879.639457537625, - 64759.39455117122, - 64745.4846006451 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1kb", - "processorName" : "spray-json" - }, - "primaryMetric" : { - "score" : 121363.84547637065, - "scoreError" : 868.2404659021983, - "scoreConfidence" : [ - 120495.60501046845, - 122232.08594227285 - ], - "scorePercentiles" : { - "0.0" : 121173.45254790495, - "50.0" : 121396.4259916749, - "90.0" : 121489.07737422787, - "95.0" : 121489.07737422787, - "99.0" : 121489.07737422787, - "99.9" : 121489.07737422787, - "99.99" : 121489.07737422787, - "99.999" : 121489.07737422787, - "99.9999" : 121489.07737422787, - "100.0" : 121489.07737422787 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 121403.61275627548, - 121389.23922707431, - 121489.07737422787, - 121173.45254790495 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1kb", - "processorName" : "zio-json" - }, - "primaryMetric" : { - "score" : 255068.59053340956, - "scoreError" : 883.3864208655376, - "scoreConfidence" : [ - 254185.20411254402, - 255951.9769542751 - ], - "scorePercentiles" : { - "0.0" : 254882.52912064738, - "50.0" : 255108.6583979822, - "90.0" : 255174.51621702657, - "95.0" : 255174.51621702657, - "99.0" : 255174.51621702657, - "99.9" : 255174.51621702657, - "99.99" : 255174.51621702657, - "99.999" : 255174.51621702657, - "99.9999" : 255174.51621702657, - "100.0" : 255174.51621702657 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 254882.52912064738, - 255167.8818117706, - 255049.43498419374, - 255174.51621702657 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128kb", - "processorName" : "tethys-jackson" - }, - "primaryMetric" : { - "score" : 1342.1847033298734, - "scoreError" : 16.20391937035811, - "scoreConfidence" : [ - 1325.9807839595153, - 1358.3886227002315 - ], - "scorePercentiles" : { - "0.0" : 1338.5377154330088, - "50.0" : 1342.9812388787618, - "90.0" : 1344.2386201289612, - "95.0" : 1344.2386201289612, - "99.0" : 1344.2386201289612, - "99.9" : 1344.2386201289612, - "99.99" : 1344.2386201289612, - "99.999" : 1344.2386201289612, - "99.9999" : 1344.2386201289612, - "100.0" : 1344.2386201289612 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 1344.2386201289612, - 1343.1758619846332, - 1342.7866157728904, - 1338.5377154330088 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128kb", - "processorName" : "pure-jackson" - }, - "primaryMetric" : { - "score" : 1721.6229988447876, - "scoreError" : 146.59883259580164, - "scoreConfidence" : [ - 1575.024166248986, - 1868.2218314405893 - ], - "scorePercentiles" : { - "0.0" : 1699.2538695809183, - "50.0" : 1721.8093755250118, - "90.0" : 1743.6193747482089, - "95.0" : 1743.6193747482089, - "99.0" : 1743.6193747482089, - "99.9" : 1743.6193747482089, - "99.99" : 1743.6193747482089, - "99.999" : 1743.6193747482089, - "99.9999" : 1743.6193747482089, - "100.0" : 1743.6193747482089 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 1699.2538695809183, - 1705.0803445075178, - 1738.5384065425058, - 1743.6193747482089 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128kb", - "processorName" : "circe-jawn" - }, - "primaryMetric" : { - "score" : 945.4187760649006, - "scoreError" : 2.183751802419896, - "scoreConfidence" : [ - 943.2350242624807, - 947.6025278673205 - ], - "scorePercentiles" : { - "0.0" : 945.1250529808461, - "50.0" : 945.3246784380651, - "90.0" : 945.9006944026257, - "95.0" : 945.9006944026257, - "99.0" : 945.9006944026257, - "99.9" : 945.9006944026257, - "99.99" : 945.9006944026257, - "99.999" : 945.9006944026257, - "99.9999" : 945.9006944026257, - "100.0" : 945.9006944026257 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 945.9006944026257, - 945.2682026376795, - 945.3811542384509, - 945.1250529808461 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128kb", - "processorName" : "circe-jackson" - }, - "primaryMetric" : { - "score" : 363.00530700456716, - "scoreError" : 2.5284431265662755, - "scoreConfidence" : [ - 360.47686387800087, - 365.53375013113344 - ], - "scorePercentiles" : { - "0.0" : 362.7165362775112, - "50.0" : 362.861798791369, - "90.0" : 363.58109415801937, - "95.0" : 363.58109415801937, - "99.0" : 363.58109415801937, - "99.9" : 363.58109415801937, - "99.99" : 363.58109415801937, - "99.999" : 363.58109415801937, - "99.9999" : 363.58109415801937, - "100.0" : 363.58109415801937 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 362.90173654915014, - 362.8218610335879, - 363.58109415801937, - 362.7165362775112 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128kb", - "processorName" : "play-json" - }, - "primaryMetric" : { - "score" : 499.5742919458588, - "scoreError" : 6.093313640347425, - "scoreConfidence" : [ - 493.48097830551137, - 505.66760558620626 - ], - "scorePercentiles" : { - "0.0" : 498.7420007630053, - "50.0" : 499.37242517065727, - "90.0" : 500.81031667911543, - "95.0" : 500.81031667911543, - "99.0" : 500.81031667911543, - "99.9" : 500.81031667911543, - "99.99" : 500.81031667911543, - "99.999" : 500.81031667911543, - "99.9999" : 500.81031667911543, - "100.0" : 500.81031667911543 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 500.81031667911543, - 499.7999068452079, - 498.9449434961067, - 498.7420007630053 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128kb", - "processorName" : "spray-json" - }, - "primaryMetric" : { - "score" : 1028.7599672304448, - "scoreError" : 6.1122124547278345, - "scoreConfidence" : [ - 1022.647754775717, - 1034.8721796851726 - ], - "scorePercentiles" : { - "0.0" : 1027.8687142210626, - "50.0" : 1028.5465020153126, - "90.0" : 1030.0781506700912, - "95.0" : 1030.0781506700912, - "99.0" : 1030.0781506700912, - "99.9" : 1030.0781506700912, - "99.99" : 1030.0781506700912, - "99.999" : 1030.0781506700912, - "99.9999" : 1030.0781506700912, - "100.0" : 1030.0781506700912 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 1028.7210660331664, - 1028.3719379974589, - 1027.8687142210626, - 1030.0781506700912 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "128kb", - "processorName" : "zio-json" - }, - "primaryMetric" : { - "score" : 2001.826408239838, - "scoreError" : 42.480581859391826, - "scoreConfidence" : [ - 1959.3458263804462, - 2044.3069900992298 - ], - "scorePercentiles" : { - "0.0" : 1994.0433170035403, - "50.0" : 2001.5805047947372, - "90.0" : 2010.1013063663365, - "95.0" : 2010.1013063663365, - "99.0" : 2010.1013063663365, - "99.9" : 2010.1013063663365, - "99.99" : 2010.1013063663365, - "99.999" : 2010.1013063663365, - "99.9999" : 2010.1013063663365, - "100.0" : 2010.1013063663365 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 2002.0692642253507, - 2001.0917453641237, - 1994.0433170035403, - 2010.1013063663365 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1mb", - "processorName" : "tethys-jackson" - }, - "primaryMetric" : { - "score" : 160.4477490800406, - "scoreError" : 2.201194254702574, - "scoreConfidence" : [ - 158.24655482533802, - 162.6489433347432 - ], - "scorePercentiles" : { - "0.0" : 160.06205164274286, - "50.0" : 160.4395764682343, - "90.0" : 160.849791740951, - "95.0" : 160.849791740951, - "99.0" : 160.849791740951, - "99.9" : 160.849791740951, - "99.99" : 160.849791740951, - "99.999" : 160.849791740951, - "99.9999" : 160.849791740951, - "100.0" : 160.849791740951 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 160.06205164274286, - 160.3025239132917, - 160.849791740951, - 160.5766290231769 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1mb", - "processorName" : "pure-jackson" - }, - "primaryMetric" : { - "score" : 184.57242130589123, - "scoreError" : 32.95243699325334, - "scoreConfidence" : [ - 151.61998431263788, - 217.52485829914457 - ], - "scorePercentiles" : { - "0.0" : 176.9272357038962, - "50.0" : 187.02461410877956, - "90.0" : 187.31322130210955, - "95.0" : 187.31322130210955, - "99.0" : 187.31322130210955, - "99.9" : 187.31322130210955, - "99.99" : 187.31322130210955, - "99.999" : 187.31322130210955, - "99.9999" : 187.31322130210955, - "100.0" : 187.31322130210955 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 187.31322130210955, - 187.13662544786501, - 186.9126027696941, - 176.9272357038962 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1mb", - "processorName" : "circe-jawn" - }, - "primaryMetric" : { - "score" : 124.76672484216905, - "scoreError" : 0.33188848774535284, - "scoreConfidence" : [ - 124.4348363544237, - 125.09861332991441 - ], - "scorePercentiles" : { - "0.0" : 124.72972836530786, - "50.0" : 124.7491752546027, - "90.0" : 124.83882049416289, - "95.0" : 124.83882049416289, - "99.0" : 124.83882049416289, - "99.9" : 124.83882049416289, - "99.99" : 124.83882049416289, - "99.999" : 124.83882049416289, - "99.9999" : 124.83882049416289, - "100.0" : 124.83882049416289 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 124.83882049416289, - 124.73005609434921, - 124.7682944148562, - 124.72972836530786 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1mb", - "processorName" : "circe-jackson" - }, - "primaryMetric" : { - "score" : 39.48507722977758, - "scoreError" : 0.40852989142117696, - "scoreConfidence" : [ - 39.076547338356406, - 39.893607121198755 - ], - "scorePercentiles" : { - "0.0" : 39.41285789748825, - "50.0" : 39.48438984253872, - "90.0" : 39.55867133654463, - "95.0" : 39.55867133654463, - "99.0" : 39.55867133654463, - "99.9" : 39.55867133654463, - "99.99" : 39.55867133654463, - "99.999" : 39.55867133654463, - "99.9999" : 39.55867133654463, - "100.0" : 39.55867133654463 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 39.55867133654463, - 39.41285789748825, - 39.510445623948755, - 39.45833406112869 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1mb", - "processorName" : "play-json" - }, - "primaryMetric" : { - "score" : 43.91015336909207, - "scoreError" : 0.8530360585248596, - "scoreConfidence" : [ - 43.057117310567214, - 44.76318942761693 - ], - "scorePercentiles" : { - "0.0" : 43.76700960688348, - "50.0" : 43.921196372174855, - "90.0" : 44.03121112513508, - "95.0" : 44.03121112513508, - "99.0" : 44.03121112513508, - "99.9" : 44.03121112513508, - "99.99" : 44.03121112513508, - "99.999" : 44.03121112513508, - "99.9999" : 44.03121112513508, - "100.0" : 44.03121112513508 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 43.76700960688348, - 44.01309180369185, - 43.82930094065786, - 44.03121112513508 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1mb", - "processorName" : "spray-json" - }, - "primaryMetric" : { - "score" : 123.77137034655759, - "scoreError" : 1.3165189495625613, - "scoreConfidence" : [ - 122.45485139699502, - 125.08788929612015 - ], - "scorePercentiles" : { - "0.0" : 123.51933731134018, - "50.0" : 123.79114484148198, - "90.0" : 123.98385439192622, - "95.0" : 123.98385439192622, - "99.0" : 123.98385439192622, - "99.9" : 123.98385439192622, - "99.99" : 123.98385439192622, - "99.999" : 123.98385439192622, - "99.9999" : 123.98385439192622, - "100.0" : 123.98385439192622 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 123.98385439192622, - 123.87794615660056, - 123.7043435263634, - 123.51933731134018 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "1mb", - "processorName" : "zio-json" - }, - "primaryMetric" : { - "score" : 237.49920878178992, - "scoreError" : 0.8468180645129579, - "scoreConfidence" : [ - 236.65239071727697, - 238.34602684630286 - ], - "scorePercentiles" : { - "0.0" : 237.38379714534622, - "50.0" : 237.47990141743617, - "90.0" : 237.65323514694114, - "95.0" : 237.65323514694114, - "99.0" : 237.65323514694114, - "99.9" : 237.65323514694114, - "99.99" : 237.65323514694114, - "99.999" : 237.65323514694114, - "99.9999" : 237.65323514694114, - "100.0" : 237.65323514694114 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 237.38379714534622, - 237.65323514694114, - 237.39704710989793, - 237.56275572497444 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "32mb", - "processorName" : "tethys-jackson" - }, - "primaryMetric" : { - "score" : 3.0137784534990244, - "scoreError" : 0.40756789287665157, - "scoreConfidence" : [ - 2.606210560622373, - 3.421346346375676 - ], - "scorePercentiles" : { - "0.0" : 2.929741526264833, - "50.0" : 3.0227237465019052, - "90.0" : 3.079924794727454, - "95.0" : 3.079924794727454, - "99.0" : 3.079924794727454, - "99.9" : 3.079924794727454, - "99.99" : 3.079924794727454, - "99.999" : 3.079924794727454, - "99.9999" : 3.079924794727454, - "100.0" : 3.079924794727454 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 3.0097527508957675, - 3.035694742108043, - 2.929741526264833, - 3.079924794727454 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "32mb", - "processorName" : "pure-jackson" - }, - "primaryMetric" : { - "score" : 3.7330143364309074, - "scoreError" : 1.0340570672985834, - "scoreConfidence" : [ - 2.698957269132324, - 4.76707140372949 - ], - "scorePercentiles" : { - "0.0" : 3.5812111456422144, - "50.0" : 3.7373796051402604, - "90.0" : 3.876086989800895, - "95.0" : 3.876086989800895, - "99.0" : 3.876086989800895, - "99.9" : 3.876086989800895, - "99.99" : 3.876086989800895, - "99.999" : 3.876086989800895, - "99.9999" : 3.876086989800895, - "100.0" : 3.876086989800895 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 3.6084057431955556, - 3.876086989800895, - 3.866353467084965, - 3.5812111456422144 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "32mb", - "processorName" : "circe-jawn" - }, - "primaryMetric" : { - "score" : 1.5225411461155023, - "scoreError" : 0.39755599886945797, - "scoreConfidence" : [ - 1.1249851472460444, - 1.9200971449849602 - ], - "scorePercentiles" : { - "0.0" : 1.4362130009126042, - "50.0" : 1.5373792525243788, - "90.0" : 1.5791930785006478, - "95.0" : 1.5791930785006478, - "99.0" : 1.5791930785006478, - "99.9" : 1.5791930785006478, - "99.99" : 1.5791930785006478, - "99.999" : 1.5791930785006478, - "99.9999" : 1.5791930785006478, - "100.0" : 1.5791930785006478 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 1.5486181533002588, - 1.5791930785006478, - 1.4362130009126042, - 1.5261403517484986 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "32mb", - "processorName" : "circe-jackson" - }, - "primaryMetric" : { - "score" : 0.8719602908897323, - "scoreError" : 0.13303923204223653, - "scoreConfidence" : [ - 0.7389210588474957, - 1.0049995229319688 - ], - "scorePercentiles" : { - "0.0" : 0.852918679952709, - "50.0" : 0.870454311975011, - "90.0" : 0.8940138596561977, - "95.0" : 0.8940138596561977, - "99.0" : 0.8940138596561977, - "99.9" : 0.8940138596561977, - "99.99" : 0.8940138596561977, - "99.999" : 0.8940138596561977, - "99.9999" : 0.8940138596561977, - "100.0" : 0.8940138596561977 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 0.8559954990209021, - 0.852918679952709, - 0.8940138596561977, - 0.8849131249291199 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "32mb", - "processorName" : "play-json" - }, - "primaryMetric" : { - "score" : 0.9268646470493817, - "scoreError" : 0.07142129572211389, - "scoreConfidence" : [ - 0.8554433513272678, - 0.9982859427714955 - ], - "scorePercentiles" : { - "0.0" : 0.9158881941393551, - "50.0" : 0.9248947313372281, - "90.0" : 0.9417809313837151, - "95.0" : 0.9417809313837151, - "99.0" : 0.9417809313837151, - "99.9" : 0.9417809313837151, - "99.99" : 0.9417809313837151, - "99.999" : 0.9417809313837151, - "99.9999" : 0.9417809313837151, - "100.0" : 0.9417809313837151 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 0.9220897620361919, - 0.9158881941393551, - 0.9276997006382643, - 0.9417809313837151 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "32mb", - "processorName" : "spray-json" - }, - "primaryMetric" : { - "score" : 2.188200726362677, - "scoreError" : 0.3354607091980212, - "scoreConfidence" : [ - 1.8527400171646558, - 2.523661435560698 - ], - "scorePercentiles" : { - "0.0" : 2.1401609225442733, - "50.0" : 2.1869952732658535, - "90.0" : 2.238651436374728, - "95.0" : 2.238651436374728, - "99.0" : 2.238651436374728, - "99.9" : 2.238651436374728, - "99.99" : 2.238651436374728, - "99.999" : 2.238651436374728, - "99.9999" : 2.238651436374728, - "100.0" : 2.238651436374728 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 2.2271751777085904, - 2.1401609225442733, - 2.1468153688231166, - 2.238651436374728 - ] - ] - }, - "secondaryMetrics" : { - } - }, - { - "jmhVersion" : "1.36", - "benchmark" : "json.bench.JmhReaderBench.bench", - "mode" : "thrpt", - "threads" : 1, - "forks" : 1, - "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", - "jvmArgs" : [ - "-Xms1G", - "-Xmx1G" - ], - "jdkVersion" : "17.0.7", - "vmName" : "OpenJDK 64-Bit Server VM", - "vmVersion" : "17.0.7+7-LTS", - "warmupIterations" : 4, - "warmupTime" : "5 s", - "warmupBatchSize" : 1, - "measurementIterations" : 4, - "measurementTime" : "5 s", - "measurementBatchSize" : 1, - "params" : { - "jsonSize" : "32mb", - "processorName" : "zio-json" - }, - "primaryMetric" : { - "score" : 3.438909701909425, - "scoreError" : 0.28976027522897935, - "scoreConfidence" : [ - 3.1491494266804456, - 3.7286699771384044 - ], - "scorePercentiles" : { - "0.0" : 3.398288711481961, - "50.0" : 3.437554759758812, - "90.0" : 3.482240576638115, - "95.0" : 3.482240576638115, - "99.0" : 3.482240576638115, - "99.9" : 3.482240576638115, - "99.99" : 3.482240576638115, - "99.999" : 3.482240576638115, - "99.9999" : 3.482240576638115, - "100.0" : 3.482240576638115 - }, - "scoreUnit" : "ops/s", - "rawData" : [ - [ - 3.398288711481961, - 3.482240576638115, - 3.402193975664248, - 3.472915543853375 - ] - ] - }, - "secondaryMetrics" : { - } - } -] - - +"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: jsonSize","Param: processorName" +"json.bench.JmhReaderBench.bench","thrpt",1,4,1845394.186985,74170.513403,"ops/s",128b,tethys +"json.bench.JmhReaderBench.bench","thrpt",1,4,1498716.500223,20783.273473,"ops/s",128b,tethys-jackson +"json.bench.JmhWriterBench.bench","thrpt",1,4,5343206.315903,132400.559328,"ops/s",128b,tethys diff --git a/modules/benchmarks/jmh-reader.log b/modules/benchmarks/jmh-reader.log new file mode 100644 index 00000000..acd1abe2 --- /dev/null +++ b/modules/benchmarks/jmh-reader.log @@ -0,0 +1,113 @@ +# JMH version: 1.37 +# VM version: JDK 17.0.7, OpenJDK 64-Bit Server VM, 17.0.7+7-LTS +# VM invoker: /Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java +# VM options: -Xms1G -Xmx1G +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 4 iterations, 5 s each +# Measurement: 4 iterations, 5 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: json.bench.JmhReaderBench.bench +# Parameters: (jsonSize = 128b, processorName = tethys) + +# Run progress: 0.00% complete, ETA 00:01:28 +# Fork: 1 of 1 +# Warmup Iteration 1: 1784680.808 ops/s +# Warmup Iteration 2: 1846010.197 ops/s +# Warmup Iteration 3: 1841240.555 ops/s +# Warmup Iteration 4: 1835400.577 ops/s +Iteration 1: 1850061.894 ops/s +Iteration 2: 1835378.960 ops/s +Iteration 3: 1859444.262 ops/s +Iteration 4: 1836691.632 ops/s + + +Result "json.bench.JmhReaderBench.bench": + 1845394.187 ±(99.9%) 74170.513 ops/s [Average] + (min, avg, max) = (1835378.960, 1845394.187, 1859444.262), stdev = 11477.969 + CI (99.9%): [1771223.674, 1919564.700] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 17.0.7, OpenJDK 64-Bit Server VM, 17.0.7+7-LTS +# VM invoker: /Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java +# VM options: -Xms1G -Xmx1G +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 4 iterations, 5 s each +# Measurement: 4 iterations, 5 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: json.bench.JmhReaderBench.bench +# Parameters: (jsonSize = 128b, processorName = tethys-jackson) + +# Run progress: 45.45% complete, ETA 00:00:48 +# Fork: 1 of 1 +# Warmup Iteration 1: 1436200.404 ops/s +# Warmup Iteration 2: 1497128.016 ops/s +# Warmup Iteration 3: 1488807.518 ops/s +# Warmup Iteration 4: 1495728.184 ops/s +Iteration 1: 1498466.737 ops/s +Iteration 2: 1498386.191 ops/s +Iteration 3: 1495089.091 ops/s +Iteration 4: 1502923.983 ops/s + + +Result "json.bench.JmhReaderBench.bench": + 1498716.500 ±(99.9%) 20783.273 ops/s [Average] + (min, avg, max) = (1495089.091, 1498716.500, 1502923.983), stdev = 3216.235 + CI (99.9%): [1477933.227, 1519499.774] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 17.0.7, OpenJDK 64-Bit Server VM, 17.0.7+7-LTS +# VM invoker: /Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java +# VM options: -Xms1G -Xmx1G +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 4 iterations, 1 s each +# Measurement: 4 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: json.bench.JmhWriterBench.bench +# Parameters: (jsonSize = 128b, processorName = tethys) + +# Run progress: 90.91% complete, ETA 00:00:08 +# Fork: 1 of 1 +# Warmup Iteration 1: 4413936.316 ops/s +# Warmup Iteration 2: 5379633.919 ops/s +# Warmup Iteration 3: 5391641.469 ops/s +# Warmup Iteration 4: 5370543.532 ops/s +Iteration 1: 5356394.259 ops/s +Iteration 2: 5314757.444 ops/s +Iteration 3: 5359772.820 ops/s +Iteration 4: 5341900.740 ops/s + + +Result "json.bench.JmhWriterBench.bench": + 5343206.316 ±(99.9%) 132400.559 ops/s [Average] + (min, avg, max) = (5314757.444, 5343206.316, 5359772.820), stdev = 20489.133 + CI (99.9%): [5210805.757, 5475606.875] (assumes normal distribution) + + +# Run complete. Total time: 00:01:28 + +REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on +why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial +experiments, perform baseline and negative tests that provide experimental control, make sure +the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. +Do not assume the numbers tell you what you want them to tell. + +NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise +extra caution when trusting the results, look into the generated code to check the benchmark still +works, and factor in a small probability of new VM bugs. Additionally, while comparisons between +different JVMs are already problematic, the performance difference caused by different Blackhole +modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. + +Benchmark (jsonSize) (processorName) Mode Cnt Score Error Units +JmhReaderBench.bench 128b tethys thrpt 4 1845394.187 ± 74170.513 ops/s +JmhReaderBench.bench 128b tethys-jackson thrpt 4 1498716.500 ± 20783.273 ops/s +JmhWriterBench.bench 128b tethys thrpt 4 5343206.316 ± 132400.559 ops/s + +Benchmark result is saved to jmh-reader.json diff --git a/modules/benchmarks/jmh-writer.json b/modules/benchmarks/jmh-writer.json index 5227eab3..3905b378 100644 --- a/modules/benchmarks/jmh-writer.json +++ b/modules/benchmarks/jmh-writer.json @@ -1,6 +1,6 @@ [ { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -14,41 +14,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128b", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 5623657.356338973, + "scoreError" : 621518.5137114808, + "scoreConfidence" : [ + 5002138.842627493, + 6245175.870050454 + ], + "scorePercentiles" : { + "0.0" : 5542828.219103702, + "50.0" : 5594806.800902309, + "90.0" : 5762187.604447574, + "95.0" : 5762187.604447574, + "99.0" : 5762187.604447574, + "99.9" : 5762187.604447574, + "99.99" : 5762187.604447574, + "99.999" : 5762187.604447574, + "99.9999" : 5762187.604447574, + "100.0" : 5762187.604447574 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 5762187.604447574, + 5608287.904018748, + 5542828.219103702, + 5581325.69778587 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 1819887.866280839, - "scoreError" : 204446.28818611056, + "score" : 1778419.8031546003, + "scoreError" : 88448.6938156392, "scoreConfidence" : [ - 1615441.5780947285, - 2024334.1544669496 + 1689971.109338961, + 1866868.4969702396 ], "scorePercentiles" : { - "0.0" : 1787500.8132402124, - "50.0" : 1822320.2268073645, - "90.0" : 1847410.1982684152, - "95.0" : 1847410.1982684152, - "99.0" : 1847410.1982684152, - "99.9" : 1847410.1982684152, - "99.99" : 1847410.1982684152, - "99.999" : 1847410.1982684152, - "99.9999" : 1847410.1982684152, - "100.0" : 1847410.1982684152 + "0.0" : 1758832.1560085032, + "50.0" : 1782241.358763401, + "90.0" : 1790364.3390830962, + "95.0" : 1790364.3390830962, + "99.0" : 1790364.3390830962, + "99.9" : 1790364.3390830962, + "99.99" : 1790364.3390830962, + "99.999" : 1790364.3390830962, + "99.9999" : 1790364.3390830962, + "100.0" : 1790364.3390830962 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1847410.1982684152, - 1846658.5849204224, - 1797981.8686943066, - 1787500.8132402124 + 1790364.3390830962, + 1780440.0720526075, + 1784042.645474194, + 1758832.1560085032 ] ] }, @@ -56,7 +112,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -70,41 +126,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128b", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 6036735.562977094, + "scoreError" : 100765.38118882457, + "scoreConfidence" : [ + 5935970.181788269, + 6137500.944165919 + ], + "scorePercentiles" : { + "0.0" : 6016191.768396554, + "50.0" : 6038824.112508209, + "90.0" : 6053102.258495404, + "95.0" : 6053102.258495404, + "99.0" : 6053102.258495404, + "99.9" : 6053102.258495404, + "99.99" : 6053102.258495404, + "99.999" : 6053102.258495404, + "99.9999" : 6053102.258495404, + "100.0" : 6053102.258495404 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 6053102.258495404, + 6042750.721029169, + 6016191.768396554, + 6034897.50398725 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 1109155.6927294005, - "scoreError" : 11722.369381590592, + "score" : 1153651.396793026, + "scoreError" : 43091.44637822818, "scoreConfidence" : [ - 1097433.32334781, - 1120878.062110991 + 1110559.9504147978, + 1196742.8431712543 ], "scorePercentiles" : { - "0.0" : 1107159.6211813919, - "50.0" : 1109266.9122699166, - "90.0" : 1110929.3251963765, - "95.0" : 1110929.3251963765, - "99.0" : 1110929.3251963765, - "99.9" : 1110929.3251963765, - "99.99" : 1110929.3251963765, - "99.999" : 1110929.3251963765, - "99.9999" : 1110929.3251963765, - "100.0" : 1110929.3251963765 + "0.0" : 1148131.5116133774, + "50.0" : 1151584.3156358043, + "90.0" : 1163305.4442871178, + "95.0" : 1163305.4442871178, + "99.0" : 1163305.4442871178, + "99.9" : 1163305.4442871178, + "99.99" : 1163305.4442871178, + "99.999" : 1163305.4442871178, + "99.9999" : 1163305.4442871178, + "100.0" : 1163305.4442871178 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1107159.6211813919, - 1110432.569796542, - 1110929.3251963765, - 1108101.2547432913 + 1150813.2051328751, + 1163305.4442871178, + 1152355.4261387335, + 1148131.5116133774 ] ] }, @@ -112,7 +224,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -126,41 +238,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "circe" }, "primaryMetric" : { - "score" : 1348593.2665467546, - "scoreError" : 16725.42285120894, + "score" : 1393264.287496805, + "scoreError" : 75110.6708440298, "scoreConfidence" : [ - 1331867.8436955458, - 1365318.6893979635 + 1318153.616652775, + 1468374.9583408348 ], "scorePercentiles" : { - "0.0" : 1344915.614381369, - "50.0" : 1349258.9734497662, - "90.0" : 1350939.5049061175, - "95.0" : 1350939.5049061175, - "99.0" : 1350939.5049061175, - "99.9" : 1350939.5049061175, - "99.99" : 1350939.5049061175, - "99.999" : 1350939.5049061175, - "99.9999" : 1350939.5049061175, - "100.0" : 1350939.5049061175 + "0.0" : 1379468.2130725912, + "50.0" : 1395013.830156555, + "90.0" : 1403561.2766015183, + "95.0" : 1403561.2766015183, + "99.0" : 1403561.2766015183, + "99.9" : 1403561.2766015183, + "99.99" : 1403561.2766015183, + "99.999" : 1403561.2766015183, + "99.9999" : 1403561.2766015183, + "100.0" : 1403561.2766015183 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1348957.93100329, - 1349560.0158962426, - 1350939.5049061175, - 1344915.614381369 + 1402184.3592851234, + 1387843.3010279862, + 1403561.2766015183, + 1379468.2130725912 ] ] }, @@ -168,7 +280,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -182,41 +294,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 327191.60334054136, - "scoreError" : 16833.393674908828, + "score" : 329380.55274132255, + "scoreError" : 7549.991291541363, "scoreConfidence" : [ - 310358.2096656325, - 344024.9970154502 + 321830.5614497812, + 336930.5440328639 ], "scorePercentiles" : { - "0.0" : 323284.24612123315, - "50.0" : 328479.9201514422, - "90.0" : 328522.32693804795, - "95.0" : 328522.32693804795, - "99.0" : 328522.32693804795, - "99.9" : 328522.32693804795, - "99.99" : 328522.32693804795, - "99.999" : 328522.32693804795, - "99.9999" : 328522.32693804795, - "100.0" : 328522.32693804795 + "0.0" : 328093.33984041726, + "50.0" : 329517.90137734724, + "90.0" : 330393.0683701786, + "95.0" : 330393.0683701786, + "99.0" : 330393.0683701786, + "99.9" : 330393.0683701786, + "99.99" : 330393.0683701786, + "99.999" : 330393.0683701786, + "99.9999" : 330393.0683701786, + "100.0" : 330393.0683701786 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 323284.24612123315, - 328473.80463527696, - 328486.03566760744, - 328522.32693804795 + 328688.61623730283, + 330393.0683701786, + 330347.18651739164, + 328093.33984041726 ] ] }, @@ -224,7 +336,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -238,41 +350,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 1172370.1949547157, - "scoreError" : 44623.57635400855, + "score" : 1167053.2311822318, + "scoreError" : 5257.9839040498, "scoreConfidence" : [ - 1127746.618600707, - 1216993.7713087243 + 1161795.247278182, + 1172311.2150862815 ], "scorePercentiles" : { - "0.0" : 1164002.9183628866, - "50.0" : 1172433.802146774, - "90.0" : 1180610.257162428, - "95.0" : 1180610.257162428, - "99.0" : 1180610.257162428, - "99.9" : 1180610.257162428, - "99.99" : 1180610.257162428, - "99.999" : 1180610.257162428, - "99.9999" : 1180610.257162428, - "100.0" : 1180610.257162428 + "0.0" : 1165914.62029355, + "50.0" : 1167305.0975543554, + "90.0" : 1167688.1093266667, + "95.0" : 1167688.1093266667, + "99.0" : 1167688.1093266667, + "99.9" : 1167688.1093266667, + "99.99" : 1167688.1093266667, + "99.999" : 1167688.1093266667, + "99.9999" : 1167688.1093266667, + "100.0" : 1167688.1093266667 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1180610.257162428, - 1164002.9183628866, - 1174037.1951503523, - 1170830.4091431957 + 1165914.62029355, + 1167688.1093266667, + 1167587.8000121168, + 1167022.395096594 ] ] }, @@ -280,7 +392,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -294,41 +406,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 2191564.9811769784, - "scoreError" : 20901.204115111937, + "score" : 2034331.7853433017, + "scoreError" : 45080.59303419907, + "scoreConfidence" : [ + 1989251.1923091027, + 2079412.3783775007 + ], + "scorePercentiles" : { + "0.0" : 2024697.6544727832, + "50.0" : 2035622.184185423, + "90.0" : 2041385.1185295777, + "95.0" : 2041385.1185295777, + "99.0" : 2041385.1185295777, + "99.9" : 2041385.1185295777, + "99.99" : 2041385.1185295777, + "99.999" : 2041385.1185295777, + "99.9999" : 2041385.1185295777, + "100.0" : 2041385.1185295777 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 2035855.4805261057, + 2041385.1185295777, + 2035388.8878447404, + 2024697.6544727832 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1kb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 549741.974487169, + "scoreError" : 18510.860445075177, "scoreConfidence" : [ - 2170663.7770618666, - 2212466.1852920903 + 531231.1140420937, + 568252.8349322441 ], "scorePercentiles" : { - "0.0" : 2187130.045963812, - "50.0" : 2192139.318299738, - "90.0" : 2194851.2421446256, - "95.0" : 2194851.2421446256, - "99.0" : 2194851.2421446256, - "99.9" : 2194851.2421446256, - "99.99" : 2194851.2421446256, - "99.999" : 2194851.2421446256, - "99.9999" : 2194851.2421446256, - "100.0" : 2194851.2421446256 + "0.0" : 547187.0258438425, + "50.0" : 549027.1151022253, + "90.0" : 553726.6419003825, + "95.0" : 553726.6419003825, + "99.0" : 553726.6419003825, + "99.9" : 553726.6419003825, + "99.99" : 553726.6419003825, + "99.999" : 553726.6419003825, + "99.9999" : 553726.6419003825, + "100.0" : 553726.6419003825 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 2187130.045963812, - 2192498.331993979, - 2194851.2421446256, - 2191780.3046054975 + 549798.4790281476, + 553726.6419003825, + 547187.0258438425, + 548255.751176303 ] ] }, @@ -336,7 +504,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -350,41 +518,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 403267.4027522539, - "scoreError" : 43515.31402068551, + "score" : 434317.73653196916, + "scoreError" : 8887.004496772832, "scoreConfidence" : [ - 359752.0887315684, - 446782.7167729394 + 425430.7320351963, + 443204.741028742 ], "scorePercentiles" : { - "0.0" : 397328.9014945461, - "50.0" : 402949.1987281769, - "90.0" : 409842.3120581157, - "95.0" : 409842.3120581157, - "99.0" : 409842.3120581157, - "99.9" : 409842.3120581157, - "99.99" : 409842.3120581157, - "99.999" : 409842.3120581157, - "99.9999" : 409842.3120581157, - "100.0" : 409842.3120581157 + "0.0" : 432850.8425543397, + "50.0" : 434339.8306456285, + "90.0" : 435740.44228227995, + "95.0" : 435740.44228227995, + "99.0" : 435740.44228227995, + "99.9" : 435740.44228227995, + "99.99" : 435740.44228227995, + "99.999" : 435740.44228227995, + "99.9999" : 435740.44228227995, + "100.0" : 435740.44228227995 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 397594.5620112011, - 397328.9014945461, - 408303.8354451526, - 409842.3120581157 + 433474.58847059973, + 435740.44228227995, + 435205.07282065734, + 432850.8425543397 ] ] }, @@ -392,7 +560,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -406,41 +574,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1kb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 892874.5689568161, + "scoreError" : 29854.73803129264, + "scoreConfidence" : [ + 863019.8309255234, + 922729.3069881088 + ], + "scorePercentiles" : { + "0.0" : 887656.8064194502, + "50.0" : 892500.8721891862, + "90.0" : 898839.7250294421, + "95.0" : 898839.7250294421, + "99.0" : 898839.7250294421, + "99.9" : 898839.7250294421, + "99.99" : 898839.7250294421, + "99.999" : 898839.7250294421, + "99.9999" : 898839.7250294421, + "100.0" : 898839.7250294421 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 898839.7250294421, + 887656.8064194502, + 893189.0795532095, + 891812.6648251629 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 427421.9247585598, - "scoreError" : 24518.728600886658, + "score" : 420722.9597510017, + "scoreError" : 6008.59118247858, "scoreConfidence" : [ - 402903.19615767314, - 451940.65335944644 + 414714.3685685231, + 426731.55093348026 ], "scorePercentiles" : { - "0.0" : 421863.8499176919, - "50.0" : 428821.3012850069, - "90.0" : 430181.24654653337, - "95.0" : 430181.24654653337, - "99.0" : 430181.24654653337, - "99.9" : 430181.24654653337, - "99.99" : 430181.24654653337, - "99.999" : 430181.24654653337, - "99.9999" : 430181.24654653337, - "100.0" : 430181.24654653337 + "0.0" : 419381.737651805, + "50.0" : 420989.7569802229, + "90.0" : 421530.58739175607, + "95.0" : 421530.58739175607, + "99.0" : 421530.58739175607, + "99.9" : 421530.58739175607, + "99.99" : 421530.58739175607, + "99.999" : 421530.58739175607, + "99.9999" : 421530.58739175607, + "100.0" : 421530.58739175607 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 428201.79164324555, - 430181.24654653337, - 429440.8109267683, - 421863.8499176919 + 421530.58739175607, + 419381.737651805, + 420977.8180895547, + 421001.695870891 ] ] }, @@ -448,7 +672,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -462,41 +686,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 177497.0107319799, - "scoreError" : 14364.31626843296, + "score" : 176604.56851430555, + "scoreError" : 5367.441061591579, "scoreConfidence" : [ - 163132.69446354694, - 191861.3270004129 + 171237.12745271396, + 181972.00957589713 ], "scorePercentiles" : { - "0.0" : 174194.40738251232, - "50.0" : 178388.27788455077, - "90.0" : 179017.07977630582, - "95.0" : 179017.07977630582, - "99.0" : 179017.07977630582, - "99.9" : 179017.07977630582, - "99.99" : 179017.07977630582, - "99.999" : 179017.07977630582, - "99.9999" : 179017.07977630582, - "100.0" : 179017.07977630582 + "0.0" : 175891.7547813067, + "50.0" : 176498.65950984042, + "90.0" : 177529.2002562346, + "95.0" : 177529.2002562346, + "99.0" : 177529.2002562346, + "99.9" : 177529.2002562346, + "99.99" : 177529.2002562346, + "99.999" : 177529.2002562346, + "99.9999" : 177529.2002562346, + "100.0" : 177529.2002562346 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 179017.07977630582, - 178481.170435525, - 174194.40738251232, - 178295.38533357653 + 175913.72561930353, + 175891.7547813067, + 177529.2002562346, + 177083.59340037729 ] ] }, @@ -504,7 +728,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -518,41 +742,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 47736.65476696285, - "scoreError" : 1345.094348191517, + "score" : 48283.89499745317, + "scoreError" : 1656.4635042534533, "scoreConfidence" : [ - 46391.56041877133, - 49081.749115154365 + 46627.43149319972, + 49940.35850170662 ], "scorePercentiles" : { - "0.0" : 47555.57243850504, - "50.0" : 47686.14025700826, - "90.0" : 48018.76611532986, - "95.0" : 48018.76611532986, - "99.0" : 48018.76611532986, - "99.9" : 48018.76611532986, - "99.99" : 48018.76611532986, - "99.999" : 48018.76611532986, - "99.9999" : 48018.76611532986, - "100.0" : 48018.76611532986 + "0.0" : 48041.62315484572, + "50.0" : 48229.64055126053, + "90.0" : 48634.67573244592, + "95.0" : 48634.67573244592, + "99.0" : 48634.67573244592, + "99.9" : 48634.67573244592, + "99.99" : 48634.67573244592, + "99.999" : 48634.67573244592, + "99.9999" : 48634.67573244592, + "100.0" : 48634.67573244592 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 48018.76611532986, - 47607.06985357856, - 47765.210660437944, - 47555.57243850504 + 48160.72011965533, + 48298.56098286572, + 48041.62315484572, + 48634.67573244592 ] ] }, @@ -560,7 +784,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -574,41 +798,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 148580.55389673484, - "scoreError" : 7903.956542352092, + "score" : 149003.2664377889, + "scoreError" : 4034.6402888002817, "scoreConfidence" : [ - 140676.59735438274, - 156484.51043908694 + 144968.6261489886, + 153037.9067265892 ], "scorePercentiles" : { - "0.0" : 147110.3341399147, - "50.0" : 148799.92883293395, - "90.0" : 149612.0237811568, - "95.0" : 149612.0237811568, - "99.0" : 149612.0237811568, - "99.9" : 149612.0237811568, - "99.99" : 149612.0237811568, - "99.999" : 149612.0237811568, - "99.9999" : 149612.0237811568, - "100.0" : 149612.0237811568 + "0.0" : 148126.13096242023, + "50.0" : 149196.69225041344, + "90.0" : 149493.5502879085, + "95.0" : 149493.5502879085, + "99.0" : 149493.5502879085, + "99.9" : 149493.5502879085, + "99.99" : 149493.5502879085, + "99.999" : 149493.5502879085, + "99.9999" : 149493.5502879085, + "100.0" : 149493.5502879085 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 148036.21042685886, - 147110.3341399147, - 149563.64723900906, - 149612.0237811568 + 148126.13096242023, + 149493.5502879085, + 148990.63016748935, + 149402.75433333754 ] ] }, @@ -616,7 +840,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -630,41 +854,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 266636.44019146083, - "scoreError" : 3831.5862190856205, + "score" : 277147.0123110621, + "scoreError" : 10341.200741446326, + "scoreConfidence" : [ + 266805.81156961573, + 287488.2130525084 + ], + "scorePercentiles" : { + "0.0" : 275522.27501270274, + "50.0" : 276875.17110009305, + "90.0" : 279315.4320313594, + "95.0" : 279315.4320313594, + "99.0" : 279315.4320313594, + "99.9" : 279315.4320313594, + "99.99" : 279315.4320313594, + "99.999" : 279315.4320313594, + "99.9999" : 279315.4320313594, + "100.0" : 279315.4320313594 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 276564.2150378382, + 277186.12716234784, + 279315.4320313594, + 275522.27501270274 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128kb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 4728.468432101119, + "scoreError" : 169.23274141134854, "scoreConfidence" : [ - 262804.8539723752, - 270468.02641054644 + 4559.23569068977, + 4897.701173512468 ], "scorePercentiles" : { - "0.0" : 266038.3930441509, - "50.0" : 266528.46815931366, - "90.0" : 267450.43140306504, - "95.0" : 267450.43140306504, - "99.0" : 267450.43140306504, - "99.9" : 267450.43140306504, - "99.99" : 267450.43140306504, - "99.999" : 267450.43140306504, - "99.9999" : 267450.43140306504, - "100.0" : 267450.43140306504 + "0.0" : 4689.218535377044, + "50.0" : 4740.948460607137, + "90.0" : 4742.758271813157, + "95.0" : 4742.758271813157, + "99.0" : 4742.758271813157, + "99.9" : 4742.758271813157, + "99.99" : 4742.758271813157, + "99.999" : 4742.758271813157, + "99.9999" : 4742.758271813157, + "100.0" : 4742.758271813157 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 267450.43140306504, - 266453.6905350327, - 266603.24578359467, - 266038.3930441509 + 4689.218535377044, + 4742.758271813157, + 4741.763264207704, + 4740.1336570065705 ] ] }, @@ -672,7 +952,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -686,41 +966,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 3265.018485513807, - "scoreError" : 211.52248659255656, + "score" : 3577.79887805201, + "scoreError" : 26.18467106574972, "scoreConfidence" : [ - 3053.4959989212502, - 3476.5409721063634 + 3551.6142069862603, + 3603.9835491177596 ], "scorePercentiles" : { - "0.0" : 3220.6314731712628, - "50.0" : 3274.0272158278585, - "90.0" : 3291.388037228249, - "95.0" : 3291.388037228249, - "99.0" : 3291.388037228249, - "99.9" : 3291.388037228249, - "99.99" : 3291.388037228249, - "99.999" : 3291.388037228249, - "99.9999" : 3291.388037228249, - "100.0" : 3291.388037228249 + "0.0" : 3572.0979436748175, + "50.0" : 3579.15086827506, + "90.0" : 3580.795831983102, + "95.0" : 3580.795831983102, + "99.0" : 3580.795831983102, + "99.9" : 3580.795831983102, + "99.99" : 3580.795831983102, + "99.999" : 3580.795831983102, + "99.9999" : 3580.795831983102, + "100.0" : 3580.795831983102 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 3260.1250600835074, - 3220.6314731712628, - 3291.388037228249, - 3287.929371572209 + 3577.715499233423, + 3580.795831983102, + 3580.5862373166974, + 3572.0979436748175 ] ] }, @@ -728,7 +1008,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -742,41 +1022,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128kb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 5576.712066017241, + "scoreError" : 56.87910417352511, + "scoreConfidence" : [ + 5519.8329618437165, + 5633.591170190766 + ], + "scorePercentiles" : { + "0.0" : 5569.143715740925, + "50.0" : 5574.144810654081, + "90.0" : 5589.414927019879, + "95.0" : 5589.414927019879, + "99.0" : 5589.414927019879, + "99.9" : 5589.414927019879, + "99.99" : 5589.414927019879, + "99.999" : 5589.414927019879, + "99.9999" : 5589.414927019879, + "100.0" : 5589.414927019879 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 5574.6956819036295, + 5573.593939404532, + 5589.414927019879, + 5569.143715740925 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 3096.090303694719, - "scoreError" : 114.1578488303212, + "score" : 3177.920949665991, + "scoreError" : 58.74788909478107, "scoreConfidence" : [ - 2981.9324548643976, - 3210.24815252504 + 3119.17306057121, + 3236.668838760772 ], "scorePercentiles" : { - "0.0" : 3075.4612594160444, - "50.0" : 3098.1337498176445, - "90.0" : 3112.6324557275416, - "95.0" : 3112.6324557275416, - "99.0" : 3112.6324557275416, - "99.9" : 3112.6324557275416, - "99.99" : 3112.6324557275416, - "99.999" : 3112.6324557275416, - "99.9999" : 3112.6324557275416, - "100.0" : 3112.6324557275416 + "0.0" : 3169.9666905993176, + "50.0" : 3176.9266137796594, + "90.0" : 3187.8638805053274, + "95.0" : 3187.8638805053274, + "99.0" : 3187.8638805053274, + "99.9" : 3187.8638805053274, + "99.99" : 3187.8638805053274, + "99.999" : 3187.8638805053274, + "99.9999" : 3187.8638805053274, + "100.0" : 3187.8638805053274 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 3108.8275968787652, - 3075.4612594160444, - 3087.439902756524, - 3112.6324557275416 + 3170.4518266905884, + 3187.8638805053274, + 3183.40140086873, + 3169.9666905993176 ] ] }, @@ -784,7 +1120,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -798,41 +1134,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 1255.0932751513285, - "scoreError" : 46.22591384242437, + "score" : 1279.9729785390234, + "scoreError" : 20.191937356816076, "scoreConfidence" : [ - 1208.867361308904, - 1301.319188993753 + 1259.7810411822074, + 1300.1649158958394 ], "scorePercentiles" : { - "0.0" : 1244.6630457421552, - "50.0" : 1257.3824405600344, - "90.0" : 1260.9451737430902, - "95.0" : 1260.9451737430902, - "99.0" : 1260.9451737430902, - "99.9" : 1260.9451737430902, - "99.99" : 1260.9451737430902, - "99.999" : 1260.9451737430902, - "99.9999" : 1260.9451737430902, - "100.0" : 1260.9451737430902 + "0.0" : 1275.6939320168879, + "50.0" : 1280.6173242904092, + "90.0" : 1282.9633335583874, + "95.0" : 1282.9633335583874, + "99.0" : 1282.9633335583874, + "99.9" : 1282.9633335583874, + "99.99" : 1282.9633335583874, + "99.999" : 1282.9633335583874, + "99.9999" : 1282.9633335583874, + "100.0" : 1282.9633335583874 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1257.4225286709386, - 1257.34235244913, - 1260.9451737430902, - 1244.6630457421552 + 1275.6939320168879, + 1281.394784055471, + 1282.9633335583874, + 1279.8398645253476 ] ] }, @@ -840,7 +1176,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -854,41 +1190,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 360.73397668574137, - "scoreError" : 2.9849183809715094, + "score" : 375.69147856459927, + "scoreError" : 26.87803980719791, "scoreConfidence" : [ - 357.74905830476985, - 363.7188950667129 + 348.81343875740134, + 402.5695183717972 ], "scorePercentiles" : { - "0.0" : 360.1643871524679, - "50.0" : 360.74936066459566, - "90.0" : 361.27279826130626, - "95.0" : 361.27279826130626, - "99.0" : 361.27279826130626, - "99.9" : 361.27279826130626, - "99.99" : 361.27279826130626, - "99.999" : 361.27279826130626, - "99.9999" : 361.27279826130626, - "100.0" : 361.27279826130626 + "0.0" : 369.66421529142144, + "50.0" : 377.01959904759076, + "90.0" : 379.062500871794, + "95.0" : 379.062500871794, + "99.0" : 379.062500871794, + "99.9" : 379.062500871794, + "99.99" : 379.062500871794, + "99.999" : 379.062500871794, + "99.9999" : 379.062500871794, + "100.0" : 379.062500871794 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 360.86088286252544, - 361.27279826130626, - 360.6378384666658, - 360.1643871524679 + 377.6037578935196, + 376.43544020166183, + 379.062500871794, + 369.66421529142144 ] ] }, @@ -896,7 +1232,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -910,41 +1246,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 1053.7816934132636, - "scoreError" : 17.98846836879526, + "score" : 1109.5044338432704, + "scoreError" : 14.324446676367586, "scoreConfidence" : [ - 1035.7932250444683, - 1071.770161782059 + 1095.1799871669027, + 1123.828880519638 ], "scorePercentiles" : { - "0.0" : 1051.7004653852352, - "50.0" : 1052.8498041712742, - "90.0" : 1057.726699925271, - "95.0" : 1057.726699925271, - "99.0" : 1057.726699925271, - "99.9" : 1057.726699925271, - "99.99" : 1057.726699925271, - "99.999" : 1057.726699925271, - "99.9999" : 1057.726699925271, - "100.0" : 1057.726699925271 + "0.0" : 1108.0686828247212, + "50.0" : 1108.6025725651468, + "90.0" : 1112.7439074180666, + "95.0" : 1112.7439074180666, + "99.0" : 1112.7439074180666, + "99.9" : 1112.7439074180666, + "99.99" : 1112.7439074180666, + "99.999" : 1112.7439074180666, + "99.9999" : 1112.7439074180666, + "100.0" : 1112.7439074180666 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1057.726699925271, - 1053.7487275819278, - 1051.9508807606203, - 1051.7004653852352 + 1108.0737445954674, + 1108.0686828247212, + 1109.1314005348263, + 1112.7439074180666 ] ] }, @@ -952,7 +1288,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -966,41 +1302,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 2092.4795061467858, - "scoreError" : 3.4526544775530565, + "score" : 1934.2735060465438, + "scoreError" : 145.3142718068349, "scoreConfidence" : [ - 2089.0268516692327, - 2095.932160624339 + 1788.9592342397088, + 2079.5877778533786 ], "scorePercentiles" : { - "0.0" : 2092.002062716033, - "50.0" : 2092.3633355152065, - "90.0" : 2093.189290840698, - "95.0" : 2093.189290840698, - "99.0" : 2093.189290840698, - "99.9" : 2093.189290840698, - "99.99" : 2093.189290840698, - "99.999" : 2093.189290840698, - "99.9999" : 2093.189290840698, - "100.0" : 2093.189290840698 + "0.0" : 1900.5575849264953, + "50.0" : 1945.0995448027188, + "90.0" : 1946.3373496542426, + "95.0" : 1946.3373496542426, + "99.0" : 1946.3373496542426, + "99.9" : 1946.3373496542426, + "99.99" : 1946.3373496542426, + "99.999" : 1946.3373496542426, + "99.9999" : 1946.3373496542426, + "100.0" : 1946.3373496542426 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 2092.142316922251, - 2093.189290840698, - 2092.5843541081617, - 2092.002062716033 + 1945.5256149916627, + 1946.3373496542426, + 1944.6734746137747, + 1900.5575849264953 ] ] }, @@ -1008,7 +1344,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1022,41 +1358,153 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1mb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 582.9115264911206, + "scoreError" : 3.036982655418544, + "scoreConfidence" : [ + 579.8745438357021, + 585.9485091465392 + ], + "scorePercentiles" : { + "0.0" : 582.5104232647693, + "50.0" : 582.7768370434262, + "90.0" : 583.5820086128612, + "95.0" : 583.5820086128612, + "99.0" : 583.5820086128612, + "99.9" : 583.5820086128612, + "99.99" : 583.5820086128612, + "99.999" : 583.5820086128612, + "99.9999" : 583.5820086128612, + "100.0" : 583.5820086128612 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 582.687615523413, + 582.8660585634393, + 583.5820086128612, + 582.5104232647693 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 300.50175605365195, - "scoreError" : 16.628260772796597, + "score" : 499.2126793098415, + "scoreError" : 16.65777230462521, + "scoreConfidence" : [ + 482.5549070052163, + 515.8704516144667 + ], + "scorePercentiles" : { + "0.0" : 495.5221775965387, + "50.0" : 500.12397993581743, + "90.0" : 501.08057977119233, + "95.0" : 501.08057977119233, + "99.0" : 501.08057977119233, + "99.9" : 501.08057977119233, + "99.99" : 501.08057977119233, + "99.999" : 501.08057977119233, + "99.9999" : 501.08057977119233, + "100.0" : 501.08057977119233 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 495.5221775965387, + 499.36059915298074, + 501.08057977119233, + 500.8873607186541 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1mb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 727.8444320522649, + "scoreError" : 5.4201652188762965, "scoreConfidence" : [ - 283.87349528085537, - 317.1300168264485 + 722.4242668333886, + 733.2645972711412 ], "scorePercentiles" : { - "0.0" : 296.7270976551244, - "50.0" : 301.46267892494234, - "90.0" : 302.35456870959865, - "95.0" : 302.35456870959865, - "99.0" : 302.35456870959865, - "99.9" : 302.35456870959865, - "99.99" : 302.35456870959865, - "99.999" : 302.35456870959865, - "99.9999" : 302.35456870959865, - "100.0" : 302.35456870959865 + "0.0" : 726.9083542675232, + "50.0" : 727.7621565394252, + "90.0" : 728.9450608626861, + "95.0" : 728.9450608626861, + "99.0" : 728.9450608626861, + "99.9" : 728.9450608626861, + "99.99" : 728.9450608626861, + "99.999" : 728.9450608626861, + "99.9999" : 728.9450608626861, + "100.0" : 728.9450608626861 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 302.35456870959865, - 301.0521833779614, - 301.87317447192333, - 296.7270976551244 + 727.8309841229437, + 727.6933289559067, + 728.9450608626861, + 726.9083542675232 ] ] }, @@ -1064,7 +1512,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1078,41 +1526,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 392.0619134376386, - "scoreError" : 1.6351377127176903, + "score" : 394.10021533585757, + "scoreError" : 4.34839951102803, "scoreConfidence" : [ - 390.42677572492096, - 393.6970511503563 + 389.7518158248295, + 398.4486148468856 ], "scorePercentiles" : { - "0.0" : 391.8480127263682, - "50.0" : 391.98575996613056, - "90.0" : 392.42812109192516, - "95.0" : 392.42812109192516, - "99.0" : 392.42812109192516, - "99.9" : 392.42812109192516, - "99.99" : 392.42812109192516, - "99.999" : 392.42812109192516, - "99.9999" : 392.42812109192516, - "100.0" : 392.42812109192516 + "0.0" : 393.5074744682836, + "50.0" : 393.93577760972846, + "90.0" : 395.0218316556898, + "95.0" : 395.0218316556898, + "99.0" : 395.0218316556898, + "99.9" : 395.0218316556898, + "99.99" : 395.0218316556898, + "99.999" : 395.0218316556898, + "99.9999" : 395.0218316556898, + "100.0" : 395.0218316556898 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 392.00345893627724, - 391.96806099598393, - 392.42812109192516, - 391.8480127263682 + 393.5074744682836, + 394.16344469985165, + 395.0218316556898, + 393.7081105196052 ] ] }, @@ -1120,7 +1568,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1134,41 +1582,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 157.51845044919145, - "scoreError" : 0.680659832435363, + "score" : 156.18364150517357, + "scoreError" : 3.967924463335569, "scoreConfidence" : [ - 156.83779061675608, - 158.19911028162682 + 152.215717041838, + 160.15156596850915 ], "scorePercentiles" : { - "0.0" : 157.41259901009516, - "50.0" : 157.51990966919712, - "90.0" : 157.62138344827642, - "95.0" : 157.62138344827642, - "99.0" : 157.62138344827642, - "99.9" : 157.62138344827642, - "99.99" : 157.62138344827642, - "99.999" : 157.62138344827642, - "99.9999" : 157.62138344827642, - "100.0" : 157.62138344827642 + "0.0" : 155.33978014796764, + "50.0" : 156.33363491224395, + "90.0" : 156.72751604823878, + "95.0" : 156.72751604823878, + "99.0" : 156.72751604823878, + "99.9" : 156.72751604823878, + "99.99" : 156.72751604823878, + "99.999" : 156.72751604823878, + "99.9999" : 156.72751604823878, + "100.0" : 156.72751604823878 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 157.44414342450114, - 157.62138344827642, - 157.5956759138931, - 157.41259901009516 + 155.33978014796764, + 156.13584663979125, + 156.72751604823878, + 156.53142318469662 ] ] }, @@ -1176,7 +1624,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1190,41 +1638,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 49.41659582197183, - "scoreError" : 4.128650672278151, + "score" : 49.42348078315202, + "scoreError" : 4.458763358666092, "scoreConfidence" : [ - 45.287945149693684, - 53.54524649424998 + 44.96471742448593, + 53.88224414181811 ], "scorePercentiles" : { - "0.0" : 48.76101535916496, - "50.0" : 49.306450172914005, - "90.0" : 50.29246758289436, - "95.0" : 50.29246758289436, - "99.0" : 50.29246758289436, - "99.9" : 50.29246758289436, - "99.99" : 50.29246758289436, - "99.999" : 50.29246758289436, - "99.9999" : 50.29246758289436, - "100.0" : 50.29246758289436 + "0.0" : 48.56102653054879, + "50.0" : 49.49864645891251, + "90.0" : 50.135603684234304, + "95.0" : 50.135603684234304, + "99.0" : 50.135603684234304, + "99.9" : 50.135603684234304, + "99.99" : 50.135603684234304, + "99.999" : 50.135603684234304, + "99.9999" : 50.135603684234304, + "100.0" : 50.135603684234304 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 49.34785823381571, - 50.29246758289436, - 48.76101535916496, - 49.2650421120123 + 50.135603684234304, + 48.56102653054879, + 49.78678526110603, + 49.21050765671899 ] ] }, @@ -1232,7 +1680,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1246,41 +1694,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 142.1640514746978, - "scoreError" : 1.4225526580210257, + "score" : 138.30900400088186, + "scoreError" : 2.3224502928310424, "scoreConfidence" : [ - 140.74149881667677, - 143.58660413271883 + 135.98655370805082, + 140.6314542937129 ], "scorePercentiles" : { - "0.0" : 141.90393517724456, - "50.0" : 142.1560858061505, - "90.0" : 142.4400991092456, - "95.0" : 142.4400991092456, - "99.0" : 142.4400991092456, - "99.9" : 142.4400991092456, - "99.99" : 142.4400991092456, - "99.999" : 142.4400991092456, - "99.9999" : 142.4400991092456, - "100.0" : 142.4400991092456 + "0.0" : 137.8065551308592, + "50.0" : 138.3862375523468, + "90.0" : 138.65698576797462, + "95.0" : 138.65698576797462, + "99.0" : 138.65698576797462, + "99.9" : 138.65698576797462, + "99.99" : 138.65698576797462, + "99.999" : 138.65698576797462, + "99.9999" : 138.65698576797462, + "100.0" : 138.65698576797462 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 142.18251762104686, - 142.4400991092456, - 141.90393517724456, - 142.12965399125414 + 138.65698576797462, + 138.4181173369827, + 137.8065551308592, + 138.35435776771092 ] ] }, @@ -1288,7 +1736,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1302,41 +1750,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 239.59825470502108, - "scoreError" : 12.090993885755951, + "score" : 238.86175823829703, + "scoreError" : 0.6522379606193902, "scoreConfidence" : [ - 227.50726081926513, - 251.68924859077703 + 238.20952027767763, + 239.51399619891643 ], "scorePercentiles" : { - "0.0" : 237.33856097367186, - "50.0" : 239.95747440052935, - "90.0" : 241.13950904535375, - "95.0" : 241.13950904535375, - "99.0" : 241.13950904535375, - "99.9" : 241.13950904535375, - "99.99" : 241.13950904535375, - "99.999" : 241.13950904535375, - "99.9999" : 241.13950904535375, - "100.0" : 241.13950904535375 + "0.0" : 238.71235571423296, + "50.0" : 238.9030442351787, + "90.0" : 238.92858876859776, + "95.0" : 238.92858876859776, + "99.0" : 238.92858876859776, + "99.9" : 238.92858876859776, + "99.99" : 238.92858876859776, + "99.999" : 238.92858876859776, + "99.9999" : 238.92858876859776, + "100.0" : 238.92858876859776 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 241.1328951147916, - 241.13950904535375, - 238.78205368626712, - 237.33856097367186 + 238.71235571423296, + 238.88950084360954, + 238.92858876859776, + 238.91658762674786 ] ] }, @@ -1344,7 +1792,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1358,41 +1806,153 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "32mb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 16.222819445110638, + "scoreError" : 0.12297511014609462, + "scoreConfidence" : [ + 16.099844334964544, + 16.34579455525673 + ], + "scorePercentiles" : { + "0.0" : 16.197468097538295, + "50.0" : 16.226136279571865, + "90.0" : 16.24153712376053, + "95.0" : 16.24153712376053, + "99.0" : 16.24153712376053, + "99.9" : 16.24153712376053, + "99.99" : 16.24153712376053, + "99.999" : 16.24153712376053, + "99.9999" : 16.24153712376053, + "100.0" : 16.24153712376053 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 16.197468097538295, + 16.24153712376053, + 16.220159716115297, + 16.232112843028432 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 11.786317004742862, - "scoreError" : 0.14575827384743734, + "score" : 11.343010380444237, + "scoreError" : 0.15542330497021425, + "scoreConfidence" : [ + 11.187587075474024, + 11.49843368541445 + ], + "scorePercentiles" : { + "0.0" : 11.322620898539569, + "50.0" : 11.337904510310192, + "90.0" : 11.373611602616997, + "95.0" : 11.373611602616997, + "99.0" : 11.373611602616997, + "99.9" : 11.373611602616997, + "99.99" : 11.373611602616997, + "99.999" : 11.373611602616997, + "99.9999" : 11.373611602616997, + "100.0" : 11.373611602616997 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 11.350772692027288, + 11.325036328593098, + 11.373611602616997, + 11.322620898539569 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "32mb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 19.49130674162078, + "scoreError" : 0.06554510920029963, "scoreConfidence" : [ - 11.640558730895425, - 11.9320752785903 + 19.42576163242048, + 19.55685185082108 ], "scorePercentiles" : { - "0.0" : 11.758114656066427, - "50.0" : 11.78703829894285, - "90.0" : 11.81307676501932, - "95.0" : 11.81307676501932, - "99.0" : 11.81307676501932, - "99.9" : 11.81307676501932, - "99.99" : 11.81307676501932, - "99.999" : 11.81307676501932, - "99.9999" : 11.81307676501932, - "100.0" : 11.81307676501932 + "0.0" : 19.48264722596688, + "50.0" : 19.488354876861465, + "90.0" : 19.505869986793307, + "95.0" : 19.505869986793307, + "99.0" : 19.505869986793307, + "99.9" : 19.505869986793307, + "99.99" : 19.505869986793307, + "99.999" : 19.505869986793307, + "99.9999" : 19.505869986793307, + "100.0" : 19.505869986793307 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 11.758114656066427, - 11.78967041235971, - 11.784406185525992, - 11.81307676501932 + 19.486914415176994, + 19.505869986793307, + 19.48264722596688, + 19.489795338545935 ] ] }, @@ -1400,7 +1960,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1414,41 +1974,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 10.441401348799962, - "scoreError" : 0.4257042267370256, + "score" : 9.97226934026093, + "scoreError" : 0.14099093351999584, "scoreConfidence" : [ - 10.015697122062937, - 10.867105575536987 + 9.831278406740934, + 10.113260273780925 ], "scorePercentiles" : { - "0.0" : 10.349816758614011, - "50.0" : 10.460552010194798, - "90.0" : 10.494684616196244, - "95.0" : 10.494684616196244, - "99.0" : 10.494684616196244, - "99.9" : 10.494684616196244, - "99.99" : 10.494684616196244, - "99.999" : 10.494684616196244, - "99.9999" : 10.494684616196244, - "100.0" : 10.494684616196244 + "0.0" : 9.943063947678294, + "50.0" : 9.978283708721236, + "90.0" : 9.98944599592296, + "95.0" : 9.98944599592296, + "99.0" : 9.98944599592296, + "99.9" : 9.98944599592296, + "99.99" : 9.98944599592296, + "99.999" : 9.98944599592296, + "99.9999" : 9.98944599592296, + "100.0" : 9.98944599592296 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 10.494684616196244, - 10.437536011173183, - 10.483568009216413, - 10.349816758614011 + 9.988476929796445, + 9.943063947678294, + 9.968090487646025, + 9.98944599592296 ] ] }, @@ -1456,7 +2016,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1470,41 +2030,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 2.7059660558815626, - "scoreError" : 0.5529906205263425, + "score" : 2.1917481848188074, + "scoreError" : 0.19753919868090142, "scoreConfidence" : [ - 2.15297543535522, - 3.258956676407905 + 1.994208986137906, + 2.389287383499709 ], "scorePercentiles" : { - "0.0" : 2.5821394278212177, - "50.0" : 2.7386367255520128, - "90.0" : 2.7644513446010057, - "95.0" : 2.7644513446010057, - "99.0" : 2.7644513446010057, - "99.9" : 2.7644513446010057, - "99.99" : 2.7644513446010057, - "99.999" : 2.7644513446010057, - "99.9999" : 2.7644513446010057, - "100.0" : 2.7644513446010057 + "0.0" : 2.161174770863264, + "50.0" : 2.1931165391649596, + "90.0" : 2.2195848900820465, + "95.0" : 2.2195848900820465, + "99.0" : 2.2195848900820465, + "99.9" : 2.2195848900820465, + "99.99" : 2.2195848900820465, + "99.999" : 2.2195848900820465, + "99.9999" : 2.2195848900820465, + "100.0" : 2.2195848900820465 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 2.715383754518447, - 2.5821394278212177, - 2.7618896965855786, - 2.7644513446010057 + 2.2164629075394187, + 2.161174770863264, + 2.169770170790501, + 2.2195848900820465 ] ] }, @@ -1512,7 +2072,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1526,41 +2086,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 1.0644466719884713, - "scoreError" : 0.10828100329407624, + "score" : 1.0653568016661057, + "scoreError" : 0.10740651196183097, "scoreConfidence" : [ - 0.9561656686943951, - 1.1727276752825475 + 0.9579502897042748, + 1.1727633136279367 ], "scorePercentiles" : { - "0.0" : 1.0488479596654356, - "50.0" : 1.0605586721042153, - "90.0" : 1.0878213840800195, - "95.0" : 1.0878213840800195, - "99.0" : 1.0878213840800195, - "99.9" : 1.0878213840800195, - "99.99" : 1.0878213840800195, - "99.999" : 1.0878213840800195, - "99.9999" : 1.0878213840800195, - "100.0" : 1.0878213840800195 + "0.0" : 1.0433927257114266, + "50.0" : 1.0689022836621562, + "90.0" : 1.0802299136286833, + "95.0" : 1.0802299136286833, + "99.0" : 1.0802299136286833, + "99.9" : 1.0802299136286833, + "99.99" : 1.0802299136286833, + "99.999" : 1.0802299136286833, + "99.9999" : 1.0802299136286833, + "100.0" : 1.0802299136286833 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1.0639069080349006, - 1.0488479596654356, - 1.05721043617353, - 1.0878213840800195 + 1.0802299136286833, + 1.0759743955128906, + 1.0618301718114218, + 1.0433927257114266 ] ] }, @@ -1568,7 +2128,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1582,41 +2142,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 3.4253825528465027, - "scoreError" : 0.39600842164497063, + "score" : 2.949846107789613, + "scoreError" : 1.2234104146252418, "scoreConfidence" : [ - 3.029374131201532, - 3.8213909744914734 + 1.7264356931643714, + 4.173256522414855 ], "scorePercentiles" : { - "0.0" : 3.385092868844048, - "50.0" : 3.400474031886585, - "90.0" : 3.5154892787687935, - "95.0" : 3.5154892787687935, - "99.0" : 3.5154892787687935, - "99.9" : 3.5154892787687935, - "99.99" : 3.5154892787687935, - "99.999" : 3.5154892787687935, - "99.9999" : 3.5154892787687935, - "100.0" : 3.5154892787687935 + "0.0" : 2.668916069460295, + "50.0" : 3.0243580598737383, + "90.0" : 3.0817522419506798, + "95.0" : 3.0817522419506798, + "99.0" : 3.0817522419506798, + "99.9" : 3.0817522419506798, + "99.99" : 3.0817522419506798, + "99.999" : 3.0817522419506798, + "99.9999" : 3.0817522419506798, + "100.0" : 3.0817522419506798 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 3.5154892787687935, - 3.385092868844048, - 3.4123772610553913, - 3.388570802717778 + 3.0817522419506798, + 3.017081868987934, + 2.668916069460295, + 3.031634250759543 ] ] }, @@ -1624,7 +2184,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1638,41 +2198,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 7.347089488260391, - "scoreError" : 0.025160682828075858, + "score" : 7.332264236852336, + "scoreError" : 0.0252345687851438, "scoreConfidence" : [ - 7.321928805432315, - 7.372250171088466 + 7.307029668067193, + 7.35749880563748 ], "scorePercentiles" : { - "0.0" : 7.344136033159427, - "50.0" : 7.345870502020608, - "90.0" : 7.35248091584092, - "95.0" : 7.35248091584092, - "99.0" : 7.35248091584092, - "99.9" : 7.35248091584092, - "99.99" : 7.35248091584092, - "99.999" : 7.35248091584092, - "99.9999" : 7.35248091584092, - "100.0" : 7.35248091584092 + "0.0" : 7.32926086007502, + "50.0" : 7.330910436590052, + "90.0" : 7.337975214154221, + "95.0" : 7.337975214154221, + "99.0" : 7.337975214154221, + "99.9" : 7.337975214154221, + "99.99" : 7.337975214154221, + "99.999" : 7.337975214154221, + "99.9999" : 7.337975214154221, + "100.0" : 7.337975214154221 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 7.344334413439188, - 7.347406590602027, - 7.35248091584092, - 7.344136033159427 + 7.330437252894339, + 7.337975214154221, + 7.32926086007502, + 7.331383620285766 ] ] }, diff --git a/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala b/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala index ecb5e3b6..8cc4c84f 100644 --- a/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala +++ b/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala @@ -7,13 +7,21 @@ import tethys.jackson._ object TethysBench { - implicit val dataWriter: JsonWriter[Data] = tethys.derivation.semiauto.jsonWriter[Data] - implicit val dataReader: JsonReader[Data] = tethys.derivation.semiauto.jsonReader[Data] + implicit val dataWriter: JsonWriter[Data] = + tethys.derivation.semiauto.jsonWriter[Data] + implicit val dataReader: JsonReader[Data] = + tethys.derivation.semiauto.jsonReader[Data] + + object TethysDataProcessor extends DataWriter with DataReader { + override def write(seq: Seq[Data]): String = seq.asJson + override def read(json: String): Seq[Data] = ??? + } object TethysJacksonDataProcessor extends DataWriter with DataReader { override def write(seq: Seq[Data]): String = seq.asJson - override def read(json: String): Seq[Data] = json.jsonAs[Seq[Data]].toOption.get + override def read(json: String): Seq[Data] = + json.jsonAs[Seq[Data]].toOption.get } } diff --git a/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala b/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala index 18a22c21..bee6d861 100644 --- a/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala +++ b/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala @@ -2,21 +2,32 @@ package json.bench.tethysjson import json.bench.model.Data import json.bench.{DataReader, DataWriter} -import tethys.* -import tethys.jackson.* +import tethys._ -/** - * Created by eld0727 on 21.04.17. +/** Created by eld0727 on 21.04.17. */ object TethysBench { - implicit val dataWriter: JsonWriter[Data] = JsonWriter.derived[Data] - implicit val dataReader: JsonReader[Data] = JsonReader.derived[Data] + implicit val dataWriter: JsonWriter[Seq[Data]] = + JsonWriter.iterableWriter(tethys.derivation.semiauto.jsonWriter[Data]) + implicit val dataReader: JsonReader[Seq[Data]] = + JsonReader.iterableReader(tethys.derivation.semiauto.jsonReader[Data]) + + object TethysDataProcessor extends DataWriter with DataReader { + override def write(seq: Seq[Data]): String = seq.asJson + override def read(json: String): Seq[Data] = + json.jsonAs[Seq[Data]] match { + case Right(data) => data + case Left(ex) => throw ex + } + } object TethysJacksonDataProcessor extends DataWriter with DataReader { + import tethys.jackson._ override def write(seq: Seq[Data]): String = seq.asJson - override def read(json: String): Seq[Data] = json.jsonAs[Seq[Data]].toOption.get + override def read(json: String): Seq[Data] = + json.jsonAs[Seq[Data]].toOption.get } } diff --git a/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala b/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala index a3ec4d3d..74d8484b 100644 --- a/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala +++ b/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala @@ -19,22 +19,24 @@ object BenchMarkdown { val benchmarksOrdering: Ordering[String] = Ordering.by[String, Int] { case "Parsing" | "json.bench.JmhReaderBench.bench" => 1 case "Writing" | "json.bench.JmhWriterBench.bench" => 2 - case _ => 3 + case _ => 3 } val processorsOrdering: Ordering[String] = Ordering.by[String, Int] { + case "tethys" => 0 case "tethys-jackson" => 1 - case "pure-jackson" => 2 - case "circe" => 3 - case "circe-jawn" => 4 - case "circe-jackson" => 5 + case "pure-jackson" => 2 + case "circe" => 3 + case "circe-jawn" => 4 + case "circe-jackson" => 5 + case "jsoniter" => 7 case "json4s-jackson" => 8 - case "json4s-native" => 9 - case "play-json" => 10 - case "spray-json" => 11 - case "pushka" => 12 - case "zio-json" => 13 - case _ => 13 + case "json4s-native" => 9 + case "play-json" => 10 + case "spray-json" => 11 + case "pushka" => 12 + case "zio-json" => 13 + case _ => 13 } val namesMapping = Map( @@ -42,17 +44,30 @@ object BenchMarkdown { "json.bench.JmhWriterBench.bench" -> "Writing" ) - implicit val mbScore: JsonReader[Either[String, Double]] = new JsonReader[Either[String, Double]] { - override def read(it: TokenIterator)(implicit fieldName: FieldName): Either[String, Double] = { - if(it.currentToken().isNumberValue) Right(Math.round(JsonReader.doubleReader.read(it) * 1000) / 1000.0) - else Left(JsonReader.stringReader.read(it)) + implicit val mbScore: JsonReader[Either[String, Double]] = + new JsonReader[Either[String, Double]] { + override def read( + it: TokenIterator + )(implicit fieldName: FieldName): Either[String, Double] = { + if (it.currentToken().isNumberValue) + Right(Math.round(JsonReader.doubleReader.read(it) * 1000) / 1000.0) + else Left(JsonReader.stringReader.read(it)) + } } - } - implicit val primaryMetricsReader: JsonReader[PrimaryMetrics] = jsonReader[PrimaryMetrics] + implicit val primaryMetricsReader: JsonReader[PrimaryMetrics] = + jsonReader[PrimaryMetrics] implicit val benchmarkReader: JsonReader[Benchmark] = jsonReader[Benchmark] - case class Benchmark(benchmark: String, mode: String, params: mutable.LinkedHashMap[String, String], primaryMetric: PrimaryMetrics) - case class PrimaryMetrics(score: Either[String, Double], scoreError: Either[String, Double]) + case class Benchmark( + benchmark: String, + mode: String, + params: mutable.LinkedHashMap[String, String], + primaryMetric: PrimaryMetrics + ) + case class PrimaryMetrics( + score: Either[String, Double], + scoreError: Either[String, Double] + ) def readBenchmarks(dir: String, file: String): Seq[Benchmark] = { val jhmResultsPath = Paths.get(dir, file) @@ -61,15 +76,22 @@ object BenchMarkdown { } def main(args: Array[String]): Unit = { - val List(dir) = args.toList - val benchs = readBenchmarks(dir, "jmh-reader.json") ++ readBenchmarks(dir, "jmh-writer.json") + val dir = util.Try(args(0)).getOrElse(".") + val benchs = readBenchmarks(dir, "jmh-reader.json") ++ readBenchmarks( + dir, + "jmh-writer.json" + ) val grouped = benchs.groupBy(_.benchmark) val mainTables = grouped.toList.sortBy(_._1)(benchmarksOrdering).map { case (name, benchmarks) => - val rows = benchmarks.map(_.params(nameColumn)).distinct.sorted(processorsOrdering) + val rows = benchmarks + .map(_.params(nameColumn)) + .distinct + .sorted(processorsOrdering) val colls = benchmarks.map(_.params(sizeColumn)).distinct val data = benchmarks.map { b => - (b.params(nameColumn), b.params(sizeColumn)) -> b.primaryMetric.score.fold(identity, _.toString) + (b.params(nameColumn), b.params(sizeColumn)) -> b.primaryMetric.score + .fold(identity, _.toString) }.toMap s""" @@ -87,7 +109,7 @@ object BenchMarkdown { private def chart(name: String, bs: Seq[Benchmark], dir: String): String = { val title = namesMapping.getOrElse(name, name) val images = Paths.get(dir, "images") - if(Files.notExists(images)) { + if (Files.notExists(images)) { Files.createDirectory(images) } val imgPath = dir + "/images/" + title + "Performance" @@ -98,16 +120,25 @@ object BenchMarkdown { """.stripMargin } - private def table(rows: Seq[String], columns: Seq[String], data: Map[(String, String), String]): String = { + private def table( + rows: Seq[String], + columns: Seq[String], + data: Map[(String, String), String] + ): String = { val header = columns.foldLeft("name \\ size")(_ + "|" + _) val line = columns.map(_ => "---").foldLeft("---")(_ + "|" + _) val dataLines = rows.map { row => - columns.map(col => data.getOrElse((row, col), " ")).foldLeft(row)(_ + "|" + _) + columns + .map(col => data.getOrElse((row, col), " ")) + .foldLeft(row)(_ + "|" + _) } dataLines.foldLeft(header + "\n" + line)(_ + "\n" + _) } - private def buildPerformanceChart(title: String, benchmarks: Seq[Benchmark]): CategoryChart = { + private def buildPerformanceChart( + title: String, + benchmarks: Seq[Benchmark] + ): CategoryChart = { val chart: CategoryChart = new CategoryChartBuilder() .width(940) .height(400) @@ -116,12 +147,15 @@ object BenchMarkdown { .xAxisTitle("size") .build() - val maxs = benchmarks.groupBy(_.params(sizeColumn)).map { - case (size, bs) => size -> bs.map(_.primaryMetric.score.fold(_ => 0.0, identity)).max + val maxs = benchmarks.groupBy(_.params(sizeColumn)).map { case (size, bs) => + size -> bs.map(_.primaryMetric.score.fold(_ => 0.0, identity)).max } - benchmarks.groupBy(_.params(nameColumn)).toList.sortBy(_._1)(processorsOrdering).foreach { - case (name, bs) => + benchmarks + .groupBy(_.params(nameColumn)) + .toList + .sortBy(_._1)(processorsOrdering) + .foreach { case (name, bs) => import scala.collection.JavaConverters._ val data = bs.map { b => @@ -131,7 +165,7 @@ object BenchMarkdown { val xData = data.map(_._1).asJava val yData = data.map(t => Double.box(t._2)).asJava chart.addSeries(name, xData, yData) - } + } chart } diff --git a/modules/benchmarks/src/main/scala/json/bench/DataReader.scala b/modules/benchmarks/src/main/scala/json/bench/DataReader.scala index 151926a2..57ecabfc 100644 --- a/modules/benchmarks/src/main/scala/json/bench/DataReader.scala +++ b/modules/benchmarks/src/main/scala/json/bench/DataReader.scala @@ -3,6 +3,7 @@ package json.bench import json.bench.circe.CirceBench import json.bench.handwritten.HandwrittenBench import json.bench.json4s.Json4sBench +import json.bench.jsoniter.JsoniterBench import json.bench.model.Data import json.bench.play.PlayBench import json.bench.spray.SprayBench @@ -16,9 +17,11 @@ trait DataReader { object DataReader { val instances: Map[String, DataReader] = Map[String, DataReader]( "tethys-jackson" -> TethysBench.TethysJacksonDataProcessor, + "tethys" -> TethysBench.TethysDataProcessor, "pure-jackson" -> HandwrittenBench.HandwrittenJacksonDataProcessor, "circe-jawn" -> CirceBench.CirceJawnDataReader, "circe-jackson" -> CirceBench.CirceJacksonDataReader, + "jsoniter" -> JsoniterBench.JsoniterProcessor, "json4s-jackson" -> Json4sBench.Json4sJacksonDataProcessor, "json4s-native" -> Json4sBench.Json4sNativeDataProcessor, "play-json" -> PlayBench.PlayDataProcessor, diff --git a/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala b/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala index a54654bc..6f3a5e24 100644 --- a/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala +++ b/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala @@ -3,6 +3,7 @@ package json.bench import json.bench.circe.CirceBench import json.bench.handwritten.HandwrittenBench import json.bench.json4s.Json4sBench +import json.bench.jsoniter.JsoniterBench import json.bench.model.Data import json.bench.play.PlayBench import json.bench.spray.SprayBench @@ -16,12 +17,14 @@ trait DataWriter { object DataWriter { val instances: Map[String, DataWriter] = Map( "tethys-jackson" -> TethysBench.TethysJacksonDataProcessor, + "tethys" -> TethysBench.TethysDataProcessor, "pure-jackson" -> HandwrittenBench.HandwrittenJacksonDataProcessor, "circe" -> CirceBench.CirceDataWriter, "java.lang.StringBuilder" -> HandwrittenBench.HandwrittenJavaDataWriter, "scala.StringBuilder" -> HandwrittenBench.HandwrittenScalaDataWriter, "json4s-jackson" -> Json4sBench.Json4sJacksonDataProcessor, "json4s-native" -> Json4sBench.Json4sNativeDataProcessor, + "jsoniter" -> JsoniterBench.JsoniterProcessor, "play-json" -> PlayBench.PlayDataProcessor, "spray-json" -> SprayBench.SprayDataProcessor, "zio-json" -> ZIOJsonBench.ZIOJsonDataProcesser diff --git a/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala b/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala index e32e0131..9a94ce86 100644 --- a/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala @@ -13,40 +13,46 @@ import org.openjdk.jmh.annotations.{State, _} @Fork(value = 1, jvmArgsAppend = Array("-Xms1G", "-Xmx1G")) @State(Scope.Benchmark) class JmhReaderBench { - @Param(Array( - "128b", - "1kb", - "128kb", - "1mb", - "32mb" - )) + @Param( + Array( + "128b" + // "1kb", + // "128kb", + // "1mb", + // "32mb" + ) + ) var jsonSize: String = _ val seed = 10000 - var data: String =_ + var data: String = _ @Setup(Level.Trial) def setup(): Unit = { val entities = jsonSize match { - case "128b" => Data.dataSamples(1, seed) - case "1kb" => Data.dataSamples(8, seed) + case "128b" => Data.dataSamples(1, seed) + case "1kb" => Data.dataSamples(8, seed) case "128kb" => Data.dataSamples(128 * 8, seed) - case "1mb" => Data.dataSamples(8 * 128 * 8, seed) - case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) + case "1mb" => Data.dataSamples(8 * 128 * 8, seed) + case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) } data = TethysJacksonDataProcessor.write(entities) } - @Param(Array( - "tethys-jackson", - "pure-jackson", - "circe-jawn", - "circe-jackson", - "play-json", - "spray-json", - "zio-json" - )) + @Param( + Array( + "tethys", + "tethys-jackson" + // "jsoniter" + // "pure-jackson", + // "circe-jawn", + // "circe-jackson", + // "play-json", + // "spray-json", + // "zio-json" + ) + ) var processorName: String = _ @Benchmark diff --git a/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala b/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala index 60bcae5c..5ab1ae8c 100644 --- a/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala @@ -7,18 +7,20 @@ import org.openjdk.jmh.annotations._ @BenchmarkMode(Array(Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) -@Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) +@Warmup(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(value = 1, jvmArgsAppend = Array("-Xms1G", "-Xmx1G")) @State(Scope.Benchmark) class JmhWriterBench { - @Param(Array( - "128b", - "1kb", - "128kb", - "1mb", - "32mb" - )) + @Param( + Array( + "128b" + // "1kb", + // "128kb", + // "1mb", + // "32mb" + ) + ) var jsonSize: String = _ val seed = 10000 @@ -27,22 +29,26 @@ class JmhWriterBench { @Setup(Level.Trial) def setup(): Unit = { data = jsonSize match { - case "128b" => Data.dataSamples(1, seed) - case "1kb" => Data.dataSamples(8, seed) + case "128b" => Data.dataSamples(1, seed) + case "1kb" => Data.dataSamples(8, seed) case "128kb" => Data.dataSamples(128 * 8, seed) - case "1mb" => Data.dataSamples(8 * 128 * 8, seed) - case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) + case "1mb" => Data.dataSamples(8 * 128 * 8, seed) + case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) } } - @Param(Array( - "tethys-jackson", - "pure-jackson", - "circe", - "play-json", - "spray-json", - "zio-json" - )) + @Param( + Array( + "tethys" + // "tethys-jackson", + // "jsoniter", + // "pure-jackson", + // "circe", + // "play-json", + // "spray-json", + // "zio-json" + ) + ) var processorName: String = _ @Benchmark diff --git a/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala b/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala index 87f8a2e6..1527cc68 100644 --- a/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala @@ -15,10 +15,12 @@ object CirceBench { } object CirceJawnDataReader extends DataReader { - override def read(json: String): Seq[Data] = jawn.decode[Seq[Data]](json).toOption.get + override def read(json: String): Seq[Data] = + jawn.decode[Seq[Data]](json).toOption.get } object CirceJacksonDataReader extends DataReader { - override def read(json: String): Seq[Data] = jackson.decode[Seq[Data]](json).toOption.get + override def read(json: String): Seq[Data] = + jackson.decode[Seq[Data]](json).toOption.get } } diff --git a/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala b/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala index 42d27f32..5b822272 100644 --- a/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala @@ -14,10 +14,10 @@ object HandwrittenBench { val builder = new StringBuilder("[") val dataIterator = seq.iterator - if(dataIterator.hasNext) { + if (dataIterator.hasNext) { writeData(dataIterator.next(), builder) } - while(dataIterator.hasNext) { + while (dataIterator.hasNext) { writeData(dataIterator.next(), builder.append(",")) } @@ -45,10 +45,10 @@ object HandwrittenBench { .append('[') val intIter = data.seqInt.iterator - if(intIter.hasNext) { + if (intIter.hasNext) { builder.append(intIter.next()) } - while(intIter.hasNext) { + while (intIter.hasNext) { builder.append(",").append(intIter.next()) } @@ -58,11 +58,11 @@ object HandwrittenBench { .append('{') val mapStringIntIter = data.mapStringInt.iterator - if(mapStringIntIter.hasNext) { + if (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } - while(mapStringIntIter.hasNext) { + while (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } @@ -73,7 +73,8 @@ object HandwrittenBench { } - private implicit class ScalaBuilderOps(val builder: StringBuilder) extends AnyVal { + private implicit class ScalaBuilderOps(val builder: StringBuilder) + extends AnyVal { def appendName(name: String): StringBuilder = { builder.append('"') appendString(builder, name) @@ -89,32 +90,33 @@ object HandwrittenBench { private def appendString(builder: StringBuilder, s: String): Unit = { var i = 0 - while(i < s.length) { + while (i < s.length) { appendChar(builder, s.charAt(i)) i = i + 1 } } - private def appendChar(builder: StringBuilder, char: Char): Unit = char match { - case '\n' => builder.append("\\n") - case '\r' => builder.append("\\r") - case '\t' => builder.append("\\t") - case '\b' => builder.append("\\b") - case '\f' => builder.append("\\f") - case '\\' => builder.append("\\\\") - case '"' => builder.append("\\\"") - case _ => builder.append(char) - } + private def appendChar(builder: StringBuilder, char: Char): Unit = + char match { + case '\n' => builder.append("\\n") + case '\r' => builder.append("\\r") + case '\t' => builder.append("\\t") + case '\b' => builder.append("\\b") + case '\f' => builder.append("\\f") + case '\\' => builder.append("\\\\") + case '"' => builder.append("\\\"") + case _ => builder.append(char) + } object HandwrittenJavaDataWriter extends DataWriter { override def write(seq: Seq[Data]): String = { val builder = new java.lang.StringBuilder("[") val dataIterator = seq.iterator - if(dataIterator.hasNext) { + if (dataIterator.hasNext) { writeData(dataIterator.next(), builder) } - while(dataIterator.hasNext) { + while (dataIterator.hasNext) { writeData(dataIterator.next(), builder.append(",")) } @@ -123,7 +125,10 @@ object HandwrittenBench { .toString() } - private def writeData(data: Data, builder: java.lang.StringBuilder): Unit = { + private def writeData( + data: Data, + builder: java.lang.StringBuilder + ): Unit = { builder .append("{") .appendName("string") @@ -142,10 +147,10 @@ object HandwrittenBench { .append('[') val intIter = data.seqInt.iterator - if(intIter.hasNext) { + if (intIter.hasNext) { builder.append(intIter.next()) } - while(intIter.hasNext) { + while (intIter.hasNext) { builder.append(",").append(intIter.next()) } @@ -155,11 +160,11 @@ object HandwrittenBench { .append('{') val mapStringIntIter = data.mapStringInt.iterator - if(mapStringIntIter.hasNext) { + if (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } - while(mapStringIntIter.hasNext) { + while (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } @@ -169,7 +174,8 @@ object HandwrittenBench { } } - private implicit class JavaBuilderOps(val builder: java.lang.StringBuilder) extends AnyVal { + private implicit class JavaBuilderOps(val builder: java.lang.StringBuilder) + extends AnyVal { def appendName(name: String): java.lang.StringBuilder = { builder.append('"') appendString(builder, name) @@ -183,24 +189,28 @@ object HandwrittenBench { } } - private def appendString(builder: java.lang.StringBuilder, s: String): Unit = { + private def appendString( + builder: java.lang.StringBuilder, + s: String + ): Unit = { var i = 0 - while(i < s.length) { + while (i < s.length) { appendChar(builder, s.charAt(i)) i = i + 1 } } - private def appendChar(builder: java.lang.StringBuilder, char: Char): Unit = char match { - case '\n' => builder.append("\\n") - case '\r' => builder.append("\\r") - case '\t' => builder.append("\\t") - case '\b' => builder.append("\\b") - case '\f' => builder.append("\\f") - case '\\' => builder.append("\\\\") - case '"' => builder.append("\\\"") - case _ => builder.append(char) - } + private def appendChar(builder: java.lang.StringBuilder, char: Char): Unit = + char match { + case '\n' => builder.append("\\n") + case '\r' => builder.append("\\r") + case '\t' => builder.append("\\t") + case '\b' => builder.append("\\b") + case '\f' => builder.append("\\f") + case '\\' => builder.append("\\\\") + case '"' => builder.append("\\\"") + case _ => builder.append(char) + } object HandwrittenJacksonDataProcessor extends DataWriter with DataReader { private val jsonFactory = { @@ -227,7 +237,7 @@ object HandwrittenBench { generator.writeStartArray() val dataIterator = seq.iterator - while(dataIterator.hasNext) { + while (dataIterator.hasNext) { writeData(dataIterator.next(), generator) } @@ -251,11 +261,10 @@ object HandwrittenBench { generator.writeFieldName("bigDecimal") generator.writeNumber(data.bigDecimal.bigDecimal) - generator.writeFieldName("seqInt") generator.writeStartArray() val intIter = data.seqInt.iterator - while(intIter.hasNext) { + while (intIter.hasNext) { generator.writeNumber(intIter.next) } generator.writeEndArray() @@ -264,7 +273,7 @@ object HandwrittenBench { generator.writeStartObject() val mapStringIntIter = data.mapStringInt.iterator - while(mapStringIntIter.hasNext) { + while (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() generator.writeFieldName(key) generator.writeNumber(value) @@ -278,7 +287,7 @@ object HandwrittenBench { val parser = jsonParser(json) val builder = Seq.newBuilder[Data] require(parser.nextToken() == JsonToken.START_ARRAY) - while(parser.nextToken() != JsonToken.END_ARRAY) { + while (parser.nextToken() != JsonToken.END_ARRAY) { builder += readDataObject(parser) } builder.result() @@ -303,24 +312,24 @@ object HandwrittenBench { var mapStringIntField: Map[String, Int] = null var mapStringIntFieldInitialized: Boolean = false - while(parser.nextToken() != JsonToken.END_OBJECT) { + while (parser.nextToken() != JsonToken.END_OBJECT) { val field = parser.getCurrentName - if(field == "string") { + if (field == "string") { stringField = parser.nextTextValue() stringFieldInitialized = true - } else if(field == "int") { + } else if (field == "int") { parser.nextToken() intField = parser.getIntValue intFieldInitialized = true - } else if(field == "boolean") { + } else if (field == "boolean") { booleanField = parser.nextBooleanValue() booleanFieldInitialized = true - } else if(field == "bigDecimal") { + } else if (field == "bigDecimal") { parser.nextToken() bigDecimalField = BigDecimal(parser.getNumberValue.doubleValue()) bigDecimalFieldInitialized = true - } else if(field == "seqInt") { + } else if (field == "seqInt") { seqIntField = readSeqOfInt(parser) seqIntFieldInitialized = true } else { @@ -329,7 +338,9 @@ object HandwrittenBench { } } - require(stringFieldInitialized && intFieldInitialized && booleanFieldInitialized && bigDecimalFieldInitialized && mapStringIntFieldInitialized) + require( + stringFieldInitialized && intFieldInitialized && booleanFieldInitialized && bigDecimalFieldInitialized && mapStringIntFieldInitialized + ) Data( string = stringField, diff --git a/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala b/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala index ce5c3656..c1a17b71 100644 --- a/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala @@ -8,14 +8,18 @@ object Json4sBench { implicit val format: DefaultFormats.type = DefaultFormats object Json4sNativeDataProcessor extends DataWriter with DataReader { - override def write(seq: Seq[Data]): String = org.json4s.native.Serialization.write(seq) + override def write(seq: Seq[Data]): String = + org.json4s.native.Serialization.write(seq) - override def read(json: String): Seq[Data] = org.json4s.native.parseJson(json).extract[Seq[Data]] + override def read(json: String): Seq[Data] = + org.json4s.native.parseJson(json).extract[Seq[Data]] } object Json4sJacksonDataProcessor extends DataWriter with DataReader { - override def write(seq: Seq[Data]): String = org.json4s.jackson.Serialization.write(seq) + override def write(seq: Seq[Data]): String = + org.json4s.jackson.Serialization.write(seq) - override def read(json: String): Seq[Data] = org.json4s.jackson.parseJson(json).extract[Seq[Data]] + override def read(json: String): Seq[Data] = + org.json4s.jackson.parseJson(json).extract[Seq[Data]] } } diff --git a/modules/benchmarks/src/main/scala/json/bench/jsoniter/JsoniterBench.scala b/modules/benchmarks/src/main/scala/json/bench/jsoniter/JsoniterBench.scala new file mode 100644 index 00000000..fa6cbf19 --- /dev/null +++ b/modules/benchmarks/src/main/scala/json/bench/jsoniter/JsoniterBench.scala @@ -0,0 +1,17 @@ +package json.bench.jsoniter + +import json.bench.model.Data + +import com.github.plokhotnyuk.jsoniter_scala.macros._ +import com.github.plokhotnyuk.jsoniter_scala.core._ +import json.bench.{DataReader, DataWriter} + +object JsoniterBench { + implicit val dataWrites: JsonValueCodec[Seq[Data]] = JsonCodecMaker.make + + object JsoniterProcessor extends DataWriter with DataReader { + override def write(seq: Seq[Data]): String = writeToString(seq) + + override def read(json: String): Seq[Data] = readFromString[Seq[Data]](json) + } +} diff --git a/modules/benchmarks/src/main/scala/json/bench/model/Data.scala b/modules/benchmarks/src/main/scala/json/bench/model/Data.scala index a171da38..b6674018 100644 --- a/modules/benchmarks/src/main/scala/json/bench/model/Data.scala +++ b/modules/benchmarks/src/main/scala/json/bench/model/Data.scala @@ -2,16 +2,22 @@ package json.bench.model import scala.util.Random -case class Data(string: String, - int: Int, - boolean: Boolean, - bigDecimal: BigDecimal, - seqInt: Seq[Int], - mapStringInt: Map[String, Int]) +case class Data( + string: String, + int: Int, + boolean: Boolean, + bigDecimal: BigDecimal, + seqInt: Seq[Int], + mapStringInt: Map[String, Int] +) object Data { - def samples[JAst](dataBuilder: DataBuilder[JAst], count: Int, seed: Int): JAst = { + def samples[JAst]( + dataBuilder: DataBuilder[JAst], + count: Int, + seed: Int + ): JAst = { val asts = dataSamples(count, seed).map(dataBuilder.ast) dataBuilder.array(asts) } @@ -27,7 +33,7 @@ object Data { (1 to count).toList.map { i => val flag = (i % 2) == 0 - //128 bytes entity + // 128 bytes entity Data( // 2 bytes string = rndString(6 - (if (flag) 0 else 1)), // 9 + 8 (7) + 1 bytes int = rndInt(3), // 6 + 3 + 1 bytes diff --git a/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala b/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala index 085c61f8..7316cf45 100644 --- a/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala @@ -10,7 +10,8 @@ object PlayBench { implicit val dataReads: Reads[Data] = Json.reads[Data] object PlayDataProcessor extends DataWriter with DataReader { - override def write(seq: Seq[Data]): String = Json.stringify(Json.toJson(seq)) + override def write(seq: Seq[Data]): String = + Json.stringify(Json.toJson(seq)) override def read(json: String): Seq[Data] = Json.parse(json).as[Seq[Data]] } diff --git a/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala b/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala index a5f87a54..849fd6de 100644 --- a/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala @@ -12,7 +12,8 @@ object SprayBench { object SprayDataProcessor extends DataWriter with DataReader { override def write(seq: Seq[Data]): String = seq.toJson.compactPrint - override def read(json: String): Seq[Data] = json.parseJson.convertTo[Seq[Data]] + override def read(json: String): Seq[Data] = + json.parseJson.convertTo[Seq[Data]] } } diff --git a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala index d59bdd4f..08985aaf 100644 --- a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala +++ b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala @@ -3,20 +3,21 @@ package tethys import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.TokenIterator -trait OrdinalEnumJsonReader[A] extends JsonReader[A] +class OrdinalEnumJsonReader[A <: scala.reflect.Enum](getByOrdinal: Int => A) + extends JsonReader[A]: + override def read(it: TokenIterator)(using FieldName): A = + if it.currentToken().isNumberValue then + val res = it.int() + it.next() + try getByOrdinal(res) + catch + case ex: NoSuchElementException => + ReaderError.wrongJson(s"Unknown enum ordinal: $res") + else + ReaderError.wrongJson( + s"Expected int value but found: ${it.currentToken()}" + ) object OrdinalEnumJsonReader: inline def derived[A <: scala.reflect.Enum]: OrdinalEnumJsonReader[A] = - new OrdinalEnumJsonReader[A]: - def read(it: TokenIterator)(implicit fieldName: FieldName): A = - if it.currentToken().isNumberValue then - val res = it.int() - it.next() - try derivation.EnumCompanion.getByOrdinal[A](res) - catch - case ex: NoSuchElementException => - ReaderError.wrongJson(s"Unknown enum ordinal: $res") - else - ReaderError.wrongJson( - s"Expected int value but found: ${it.currentToken()}" - ) + new OrdinalEnumJsonReader[A](derivation.EnumCompanion.getByOrdinal[A]) diff --git a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala index 457d1330..6bf7e7d6 100644 --- a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala +++ b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala @@ -2,16 +2,23 @@ package tethys import tethys.writers.tokens.TokenWriter -trait OrdinalEnumJsonWriter[A] extends JsonWriter[A] +class OrdinalEnumJsonWriter[A <: scala.reflect.Enum] extends JsonWriter[A] { + override def write(value: A, tokenWriter: TokenWriter): Unit = + tokenWriter.writeNumber(value.ordinal) +} object OrdinalEnumJsonWriter: inline def derived[A <: scala.reflect.Enum]: OrdinalEnumJsonWriter[A] = - (value: A, tokenWriter: TokenWriter) => - tokenWriter.writeNumber(value.ordinal) + new OrdinalEnumJsonWriter[A] - inline def withLabel[A <: scala.reflect.Enum]( - label: String - ): JsonObjectWriter[A] = - (value: A, tokenWriter: writers.tokens.TokenWriter) => + private class WithLabel[A <: scala.reflect.Enum](label: String) + extends JsonObjectWriter[A] { + def writeValues(value: A, tokenWriter: TokenWriter): Unit = { tokenWriter.writeFieldName(label) tokenWriter.writeNumber(value.ordinal) + } + } + def withLabel[A <: scala.reflect.Enum]( + label: String + ): JsonObjectWriter[A] = + new WithLabel[A](label) diff --git a/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala b/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala index e143d371..857ea7ab 100644 --- a/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala +++ b/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala @@ -3,20 +3,23 @@ package tethys import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.TokenIterator -trait StringEnumJsonReader[A] extends JsonReader[A] +class StringEnumJsonReader[A <: scala.reflect.Enum]( + getByName: String => A +) extends JsonReader[A] { + override def read(it: TokenIterator)(using FieldName): A = + if it.currentToken().isStringValue then + val res = it.string() + it.next() + try getByName(res) + catch + case ex: NoSuchElementException => + ReaderError.wrongJson(s"Unknown enum name: $res") + else + ReaderError.wrongJson( + s"Expected string value but found: ${it.currentToken()}" + ) +} object StringEnumJsonReader: inline def derived[A <: scala.reflect.Enum]: StringEnumJsonReader[A] = - new StringEnumJsonReader[A]: - def read(it: TokenIterator)(implicit fieldName: FieldName): A = - if it.currentToken().isStringValue then - val res = it.string() - it.next() - try derivation.EnumCompanion.getByName[A](res) - catch - case ex: NoSuchElementException => - ReaderError.wrongJson(s"Unknown enum name: $res") - else - ReaderError.wrongJson( - s"Expected string value but found: ${it.currentToken()}" - ) + new StringEnumJsonReader[A](derivation.EnumCompanion.getByName[A]) diff --git a/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala b/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala index c63a0b84..1a3da768 100644 --- a/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala +++ b/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala @@ -1,16 +1,23 @@ package tethys import tethys.writers.tokens.TokenWriter -trait StringEnumJsonWriter[A] extends JsonWriter[A] +class StringEnumJsonWriter[A <: scala.reflect.Enum] extends JsonWriter[A] { + def write(value: A, tokenWriter: TokenWriter): Unit = + tokenWriter.writeString(value.toString) +} object StringEnumJsonWriter: inline def derived[A <: scala.reflect.Enum]: StringEnumJsonWriter[A] = - (value: A, tokenWriter: TokenWriter) => - tokenWriter.writeString(value.toString) + new StringEnumJsonWriter[A] - inline def withLabel[A <: scala.reflect.Enum]( - label: String - ): JsonObjectWriter[A] = - (value: A, tokenWriter: writers.tokens.TokenWriter) => + private class WithLabel[A <: scala.reflect.Enum](label: String) + extends JsonObjectWriter[A] { + def writeValues(value: A, tokenWriter: TokenWriter): Unit = { tokenWriter.writeFieldName(label) tokenWriter.writeString(value.toString) + } + } + + def withLabel[A <: scala.reflect.Enum]( + label: String + ): JsonObjectWriter[A] = new WithLabel[A](label) diff --git a/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala b/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala index 66661169..4eb51562 100644 --- a/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala +++ b/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala @@ -148,7 +148,7 @@ private[derivation] class DerivationMacro(val quotes: Quotes) .get(field.tpe) .fold(lookup[JsonWriter[f]])(_.asExprOf[JsonWriter[f]]) '{ - ${ writer }.write( + $writer.write( ${ field.label }, ${ field.value('{ value }.asTerm).asExprOf[f] }, tokenWriter diff --git a/modules/core/src/main/scala/tethys/commons/RawJson.scala b/modules/core/src/main/scala/tethys/commons/RawJson.scala index 26b40093..61b45cf2 100644 --- a/modules/core/src/main/scala/tethys/commons/RawJson.scala +++ b/modules/core/src/main/scala/tethys/commons/RawJson.scala @@ -1,10 +1,12 @@ package tethys.commons -import java.io.StringWriter - import tethys.readers.FieldName import tethys.readers.tokens.TokenIterator -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} +import tethys.writers.tokens.{ + TokenWriter, + TokenWriterConfig, + TokenWriterProducer +} import tethys.{JsonReader, JsonStreaming, JsonWriter} final case class RawJson(json: String) @@ -16,16 +18,16 @@ object RawJson { } implicit def rawJsonReader(implicit - tokenWriterProducer: TokenWriterProducer + tokenWriterProducer: TokenWriterProducer, + tokenWriterConfig: TokenWriterConfig ): JsonReader[RawJson] = new JsonReader[RawJson] { override def read( it: TokenIterator )(implicit fieldName: FieldName): RawJson = { - val stringWriter = new StringWriter() - val tokenWriter: TokenWriter = tokenWriterProducer.forWriter(stringWriter) - JsonStreaming.streamValue(it, tokenWriter) - tokenWriter.flush() - RawJson(stringWriter.toString) + val tokenWriter = tokenWriterProducer.produce(tokenWriterConfig) + try JsonStreaming.streamValue(it, tokenWriter) + finally tokenWriter.flush() + RawJson(tokenWriter.result()) } } } diff --git a/modules/core/src/main/scala/tethys/commons/TokenNode.scala b/modules/core/src/main/scala/tethys/commons/TokenNode.scala index 306978d4..73d5bca7 100644 --- a/modules/core/src/main/scala/tethys/commons/TokenNode.scala +++ b/modules/core/src/main/scala/tethys/commons/TokenNode.scala @@ -2,9 +2,7 @@ package tethys.commons import tethys.JsonReader import tethys.commons.Token._ -import tethys.readers.ReaderError import tethys.readers.tokens.{QueueIterator, TokenIteratorProducer} - sealed trait TokenNode { def token: Token } @@ -144,7 +142,8 @@ object TokenNode { implicit class TokenListOps(private val tokens: Seq[TokenNode]) extends AnyVal { - import tethys.TokenIteratorOps + import tethys._ + def tokensAs[A: JsonReader]: A = QueueIterator(tokens).readJson[A].fold(throw _, identity) } diff --git a/modules/core/src/main/scala/tethys/package.scala b/modules/core/src/main/scala/tethys/package.scala index ca0f45e1..e793322d 100644 --- a/modules/core/src/main/scala/tethys/package.scala +++ b/modules/core/src/main/scala/tethys/package.scala @@ -1,9 +1,8 @@ -import java.io.{Reader, StringReader, StringWriter, Writer} - -import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.{TokenIterator, TokenIteratorProducer} -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} +import tethys.readers.{FieldName, ReaderError} +import tethys.writers.tokens.{TokenWriter, TokenWriterConfig, TokenWriterProducer} +import java.io.{Reader, StringReader, StringWriter} import scala.Specializable.Group package object tethys { @@ -12,53 +11,54 @@ package object tethys { (Byte, Short, Int, Long, Float, Double, Boolean) ) - // given - implicit class JsonWriterOps[A](val a: A) extends AnyVal { def asJson(implicit jsonWriter: JsonWriter[A], - tokenWriterProducer: TokenWriterProducer + tokenWriterProducer: TokenWriterProducer, + tokenWriterConfig: TokenWriterConfig ): String = { - val stringWriter = new StringWriter() - writeJson(tokenWriterProducer.forWriter(stringWriter)) - stringWriter.toString + val tokenWriter = tokenWriterProducer.produce(tokenWriterConfig) + try jsonWriter.write(a, tokenWriter) + finally tokenWriter.flush() + tokenWriter.result() } def asJsonWith( jsonWriter: JsonWriter[A] - )(implicit tokenWriterProducer: TokenWriterProducer): String = { - asJson(jsonWriter, tokenWriterProducer) + )(implicit + tokenWriterProducer: TokenWriterProducer, + tokenWriterConfig: TokenWriterConfig + ): String = { + val tokenWriter = tokenWriterProducer.produce(tokenWriterConfig) + try + jsonWriter.write(a, tokenWriter) + finally + tokenWriter.flush() + tokenWriter.result() } def writeJson( tokenWriter: TokenWriter )(implicit jsonWriter: JsonWriter[A]): Unit = { - try jsonWriter.write(a, tokenWriter) - finally { + try + jsonWriter.write(a, tokenWriter) + finally tokenWriter.flush() - } } } - implicit class WriterOps(val w: Writer) extends AnyVal { - def toTokenWriter(implicit - tokenWriterProducer: TokenWriterProducer - ): TokenWriter = tokenWriterProducer.forWriter(w) - } - implicit class StringReaderOps(val json: String) extends AnyVal { def jsonAs[A](implicit jsonReader: JsonReader[A], producer: TokenIteratorProducer ): Either[ReaderError, A] = { - new StringReader(json).readJson[A] + implicit val fieldName: FieldName = FieldName.Root + producer.produce(json).flatMap(it => ReaderError.catchNonFatal(jsonReader.read(it))) } def toTokenIterator(implicit producer: TokenIteratorProducer - ): Either[ReaderError, TokenIterator] = { - new StringReader(json).toTokenIterator - } + ): Either[ReaderError, TokenIterator] = producer.produce(json) } implicit class ReaderReaderOps(val reader: Reader) extends AnyVal { @@ -66,7 +66,7 @@ package object tethys { jsonReader: JsonReader[A], producer: TokenIteratorProducer ): Either[ReaderError, A] = { - implicit val root: FieldName = FieldName() + implicit val root: FieldName = FieldName.Root producer.fromReader(reader).right.flatMap(_.readJson[A]) } @@ -88,7 +88,7 @@ package object tethys { def readJson[A](implicit jsonReader: JsonReader[A] ): Either[ReaderError, A] = { - implicit val fieldName: FieldName = FieldName() + implicit val fieldName: FieldName = FieldName.Root ReaderError.catchNonFatal(jsonReader.read(tokenIterator)) } } diff --git a/modules/core/src/main/scala/tethys/readers/FieldName.scala b/modules/core/src/main/scala/tethys/readers/FieldName.scala index 84ba204e..bfb4d5e0 100644 --- a/modules/core/src/main/scala/tethys/readers/FieldName.scala +++ b/modules/core/src/main/scala/tethys/readers/FieldName.scala @@ -1,6 +1,6 @@ package tethys.readers -final case class FieldName(value: () => String) { +final case class FieldName(value: () => String) extends AnyVal { self => def appendFieldName(s: String): FieldName = FieldName(() => s"${value()}.$s") @@ -8,7 +8,9 @@ final case class FieldName(value: () => String) { def appendArrayIndex(i: Int): FieldName = FieldName(() => s"${value()}[$i]") } object FieldName { - def apply(): FieldName = new FieldName(() => "[ROOT]") + val Root: FieldName = new FieldName(() => "[ROOT]") + + def apply(): FieldName = Root def apply(value: String): FieldName = new FieldName(() => value) } diff --git a/modules/core/src/main/scala/tethys/readers/KeyReader.scala b/modules/core/src/main/scala/tethys/readers/KeyReader.scala index bcd25dbd..c992632c 100644 --- a/modules/core/src/main/scala/tethys/readers/KeyReader.scala +++ b/modules/core/src/main/scala/tethys/readers/KeyReader.scala @@ -1,37 +1,38 @@ package tethys.readers +@specialized(tethys.specializations) trait KeyReader[A] { def read(s: String)(implicit fieldName: FieldName): A } object KeyReader { - implicit lazy val stringKeyReader: KeyReader[String] = new KeyReader[String] { + implicit val stringKeyReader: KeyReader[String] = new KeyReader[String] { override def read(s: String)(implicit fieldName: FieldName): String = s } - implicit lazy val uuidKeyReader: KeyReader[java.util.UUID] = + implicit val uuidKeyReader: KeyReader[java.util.UUID] = new KeyReader[java.util.UUID] { override def read(s: String)(implicit fieldName: FieldName ): java.util.UUID = java.util.UUID.fromString(s) } - implicit lazy val intKeyReader: KeyReader[Int] = new KeyReader[Int] { + implicit val intKeyReader: KeyReader[Int] = new KeyReader[Int] { override def read(s: String)(implicit fieldName: FieldName): Int = s.toInt } - implicit lazy val longKeyReader: KeyReader[Long] = new KeyReader[Long] { + implicit val longKeyReader: KeyReader[Long] = new KeyReader[Long] { override def read(s: String)(implicit fieldName: FieldName): Long = s.toLong } - implicit lazy val instantKeyReader: KeyReader[java.time.Instant] = + implicit val instantKeyReader: KeyReader[java.time.Instant] = new KeyReader[java.time.Instant] { override def read(s: String)(implicit fieldName: FieldName ): java.time.Instant = java.time.Instant.parse(s) } - implicit lazy val localDateKeyReader: KeyReader[java.time.LocalDate] = + implicit val localDateKeyReader: KeyReader[java.time.LocalDate] = new KeyReader[java.time.LocalDate] { override def read( s: String @@ -40,7 +41,7 @@ object KeyReader { .parse(s, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) } - implicit lazy val localDateTimeKeyReader: KeyReader[java.time.LocalDateTime] = + implicit val localDateTimeKeyReader: KeyReader[java.time.LocalDateTime] = new KeyReader[java.time.LocalDateTime] { override def read( s: String @@ -49,8 +50,7 @@ object KeyReader { .parse(s, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) } - implicit lazy val offsetDateTimeKeyReader - : KeyReader[java.time.OffsetDateTime] = + implicit val offsetDateTimeKeyReader: KeyReader[java.time.OffsetDateTime] = new KeyReader[java.time.OffsetDateTime] { override def read( s: String @@ -59,7 +59,7 @@ object KeyReader { .parse(s, java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) } - implicit lazy val zonedDateTimeKeyReader: KeyReader[java.time.ZonedDateTime] = + implicit val zonedDateTimeKeyReader: KeyReader[java.time.ZonedDateTime] = new KeyReader[java.time.ZonedDateTime] { override def read( s: String diff --git a/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala b/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala index 8aa4944a..f98ebee5 100644 --- a/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala +++ b/modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala @@ -5,7 +5,7 @@ import tethys.readers.tokens.TokenIterator import tethys.readers.{FieldName, ReaderError} trait AllJsonReaders extends OptionReaders { - implicit lazy val booleanReader: JsonReader[Boolean] = + implicit val booleanReader: JsonReader[Boolean] = new JsonReader[Boolean] { override def read( it: TokenIterator @@ -22,7 +22,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val stringReader: JsonReader[String] = new JsonReader[String] { + implicit val stringReader: JsonReader[String] = new JsonReader[String] { override def read( it: TokenIterator )(implicit fieldName: FieldName): String = { @@ -38,7 +38,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val charReader: JsonReader[Char] = stringReader.mapWithField { + implicit val charReader: JsonReader[Char] = stringReader.mapWithField { implicit fieldName => { case s if s.length == 1 => s.head @@ -46,7 +46,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val numberReader: JsonReader[Number] = new JsonReader[Number] { + implicit val numberReader: JsonReader[Number] = new JsonReader[Number] { override def read( it: TokenIterator )(implicit fieldName: FieldName): Number = { @@ -62,7 +62,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val byteReader: JsonReader[Byte] = new JsonReader[Byte] { + implicit val byteReader: JsonReader[Byte] = new JsonReader[Byte] { override def read( it: TokenIterator )(implicit fieldName: FieldName): Byte = { @@ -78,7 +78,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val shortReader: JsonReader[Short] = new JsonReader[Short] { + implicit val shortReader: JsonReader[Short] = new JsonReader[Short] { override def read( it: TokenIterator )(implicit fieldName: FieldName): Short = { @@ -94,7 +94,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val intReader: JsonReader[Int] = new JsonReader[Int] { + implicit val intReader: JsonReader[Int] = new JsonReader[Int] { override def read(it: TokenIterator)(implicit fieldName: FieldName): Int = { if (it.currentToken().isNumberValue) { val res = it.int() @@ -108,7 +108,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val longReader: JsonReader[Long] = new JsonReader[Long] { + implicit val longReader: JsonReader[Long] = new JsonReader[Long] { override def read( it: TokenIterator )(implicit fieldName: FieldName): Long = { @@ -124,7 +124,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val floatReader: JsonReader[Float] = new JsonReader[Float] { + implicit val floatReader: JsonReader[Float] = new JsonReader[Float] { override def read( it: TokenIterator )(implicit fieldName: FieldName): Float = { @@ -140,7 +140,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val doubleReader: JsonReader[Double] = new JsonReader[Double] { + implicit val doubleReader: JsonReader[Double] = new JsonReader[Double] { override def read( it: TokenIterator )(implicit fieldName: FieldName): Double = { @@ -156,7 +156,7 @@ trait AllJsonReaders extends OptionReaders { } } - implicit lazy val bigDecimalReader: JsonReader[BigDecimal] = + implicit val bigDecimalReader: JsonReader[BigDecimal] = numberReader.map { case bd: BigDecimal => bd case bi: BigInt => BigDecimal(bi) @@ -171,7 +171,7 @@ trait AllJsonReaders extends OptionReaders { case num => BigDecimal(num.doubleValue()) } - implicit lazy val bigIntReader: JsonReader[BigInt] = numberReader.map { + implicit val bigIntReader: JsonReader[BigInt] = numberReader.map { case bi: BigInt => bi case jbi: java.math.BigInteger => BigInt(jbi) case bd: BigDecimal => bd.toBigInt @@ -183,47 +183,47 @@ trait AllJsonReaders extends OptionReaders { case num => BigInt(num.longValue()) } - implicit lazy val javaBooleanReader: JsonReader[java.lang.Boolean] = + implicit val javaBooleanReader: JsonReader[java.lang.Boolean] = booleanReader.map(a => a) - implicit lazy val javaByteReader: JsonReader[java.lang.Byte] = + implicit val javaByteReader: JsonReader[java.lang.Byte] = byteReader.map(a => a) - implicit lazy val javaShortReader: JsonReader[java.lang.Short] = + implicit val javaShortReader: JsonReader[java.lang.Short] = shortReader.map(a => a) - implicit lazy val javaIntReader: JsonReader[java.lang.Integer] = + implicit val javaIntReader: JsonReader[java.lang.Integer] = intReader.map(a => a) - implicit lazy val javaLongReader: JsonReader[java.lang.Long] = + implicit val javaLongReader: JsonReader[java.lang.Long] = longReader.map(a => a) - implicit lazy val javaFloatReader: JsonReader[java.lang.Float] = + implicit val javaFloatReader: JsonReader[java.lang.Float] = floatReader.map(a => a) - implicit lazy val javaDoubleReader: JsonReader[java.lang.Double] = + implicit val javaDoubleReader: JsonReader[java.lang.Double] = doubleReader.map(a => a) - implicit lazy val javaBigDecimalReader: JsonReader[java.math.BigDecimal] = + implicit val javaBigDecimalReader: JsonReader[java.math.BigDecimal] = bigDecimalReader.map(_.bigDecimal) - implicit lazy val javaBigIntegerReader: JsonReader[java.math.BigInteger] = + implicit val javaBigIntegerReader: JsonReader[java.math.BigInteger] = bigIntReader.map(_.bigInteger) - implicit lazy val javaUUIDReader: JsonReader[java.util.UUID] = + implicit val javaUUIDReader: JsonReader[java.util.UUID] = stringReader.map(java.util.UUID.fromString(_)) - implicit lazy val javaInstantReader: JsonReader[java.time.Instant] = + implicit val javaInstantReader: JsonReader[java.time.Instant] = stringReader.map(java.time.Instant.parse) - implicit lazy val javaLocalDateReader: JsonReader[java.time.LocalDate] = + implicit val javaLocalDateReader: JsonReader[java.time.LocalDate] = stringReader.map( java.time.LocalDate .parse(_, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) ) - implicit lazy val javaLocalDateTimeReader + implicit val javaLocalDateTimeReader : JsonReader[java.time.LocalDateTime] = stringReader.map( java.time.LocalDateTime .parse(_, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) ) - implicit lazy val javaOffsetDateTimeReader + implicit val javaOffsetDateTimeReader : JsonReader[java.time.OffsetDateTime] = stringReader.map( java.time.OffsetDateTime .parse(_, java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) ) - implicit lazy val javaZonedDateTimeReader + implicit val javaZonedDateTimeReader : JsonReader[java.time.ZonedDateTime] = stringReader.map( java.time.ZonedDateTime diff --git a/modules/core/src/main/scala/tethys/readers/instances/IterableReaders.scala b/modules/core/src/main/scala/tethys/readers/instances/IterableReaders.scala index 555a7b9e..9304da22 100644 --- a/modules/core/src/main/scala/tethys/readers/instances/IterableReaders.scala +++ b/modules/core/src/main/scala/tethys/readers/instances/IterableReaders.scala @@ -1,143 +1,28 @@ package tethys.readers.instances import tethys.JsonReader -import tethys.compat.CollectionBuilder +import tethys.commons.Token import tethys.readers.tokens.TokenIterator -import tethys.readers.{FieldName, ReaderError} +import tethys.readers.FieldName -import scala.annotation.tailrec -import scala.collection.mutable +import scala.collection.Factory import scala.language.higherKinds -private[tethys] trait IterableReaders extends LowPriorityIterableReaders { - - implicit def byteIterableReader[C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[Byte, C[Byte]] - ): JsonReader[C[Byte]] = new TraversableReader[Byte, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[Byte, C[Byte]] - )(implicit fieldName: FieldName): Unit = { - builder += PrimitiveReaders.ByteJsonReader.read(it) - } - } - - implicit def shortIterableReader[C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[Short, C[Short]] - ): JsonReader[C[Short]] = new TraversableReader[Short, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[Short, C[Short]] - )(implicit fieldName: FieldName): Unit = { - builder += PrimitiveReaders.ShortJsonReader.read(it) - } - } - - implicit def intIterableReader[C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[Int, C[Int]] - ): JsonReader[C[Int]] = new TraversableReader[Int, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[Int, C[Int]] - )(implicit fieldName: FieldName): Unit = { - builder += PrimitiveReaders.IntJsonReader.read(it) - } - } - - implicit def longIterableReader[C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[Long, C[Long]] - ): JsonReader[C[Long]] = new TraversableReader[Long, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[Long, C[Long]] - )(implicit fieldName: FieldName): Unit = { - builder += PrimitiveReaders.LongJsonReader.read(it) - } - } - - implicit def floatIterableReader[C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[Float, C[Float]] - ): JsonReader[C[Float]] = new TraversableReader[Float, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[Float, C[Float]] - )(implicit fieldName: FieldName): Unit = { - builder += PrimitiveReaders.FloatJsonReader.read(it) - } - } - - implicit def doubleIterableReader[C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[Double, C[Double]] - ): JsonReader[C[Double]] = new TraversableReader[Double, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[Double, C[Double]] - )(implicit fieldName: FieldName): Unit = { - builder += PrimitiveReaders.DoubleJsonReader.read(it) - } - } - - implicit def booleanIterableReader[C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[Boolean, C[Boolean]] - ): JsonReader[C[Boolean]] = new TraversableReader[Boolean, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[Boolean, C[Boolean]] - )(implicit fieldName: FieldName): Unit = { - builder += PrimitiveReaders.BooleanJsonReader.read(it) - } - } -} - -private[tethys] trait LowPriorityIterableReaders - extends LowPriorityJsonReaders { - +private[tethys] trait IterableReaders extends LowPriorityJsonReaders { implicit def iterableReader[A, C[X] <: Iterable[X]](implicit jsonReader: JsonReader[A], - collectionBuilder: CollectionBuilder[A, C[A]] - ): JsonReader[C[A]] = new TraversableReader[A, C] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[A, C[A]] - )(implicit fieldName: FieldName): Unit = { - builder += jsonReader.read(it) - } - } - - protected abstract class TraversableReader[A, C[X] <: Iterable[X]](implicit - cb: CollectionBuilder[A, C[A]] - ) extends JsonReader[C[A]] { - protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[A, C[A]] - )(implicit fieldName: FieldName): Unit - - override def read( - it: TokenIterator - )(implicit fieldName: FieldName): C[A] = { - if (it.currentToken().isArrayStart) recRead(0, it.next(), cb.newBuilder) - else - ReaderError.wrongJson( - s"Expected array start but found: ${it.currentToken()}" - ) - } - - @tailrec - private def recRead( - i: Int, - it: TokenIterator, - builder: mutable.Builder[A, C[A]] - )(implicit fieldName: FieldName): C[A] = { - it.currentToken() match { - case token if token.isEmpty => - ReaderError.wrongJson("Unexpected end of input") - case token if token.isArrayEnd => - it.nextToken() - builder.result() - case _ => - appendBuilder(it, builder)(fieldName.appendArrayIndex(i)) - recRead(i + 1, it, builder) + from: Factory[A, C[A]] + ): JsonReader[C[A]] = new JsonReader[C[A]] { + override def read(it: TokenIterator)(implicit fieldName: FieldName) = { + val builder = from.newBuilder + it.nextToken() + var idx = 0 + while (it.currentToken() != Token.ArrayEndToken) { + builder.addOne(jsonReader.read(it)(fieldName.appendArrayIndex(idx))) + idx += 1 } + it.nextToken() + builder.result() } } } diff --git a/modules/core/src/main/scala/tethys/readers/instances/MapReaders.scala b/modules/core/src/main/scala/tethys/readers/instances/MapReaders.scala index ce2b0de3..ab7027e2 100644 --- a/modules/core/src/main/scala/tethys/readers/instances/MapReaders.scala +++ b/modules/core/src/main/scala/tethys/readers/instances/MapReaders.scala @@ -1,188 +1,34 @@ package tethys.readers.instances import tethys.JsonReader -import tethys.compat.CollectionBuilder +import tethys.commons.Token import tethys.readers.tokens.TokenIterator import tethys.readers.{FieldName, KeyReader, ReaderError} -import scala.annotation.tailrec -import scala.collection.mutable +import scala.collection.Factory import scala.language.higherKinds -private[tethys] trait MapReaders extends LowPriorityMapReaders { - implicit def byteMapReader[K, M[X, Y] <: scala.collection.Map[X, Y]](implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, Byte), M[K, Byte]] - ): JsonReader[M[K, Byte]] = { - new MapReader[K, Byte, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, Byte), M[K, Byte]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> PrimitiveReaders.ByteJsonReader.read(it) - } - } - } - - implicit def shortMapReader[K, M[X, Y] <: scala.collection.Map[X, Y]](implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, Short), M[K, Short]] - ): JsonReader[M[K, Short]] = { - new MapReader[K, Short, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, Short), M[K, Short]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> PrimitiveReaders.ShortJsonReader.read(it) - } - } - } - - implicit def intMapReader[K, M[X, Y] <: scala.collection.Map[X, Y]](implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, Int), M[K, Int]] - ): JsonReader[M[K, Int]] = { - new MapReader[K, Int, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, Int), M[K, Int]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> PrimitiveReaders.IntJsonReader.read(it) - } - } - } - - implicit def longMapReader[K, M[X, Y] <: scala.collection.Map[X, Y]](implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, Long), M[K, Long]] - ): JsonReader[M[K, Long]] = { - new MapReader[K, Long, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, Long), M[K, Long]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> PrimitiveReaders.LongJsonReader.read(it) - } - } - } - - implicit def floatMapReader[K, M[X, Y] <: scala.collection.Map[X, Y]](implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, Float), M[K, Float]] - ): JsonReader[M[K, Float]] = { - new MapReader[K, Float, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, Float), M[K, Float]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> PrimitiveReaders.FloatJsonReader.read(it) - } - } - } - - implicit def doubleMapReader[K, M[X, Y] <: scala.collection.Map[X, Y]]( - implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, Double), M[K, Double]] - ): JsonReader[M[K, Double]] = { - new MapReader[K, Double, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, Double), M[K, Double]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> PrimitiveReaders.DoubleJsonReader.read(it) - } - } - } - - implicit def booleanMapReader[K, M[X, Y] <: scala.collection.Map[X, Y]]( - implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, Boolean), M[K, Boolean]] - ): JsonReader[M[K, Boolean]] = { - new MapReader[K, Boolean, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, Boolean), M[K, Boolean]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> PrimitiveReaders.BooleanJsonReader.read(it) - } - } - } -} - -private[tethys] trait LowPriorityMapReaders extends IterableReaders { - - implicit def mapReader[K, A, M[X, Y] <: scala.collection.Map[X, Y]](implicit - keyReader: KeyReader[K], - jsonReader: JsonReader[A], - cb: CollectionBuilder[(K, A), M[K, A]] - ): JsonReader[M[K, A]] = { - new MapReader[K, A, M] { - override protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, A), M[K, A]], - key: K - )(implicit fieldName: FieldName): Unit = { - builder += key -> jsonReader.read(it) - } - } - } - - protected abstract class MapReader[K, A, M[_, _]](implicit - keyReader: KeyReader[K], - cb: CollectionBuilder[(K, A), M[K, A]] - ) extends JsonReader[M[K, A]] { - - protected def appendBuilder( - it: TokenIterator, - builder: mutable.Builder[(K, A), M[K, A]], - key: K - )(implicit fieldName: FieldName): Unit - - override def read( - it: TokenIterator - )(implicit fieldName: FieldName): M[K, A] = { - if (it.currentToken().isObjectStart) - recRead(it.next(), cb.newBuilder)(fieldName) - else - ReaderError.wrongJson( - s"Expected object start but found: ${it.currentToken()}" +private[tethys] trait MapReaders extends IterableReaders { + implicit def mapReader[K, V, M[X, Y] <: scala.collection.Map[X, Y]](implicit + keyReader: KeyReader[K], + valueReader: JsonReader[V], + factory: Factory[(K, V), M[K, V]] + ): JsonReader[M[K, V]] = new JsonReader[M[K, V]] { + override def read(it: TokenIterator)(implicit fieldName: FieldName) = { + val builder = factory.newBuilder + it.nextToken() + while (it.currentToken() != Token.ObjectEndToken) { + val name = it.fieldName() + it.nextToken() + builder.addOne( + ( + keyReader.read(name), + valueReader.read(it)(fieldName.appendFieldName(name)) + ) ) - } - - @tailrec - private def recRead( - it: TokenIterator, - builder: mutable.Builder[(K, A), M[K, A]] - )(fieldName: FieldName): M[K, A] = { - it.currentToken() match { - case token if token.isObjectEnd => - it.nextToken() - builder.result() - case token if token.isFieldName => - val name = it.fieldName() - val nextFieldName = fieldName.appendFieldName(name) - appendBuilder( - it.next(), - builder, - keyReader.read(name)(nextFieldName) - )(nextFieldName) - recRead(it, builder)(fieldName) - - case token => - ReaderError.wrongJson( - s"Expect end of object or field name but '$token' found" - )(fieldName) } + it.nextToken() + builder.result() } } - } diff --git a/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala b/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala index 7c47825f..60f4a209 100644 --- a/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala +++ b/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala @@ -5,7 +5,7 @@ import tethys.readers.FieldName import tethys.readers.tokens.TokenIterator private[tethys] trait OptionReaders extends LowPriorityOptionReaders { - implicit lazy val byteOptionReader: JsonReader[Option[Byte]] = + implicit val byteOptionReader: JsonReader[Option[Byte]] = new OptionJsonReader[Byte] { override protected def readSomeValue( it: TokenIterator @@ -14,7 +14,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val shortOptionReader: JsonReader[Option[Short]] = + implicit val shortOptionReader: JsonReader[Option[Short]] = new OptionJsonReader[Short] { override protected def readSomeValue( it: TokenIterator @@ -23,7 +23,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val intOptionReader: JsonReader[Option[Int]] = + implicit val intOptionReader: JsonReader[Option[Int]] = new OptionJsonReader[Int] { override protected def readSomeValue( it: TokenIterator @@ -32,7 +32,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val longOptionReader: JsonReader[Option[Long]] = + implicit val longOptionReader: JsonReader[Option[Long]] = new OptionJsonReader[Long] { override protected def readSomeValue( it: TokenIterator @@ -41,7 +41,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val floatOptionReader: JsonReader[Option[Float]] = + implicit val floatOptionReader: JsonReader[Option[Float]] = new OptionJsonReader[Float] { override protected def readSomeValue( it: TokenIterator @@ -50,7 +50,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val doubleOptionReader: JsonReader[Option[Double]] = + implicit val doubleOptionReader: JsonReader[Option[Double]] = new OptionJsonReader[Double] { override protected def readSomeValue( it: TokenIterator @@ -59,7 +59,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val booleanOptionReader: JsonReader[Option[Boolean]] = + implicit val booleanOptionReader: JsonReader[Option[Boolean]] = new OptionJsonReader[Boolean] { override protected def readSomeValue( it: TokenIterator diff --git a/modules/core/src/main/scala/tethys/readers/tokens/ByteArrayAccess.java b/modules/core/src/main/scala/tethys/readers/tokens/ByteArrayAccess.java new file mode 100644 index 00000000..c3309a1f --- /dev/null +++ b/modules/core/src/main/scala/tethys/readers/tokens/ByteArrayAccess.java @@ -0,0 +1,46 @@ +package tethys.readers.tokens; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + +class ByteArrayAccess { // FIXME: Use Java wrapper as w/a for missing support of @PolymorphicSignature methods in Scala 3, see: https://github.com/lampepfl/dotty/issues/11332 + private static final VarHandle VH_LONG = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_INT = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_SHORT = + MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_LONG_REVERSED = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle VH_INT_REVERSED = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + static void setLong(byte[] buf, int pos, long value) { + VH_LONG.set(buf, pos, value); + } + + static long getLong(byte[] buf, int pos) { + return (long) VH_LONG.get(buf, pos); + } + + static void setInt(byte[] buf, int pos, int value) { + VH_INT.set(buf, pos, value); + } + + static int getInt(byte[] buf, int pos) { + return (int) VH_INT.get(buf, pos); + } + + static void setShort(byte[] buf, int pos, short value) { + VH_SHORT.set(buf, pos, value); + } + + static void setLongReversed(byte[] buf, int pos, long value) { + VH_LONG_REVERSED.set(buf, pos, value); + } + + static int getIntReversed(byte[] buf, int pos) { + return (int) VH_INT_REVERSED.get(buf, pos); + } +} diff --git a/modules/core/src/main/scala/tethys/readers/tokens/DefaultTokenIterator.scala b/modules/core/src/main/scala/tethys/readers/tokens/DefaultTokenIterator.scala new file mode 100644 index 00000000..7095b63e --- /dev/null +++ b/modules/core/src/main/scala/tethys/readers/tokens/DefaultTokenIterator.scala @@ -0,0 +1,2139 @@ +package tethys.readers.tokens + +import tethys.commons.Token +import java.math.MathContext + +import java.io.InputStream +import java.nio.ByteBuffer +import scala.annotation.tailrec +import scala.util.control.NoStackTrace + +final class DefaultTokenIterator( + private var buf: Array[Byte] = new Array[Byte](32768), + private var head: Int = 0, + private var tail: Int = 0, + private var mark: Int = -1, + private var charBuf: Array[Char] = new Array[Char](4096), + private var bbuf: ByteBuffer = null, + private var in: InputStream = null, + private var totalRead: Long = 0, + private var config: ReaderConfig = null +) extends TokenIterator + with BaseTokenIterator { + import DefaultTokenIterator._ + + private var magnitude: Array[Byte] = _ + private val objectTrace = new scala.collection.mutable.Stack[Boolean](6) + private var token: Token = _ + + def init(s: String): this.type = { + objectTrace.clear() + this.buf = s.getBytes() + this.config = config + head = 0 + val to = buf.length + tail = to + totalRead = 0 + mark = -1 + token = nextTethysToken(null) + this + } + + override def next(): this.type = { + token = nextTethysToken(token) + this + } + + override def currentToken(): Token = token + + override def nextToken(): Token = try { + val b = searchNextToken(head) + val next = + if ( + b == '"' && + objectTrace.nonEmpty + && objectTrace.head && + token != null && + !token.isFieldName + ) + head -= 1 + Token.FieldNameToken + else if (b == '"') + head -= 1 + Token.StringValueToken + else if ((b >= '0' && b <= '9') || b == '-') + head -= 1 + Token.NumberValueToken + else if (b == 'n') readNullOrError(Token.NullValueToken, "expected null") + else if (b == 'f' || b == 't') + head -= 1 + Token.BooleanValueToken + else if (b == '[') + objectTrace.push(false) + Token.ArrayStartToken + else if (b == ']') + objectTrace.pop() + Token.ArrayEndToken + else if (b == '{') + objectTrace.push(true) + Token.ObjectStartToken + else if (b == '}') + objectTrace.pop() + Token.ObjectEndToken + else decodeError("expected value") + token = next + next + } catch case _: EndOfInputError => Token.Empty + + @tailrec + private def searchNextToken(pos: Int): Byte = + if (pos < tail) { + val b = buf(pos) + if (b == ' ' || b == ',' || b == '\n' || (b | 0x4) == '\r') + searchNextToken(pos + 1) + else { + head = pos + 1 + b + } + } else searchNextToken(loadMoreOrError(pos)) + + private def decodeError(msg: String, bs: Int, pos: Int): Nothing = + decodeError( + msg, + (java.lang.Integer.numberOfTrailingZeros(bs ^ 0x6c6c756e) >> 3) + pos - 1 + ) + + private def decodeError( + from: Int, + pos: Int, + cause: Throwable + ): Nothing = { + var i = appendString(", offset: 0x", from) + val offset = + if ((bbuf eq null) && (in eq null)) 0 + else totalRead - tail + i = appendHexOffset(offset + pos, i) + if (config.appendHexDumpToParseException) { + i = appendString(", buf:", i) + i = appendHexDump(pos, offset.toInt, i) + } + throw new JsonReaderException( + new String(charBuf, 0, i), + cause, + config.throwReaderExceptionWithStackTrace + ) + } + + private def appendHexDump(pos: Int, offset: Int, from: Int): Int = { + val hexDumpSizeInBytes = config.hexDumpSize << 4 + val start = Math.max(pos - hexDumpSizeInBytes & 0xfffffff0, 0) + val end = Math.min(pos + hexDumpSizeInBytes + 16 & 0xfffffff0, tail) + val alignedAbsFrom = start + offset & 0xfffffff0 + val alignedAbsTo = end + offset + 15 & 0xfffffff0 + val len = alignedAbsTo - alignedAbsFrom + val bufOffset = alignedAbsFrom - offset + var i = appendChars(dumpBorder, from) + i = appendChars(dumpHeader, i) + i = appendChars(dumpBorder, i) + val buf = this.buf + val ds = hexDigits + var charBuf = this.charBuf + var lim = charBuf.length + var j = 0 + while (j < len) { + val linePos = j & 0xf + if (linePos == 0) { + if (i + 81 >= lim) { // 81 == dumpBorder.length + lim = growCharBuf(i + 81) + charBuf = this.charBuf + } + charBuf(i) = '\n' + charBuf(i + 1) = '|' + charBuf(i + 2) = ' ' + putHexInt(alignedAbsFrom + j, i + 3, charBuf, ds) + charBuf(i + 11) = ' ' + charBuf(i + 12) = '|' + charBuf(i + 13) = ' ' + i += 14 + } + val pos = bufOffset + j + charBuf(i + 50 - (linePos << 1)) = if (pos >= start && pos < end) { + val b = buf(pos) + charBuf(i) = ds(b >> 4 & 0xf) + charBuf(i + 1) = ds(b & 0xf) + charBuf(i + 2) = ' ' + if (b <= 31 || b >= 127) '.' + else b.toChar + } else { + charBuf(i) = ' ' + charBuf(i + 1) = ' ' + charBuf(i + 2) = ' ' + ' ' + } + i += 3 + if (linePos == 15) { + charBuf(i) = '|' + charBuf(i + 1) = ' ' + charBuf(i + 18) = ' ' + charBuf(i + 19) = '|' + i += 20 + } + j += 1 + } + appendChars(dumpBorder, i) + } + + private def appendChar(ch: Char, i: Int): Int = { + ensureCharBufCapacity(i + 1) + charBuf(i) = ch + i + 1 + } + + private def appendChars(cs: Array[Char], i: Int): Int = { + val len = cs.length + val required = i + len + ensureCharBufCapacity(required) + System.arraycopy(cs, 0, charBuf, i, len) + required + } + + private def appendHexOffset(d: Long, i: Int): Int = { + ensureCharBufCapacity(i + 16) + val ds = hexDigits + var j = i + val dl = d.toInt + if (dl != d) { + val dh = (d >> 32).toInt + var shift = 32 - java.lang.Integer.numberOfLeadingZeros(dh) & 0x1c + while (shift >= 0) { + charBuf(j) = ds(dh >> shift & 0xf) + shift -= 4 + j += 1 + } + } + putHexInt(dl, j, charBuf, ds) + j + 8 + } + + private def appendHexByte(b: Byte, i: Int, ds: Array[Char]): Int = { + ensureCharBufCapacity(i + 2) + charBuf(i) = ds(b >> 4 & 0xf) + charBuf(i + 1) = ds(b & 0xf) + i + 2 + } + + private def putHexInt( + d: Int, + i: Int, + charBuf: Array[Char], + ds: Array[Char] + ): Unit = { + charBuf(i) = ds(d >>> 28) + charBuf(i + 1) = ds(d >> 24 & 0xf) + charBuf(i + 2) = ds(d >> 20 & 0xf) + charBuf(i + 3) = ds(d >> 16 & 0xf) + charBuf(i + 4) = ds(d >> 12 & 0xf) + charBuf(i + 5) = ds(d >> 8 & 0xf) + charBuf(i + 6) = ds(d >> 4 & 0xf) + charBuf(i + 7) = ds(d & 0xf) + } + + private def appendString(s: String, i: Int): Int = { + val len = s.length + val required = i + len + ensureCharBufCapacity(required) + s.getChars(0, len, charBuf, i) + required + } + + private def ensureCharBufCapacity(required: Int): Unit = + if (charBuf.length < required) growCharBuf(required): Unit + + private def growCharBuf(required: Int): Int = { + var charBufLen = charBuf.length + val maxCharBufSize = config.maxCharBufSize + if (charBufLen == maxCharBufSize) tooLongStringError() + charBufLen = + (-1 >>> Integer.numberOfLeadingZeros(charBufLen | required)) + 1 + if (Integer.compareUnsigned(charBufLen, maxCharBufSize) > 0) + charBufLen = maxCharBufSize + charBuf = java.util.Arrays.copyOf(charBuf, charBufLen) + charBufLen + } + + private def tooLongInputError(): Nothing = + decodeError("too long part of input exceeded 'maxBufSize'", tail) + + private def tooLongStringError(): Nothing = + decodeError("too long string exceeded 'maxCharBufSize'", tail) + + def decodeError(msg: String): Nothing = decodeError(msg, head - 1) + + private def decodeError( + msg: String, + pos: Int, + cause: Throwable = null + ): Nothing = + decodeError(appendString(msg, 0), pos, cause) + + private def loadMoreOrError(pos: Int): Int = { + if ((bbuf eq null) && (in eq null)) throw EndOfInputError() + loadMore(pos, throwOnEndOfInput = true) + } + + private def loadMore(pos: Int): Int = + if ((bbuf eq null) && (in eq null)) pos + else loadMore(pos, throwOnEndOfInput = false) + + private def loadMore(pos: Int, throwOnEndOfInput: Boolean): Int = { + var newPos = pos + val offset = + if (mark < 0) pos + else mark + if (offset > 0) { + newPos -= offset + val buf = this.buf + val remaining = tail - offset + var i = 0 + while (i < remaining) { + buf(i) = buf(i + offset) + i += 1 + } + if (mark > 0) mark = 0 + tail = remaining + head = newPos + } else growBuf() + var len = buf.length - tail + if (bbuf ne null) { + len = Math.min(bbuf.remaining, len) + bbuf.get(buf, tail, len) + } else len = Math.max(in.read(buf, tail, len), 0) + if (throwOnEndOfInput && len == 0) throw EndOfInputError() + tail += len + totalRead += len + newPos + } + + private class EndOfInputError extends RuntimeException with NoStackTrace + + private def growBuf(): Unit = { + var bufLen = buf.length + val maxBufSize = config.maxBufSize + if (bufLen == maxBufSize) tooLongInputError() + bufLen <<= 1 + if (Integer.compareUnsigned(bufLen, maxBufSize) > 0) bufLen = maxBufSize + buf = java.util.Arrays.copyOf(buf, bufLen) + } + + override def fieldName(): String = readKeyAsString() + + /** Reads a JSON key into the internal char buffer and returns a `String` + * instance. + * + * @return + * a `String` instance of the parsed JSON key + * @throws JsonReaderException + * in cases of reaching the end of input or invalid encoding of JSON key + */ + private def readKeyAsString(): String = { + nextTokenOrError('"', head) + val pos = head + val len = parseString(0, Math.min(tail - pos, charBuf.length), charBuf, pos) + nextTokenOrError(':', head) + new String(charBuf, 0, len) + } + + @tailrec + private def nextTokenOrError(t: Byte, pos: Int): Unit = + if (pos < tail) { + val b = buf(pos) + head = pos + 1 + if ( + b != t && ((b != ' ' && b != '\n' && (b | 0x4) != '\r') || searchNextToken( + pos + 1 + ) != t) + ) tokenError(t, head - 1) + } else nextTokenOrError(t, loadMoreOrError(pos)) + + override def string(): String = readString(null) + + /** Reads a JSON string value into a `String` instance. In case of `null` JSON + * value returns the provided default value or throws a + * [[JsonReaderException]] if the provided default value is `null`. + * + * @param default + * the default `String` value to return if the JSON value is `null`. + * @return + * a `String` instance of the parsed JSON value or the default value if the + * JSON value is `null`. + * @throws JsonReaderException + * in cases of reaching the end of input or invalid encoding of JSON value + * or when both the JSON value and the provided default value are `null` + */ + private def readString(default: String): String = { + val pos = head + 1 + val len = + parseString(0, Math.min(tail - pos, charBuf.length), charBuf, pos) + new String(charBuf, 0, len) + } + + @tailrec + private def parseString( + i: Int, + minLim: Int, + charBuf: Array[Char], + pos: Int + ): Int = + if (i + 3 < minLim) { // Based on SWAR routine of JSON string parsing: https://github.com/sirthias/borer/blob/fde9d1ce674d151b0fee1dd0c2565020c3f6633a/core/src/main/scala/io/bullet/borer/json/JsonParser.scala#L456 + val bs = ByteArrayAccess.getInt(buf, pos) + val m = + ((bs - 0x20202020 ^ 0x3c3c3c3c) - 0x1010101 | (bs ^ 0x5d5d5d5d) + 0x1010101) & 0x80808080 + charBuf(i) = (bs & 0xff).toChar + charBuf(i + 1) = (bs >> 8 & 0xff).toChar + charBuf(i + 2) = (bs >> 16 & 0xff).toChar + charBuf(i + 3) = (bs >> 24).toChar + if (m != 0) { + val offset = java.lang.Integer.numberOfTrailingZeros(m) >> 3 + if ((bs >> (offset << 3)).toByte == '"') { + head = pos + offset + 1 + i + offset + } else + parseEncodedString( + i + offset, + charBuf.length - 1, + charBuf, + pos + offset + ) + } else parseString(i + 4, minLim, charBuf, pos + 4) + } else if (i < minLim) { + val b = buf(pos) + charBuf(i) = b.toChar + if (b == '"') { + head = pos + 1 + i + } else if ((b - 0x20 ^ 0x3c) <= 0) + parseEncodedString(i, charBuf.length - 1, charBuf, pos) + else parseString(i + 1, minLim, charBuf, pos + 1) + } else if (pos >= tail) { + val newPos = loadMoreOrError(pos) + parseString( + i, + Math.min(charBuf.length, i + tail - newPos), + charBuf, + newPos + ) + } else + parseString( + i, + Math.min(growCharBuf(i + 1), i + tail - pos), + this.charBuf, + pos + ) + + @tailrec + private def parseEncodedString( + i: Int, + lim: Int, + charBuf: Array[Char], + pos: Int + ): Int = { + val remaining = tail - pos + if (i < lim) { + if (remaining > 0) { + val b1 = buf(pos) + if (b1 >= 0) { + if (b1 == '"') { + head = pos + 1 + i + } else if (b1 != '\\') { // 0aaaaaaa (UTF-8 byte) -> 000000000aaaaaaa (UTF-16 char) + if (b1 < ' ') unescapedControlCharacterError(pos) + charBuf(i) = b1.toChar + parseEncodedString(i + 1, lim, charBuf, pos + 1) + } else if (remaining > 1) { + val b2 = buf(pos + 1) + if (b2 != 'u') { + charBuf(i) = (b2: @scala.annotation.switch) match { + case '"' => '"' + case 'n' => '\n' + case 'r' => '\r' + case 't' => '\t' + case 'b' => '\b' + case 'f' => '\f' + case '\\' => '\\' + case '/' => '/' + case _ => escapeSequenceError(pos + 1) + } + parseEncodedString(i + 1, lim, charBuf, pos + 2) + } else if (remaining > 5) { + val ch1 = readEscapedUnicode(pos + 2, buf) + charBuf(i) = ch1 + if ((ch1 & 0xf800) != 0xd800) + parseEncodedString(i + 1, lim, charBuf, pos + 6) + else if (remaining > 11) { + if (buf(pos + 6) != '\\') escapeSequenceError(pos + 6) + if (buf(pos + 7) != 'u') escapeSequenceError(pos + 7) + val ch2 = readEscapedUnicode(pos + 8, buf) + charBuf(i + 1) = ch2 + if (ch1 >= 0xdc00 || (ch2 & 0xfc00) != 0xdc00) + decodeError("illegal surrogate character pair", pos + 11) + parseEncodedString(i + 2, lim, charBuf, pos + 12) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else if ((b1 & 0xe0) == 0xc0) { // 110bbbbb 10aaaaaa (UTF-8 bytes) -> 00000bbbbbaaaaaa (UTF-16 char) + if (remaining > 1) { + val b2 = buf(pos + 1) + val ch = + (b1 << 6 ^ b2 ^ 0xf80).toChar // 0xF80 == 0xC0.toByte << 6 ^ 0x80.toByte + charBuf(i) = ch + if ((b2 & 0xc0) != 0x80 || ch < 0x80) + malformedBytesError(b1, b2, pos) + parseEncodedString(i + 1, lim, charBuf, pos + 2) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else if ((b1 & 0xf0) == 0xe0) { // 1110cccc 10bbbbbb 10aaaaaa (UTF-8 bytes) -> ccccbbbbbbaaaaaa (UTF-16 char) + if (remaining > 2) { + val b2 = buf(pos + 1) + val b3 = buf(pos + 2) + val ch = + (b1 << 12 ^ b2 << 6 ^ b3 ^ 0x1f80).toChar // 0x1F80 == (0x80.toByte << 6 ^ 0x80.toByte).toChar + charBuf(i) = ch + if ( + (b2 & 0xc0) != 0x80 || (b3 & 0xc0) != 0x80 || ch < 0x800 || + (ch & 0xf800) == 0xd800 + ) malformedBytesError(b1, b2, b3, pos) + parseEncodedString(i + 1, lim, charBuf, pos + 3) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else if ((b1 & 0xf8) == 0xf0) { // 11110ddd 10ddcccc 10bbbbbb 10aaaaaa (UTF-8 bytes) -> 110110uuuuccccbb 110111bbbbaaaaaa (UTF-16 chars), where uuuu = ddddd - 1 + if (remaining > 3) { + val b2 = buf(pos + 1) + val b3 = buf(pos + 2) + val b4 = buf(pos + 3) + val cp = + b1 << 18 ^ b2 << 12 ^ b3 << 6 ^ b4 ^ 0x381f80 // 0x381F80 == 0xF0.toByte << 18 ^ 0x80.toByte << 12 ^ 0x80.toByte << 6 ^ 0x80.toByte + val ch1 = + ((cp >>> 10) + 0xd7c0).toChar // 0xD7C0 == 0xD800 - (0x10000 >>> 10) + charBuf(i) = ch1 + charBuf(i + 1) = ((cp & 0x3ff) | 0xdc00).toChar + if ( + (b2 & 0xc0) != 0x80 || (b3 & 0xc0) != 0x80 || (b4 & 0xc0) != 0x80 || + (ch1 & 0xf800) != 0xd800 + ) malformedBytesError(b1, b2, b3, b4, pos) + parseEncodedString(i + 2, lim, charBuf, pos + 4) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else malformedBytesError(b1, pos) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else + parseEncodedString( + i, + growCharBuf(i + 2) - 1, + this.charBuf, + pos + ) // 2 is length of surrogate pair + } + + private def readEscapedUnicode(pos: Int, buf: Array[Byte]): Char = { + val ns = nibbles + val x = + ns(buf(pos) & 0xff) << 12 | + ns(buf(pos + 1) & 0xff) << 8 | + ns(buf(pos + 2) & 0xff) << 4 | + ns(buf(pos + 3) & 0xff) + if (x < 0) hexDigitError(pos) + x.toChar + } + + @tailrec + private def hexDigitError(pos: Int): Nothing = { + if (nibbles(buf(pos) & 0xff) < 0) decodeError("expected hex digit", pos) + hexDigitError(pos + 1) + } + + private def malformedBytesError(b1: Byte, pos: Int): Nothing = { + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, hexDigits) + decodeError(i, pos, null) + } + + private def malformedBytesError( + b1: Byte, + b2: Byte, + pos: Int + ): Nothing = { + val ds = hexDigits + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b2, i, ds) + decodeError(i, pos + 1, null) + } + + private def malformedBytesError( + b1: Byte, + b2: Byte, + b3: Byte, + pos: Int + ): Nothing = { + val ds = hexDigits + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b2, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b3, i, ds) + decodeError(i, pos + 2, null) + } + + private def malformedBytesError( + b1: Byte, + b2: Byte, + b3: Byte, + b4: Byte, + pos: Int + ): Nothing = { + val ds = hexDigits + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b2, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b3, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b4, i, ds) + decodeError(i, pos + 3, null) + } + + override def number(): Number = readBigDecimal( + isToken = true, + null, + bigDecimalMathContext, + bigDecimalScaleLimit, + bigDecimalDigitsLimit + ) + + private def readBigDecimal( + isToken: Boolean, + default: BigDecimal, + mc: MathContext, + scaleLimit: Int, + digitsLimit: Int + ): BigDecimal = { + var b = + if (isToken) searchNextToken(head) + else nextByte(head) + if (isToken && b == 'n') readNullOrNumberError(default, head) + else { + var s = 0 + if (b == '-') { + b = nextByte(head) + s = -1 + } + if (b < '0' || b > '9') numberError() + var pos = head + var buf = this.buf + var from = pos - 1 + val oldMark = mark + val newMark = + if (oldMark < 0) from + else oldMark + mark = newMark + var digits = 1 + if (isToken && b == '0') { + if ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) leadingZeroError(pos - 1) + } else { + digits -= pos + var m, bs = 0L + while ( + (pos + 7 < tail || { + digits += pos + pos = loadMore(pos) + digits -= pos + buf = this.buf + pos + 7 < tail + }) && { + bs = ByteArrayAccess.getLong( + buf, + pos + ) // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + m = (bs + 0x4646464646464646L | bs - 0x3030303030303030L) & 0x8080808080808080L + m == 0 + } + ) pos += 8 + if (m == 0) { + while ( + (pos < tail || { + digits += pos + pos = loadMore(pos) + digits -= pos + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) pos += 1 + } else { + val offset = java.lang.Long.numberOfTrailingZeros(m) >> 3 + pos += offset + b = (bs >> (offset << 3)).toByte + } + digits += pos + } + var fracLen, scale = 0 + if (digits >= digitsLimit) + digitsLimitError(pos + digitsLimit - digits - 1) + if (b == '.') { + pos += 1 + fracLen -= pos + var m, bs = 0L + while ( + (pos + 7 < tail || { + fracLen += pos + pos = loadMore(pos) + fracLen -= pos + buf = this.buf + pos + 7 < tail + }) && { + bs = ByteArrayAccess.getLong( + buf, + pos + ) // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + m = (bs + 0x4646464646464646L | bs - 0x3030303030303030L) & 0x8080808080808080L + m == 0 + } + ) pos += 8 + if (m == 0) { + while ( + (pos < tail || { + fracLen += pos + pos = loadMore(pos) + fracLen -= pos + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) pos += 1 + } else { + val offset = java.lang.Long.numberOfTrailingZeros(m) >> 3 + pos += offset + b = (bs >> (offset << 3)).toByte + } + fracLen += pos + digits += fracLen + if (fracLen == 0) numberError(pos) + if (digits >= digitsLimit) + digitsLimitError(pos + digitsLimit - digits - 1) + } + if ((b | 0x20) == 'e') { + b = nextByte(pos + 1) + var ss = 0 + if (b == '-' || b == '+') { + ss = '+' - b >> 31 + b = nextByte(head) + } + if (b < '0' || b > '9') numberError() + scale = '0' - b + pos = head + buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if ( + scale < -214748364 || { + scale = scale * 10 + ('0' - b) + scale > 0 + } + ) numberError(pos) + pos += 1 + } + scale ^= ss + scale -= ss + if (scale == -2147483648) numberError(pos - 1) + } + head = pos + if (mark == 0) from -= newMark + if (mark > oldMark) mark = oldMark + var d = + if (fracLen != 0) { + val limit = from + digits + 1 + val fracPos = limit - fracLen + val fracLimit = fracPos - 1 + if (digits < 19) { + var x = (buf(from) - '0').toLong + from += 1 + while (from < fracLimit) { + x = x * 10 + (buf(from) - '0') + from += 1 + } + from += 1 + while (from < limit) { + x = x * 10 + (buf(from) - '0') + from += 1 + } + java.math.BigDecimal.valueOf((x ^ s) - s, scale + fracLen) + } else + toBigDecimal(buf, from, fracLimit, s, scale).add( + toBigDecimal(buf, fracPos, limit, s, scale + fracLen) + ) + } else toBigDecimal(buf, from, from + digits, s, scale) + if (mc.getPrecision < digits) d = d.plus(mc) + if (Math.abs(d.scale) >= scaleLimit) scaleLimitError() + new BigDecimal(d, mc) + } + } + + @tailrec + private def readNullOrNumberError[@specialized A]( + default: A, + pos: Int + ): A = + if (default != null) { + if (pos + 2 < tail) { + val bs = ByteArrayAccess.getInt(buf, pos - 1) + if (bs == 0x6c6c756e) { + head = pos + 3 + default + } else decodeError("expected number or null", bs, pos) + } else readNullOrNumberError(default, loadMoreOrError(pos - 1) + 1) + } else numberError(pos - 1) + + private def toBigDecimal( + buf: Array[Byte], + p: Int, + limit: Int, + s: Int, + scale: Int + ): java.math.BigDecimal = { + val len = limit - p + if (len < 19) { + var pos = p + var x = (buf(pos) - '0').toLong + pos += 1 + while (pos < limit) { + x = x * 10 + (buf(pos) - '0') + pos += 1 + } + java.math.BigDecimal.valueOf((x ^ s) - s, scale) + } else if (len <= 36) toBigDecimal36(buf, p, limit, s, scale) + else if (len <= 308) + new java.math.BigDecimal(toBigInteger308(buf, p, limit, s), scale) + else { + // Based on the great idea of Eric Obermühlner to use a tree of smaller BigDecimals for parsing really big numbers + // with O(n^1.5) complexity instead of O(n^2) when using the constructor for the decimal representation from JDK: + // https://github.com/eobermuhlner/big-math/commit/7a5419aac8b2adba2aa700ccf00197f97b2ad89f + val mid = len >> 1 + val midPos = limit - mid + toBigDecimal(buf, p, midPos, s, scale - mid).add( + toBigDecimal(buf, midPos, limit, s, scale) + ) + } + } + + private def toBigDecimal36( + buf: Array[Byte], + p: Int, + limit: Int, + s: Int, + scale: Int + ): java.math.BigDecimal = { + val firstBlockLimit = limit - 18 + var pos = p + var x1 = (buf(pos) - '0').toLong + pos += 1 + while (pos < firstBlockLimit) { + x1 = x1 * 10 + (buf(pos) - '0') + pos += 1 + } + val x2 = + ({ // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + val dec = + (ByteArrayAccess.getLong(buf, pos) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 8)) * 1000000000 + { + val dec = + (ByteArrayAccess.getLong(buf, pos + 9) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 17) - 48000000048L + val q = x1 * 1000000000000000000L + val l = q + x2 + val h = Math.multiplyHigh(x1, 1000000000000000000L) + ((~l & q) >>> 63) + if (l >= 0 && h == 0) java.math.BigDecimal.valueOf((l ^ s) - s, scale) + else { + var magnitude = this.magnitude + if (magnitude eq null) { + magnitude = new Array[Byte](128) + this.magnitude = magnitude + } + ByteArrayAccess.setLongReversed(magnitude, 0, h) + ByteArrayAccess.setLongReversed(magnitude, 8, l) + new java.math.BigDecimal( + new java.math.BigInteger(s | 1, magnitude, 0, 16), + scale + ) + } + } + + private def toBigInteger308( + buf: Array[Byte], + p: Int, + limit: Int, + s: Int + ): java.math.BigInteger = { + val len = limit - p + val last = + (len * 222930821L >> 32).toInt << 3 // (len * Math.log(10) / Math.log(1L << 64)).toInt * 8 + var magnitude = this.magnitude + if (magnitude eq null) { + magnitude = new Array[Byte](128) + this.magnitude = magnitude + } else { + var i = 0 + while (i < last) { + ByteArrayAccess.setLong(magnitude, i, 0L) + i += 8 + } + } + var x = 0L + val firstBlockLimit = len % 18 + p + var pos = p + while (pos < firstBlockLimit) { + x = x * 10 + (buf(pos) - '0') + pos += 1 + } + ByteArrayAccess.setLong(magnitude, last, x) + var first = last + while (pos < limit) { + x = + ({ // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + val dec = + (ByteArrayAccess.getLong(buf, pos) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 8)) * 1000000000 + { + val dec = + (ByteArrayAccess.getLong(buf, pos + 9) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 17) - 48000000048L + pos += 18 + first = Math.max(first - 8, 0) + var i = last + val q = 1000000000000000000L + var m, mq = 0L + while ({ + m = ByteArrayAccess.getLong(magnitude, i) + mq = m * q + x += mq + ByteArrayAccess.setLong(magnitude, i, x) + i -= 8 + i >= first + }) { + x = Math.multiplyHigh( + m, + q + ) + (m >> 63 & q) + ((~x & mq) >>> 63) // TODO: when dropping JDK 17 support replace by Math.unsignedMultiplyHigh(m, q) + ((~x & mq) >>> 63) + } + } + var i = 0 + while (i <= last) { + ByteArrayAccess.setLongReversed( + magnitude, + i, + ByteArrayAccess.getLong(magnitude, i) + ) + i += 8 + } + new java.math.BigInteger(s | 1, magnitude, 0, last + 8) + } + + override def byte(): Byte = readByte(isToken = true) + + private def readByte(isToken: Boolean): Byte = { + var b = + if (isToken) searchNextToken(head) + else nextByte(head) + var s = 0 + if (b == '-') { + b = nextByte(head) + s = -1 + } + if (b < '0' || b > '9') numberError() + var x = b - '0' + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + x = x * 10 + (b - '0') + if (x > 128) byteOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if (x == 128) byteOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x.toByte + } + + override def short(): Short = readShort(isToken = true) + + private def readShort(isToken: Boolean): Short = { + var b = + if (isToken) searchNextToken(head) + else nextByte(head) + var s = 0 + if (b == '-') { + b = nextByte(head) + s = -1 + } + if (b < '0' || b > '9') numberError() + var x = b - '0' + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + x = x * 10 + (b - '0') + if (x > 32768) shortOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if (x == 32768) shortOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x.toShort + } + + override def int(): Int = readInt(isToken = true) + + private def readInt(isToken: Boolean): Int = { + var b = + if (isToken) searchNextToken(head) + else nextByte(head) + var s = -1 + if (b == '-') { + b = nextByte(head) + s = 0 + } + if (b < '0' || b > '9') numberError() + var x = '0' - b + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if ( + x < -214748364 || { + x = x * 10 + ('0' - b) + x > 0 + } + ) intOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if ((s & x) == -2147483648) intOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x + } + + private def intOverflowError(pos: Int): Nothing = + decodeError("value is too large for int", pos) + + private def shortOverflowError(pos: Int): Nothing = + decodeError("value is too large for short", pos) + + private def byteOverflowError(pos: Int): Nothing = + decodeError("value is too large for byte", pos) + + override def long(): Long = readLong(isToken = true) + + private def readLong(isToken: Boolean): Long = { + var b = + if (isToken) searchNextToken(head) + else nextByte(head) + var s = -1L + if (b == '-') { + b = nextByte(head) + s = 0L + } + if (b < '0' || b > '9') numberError() + var x = ('0' - b).toLong + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + var dec = 0L + while ( + (pos + 7 < tail || { + pos = loadMore(pos) + buf = this.buf + pos + 7 < tail + }) && { + val bs = ByteArrayAccess.getLong( + buf, + pos + ) // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + dec = bs - 0x3030303030303030L + ((bs + 0x4646464646464646L | dec) & 0x8080808080808080L) == 0 + } + ) { + if ( + x < -92233720368L || { + dec *= 2561 + x *= 100000000 + x -= ((dec >> 8 & 0xff000000ffL) * 4294967296000100L + (dec >> 24 & 0xff000000ffL) * 42949672960001L >> 32) + x > 0 + } + ) longOverflowError(pos + 2) + pos += 8 + } + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if ( + x < -922337203685477580L || { + x = x * 10 + ('0' - b) + x > 0 + } + ) longOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if ((s & x) == -9223372036854775808L) longOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x + } + + private def ensureNotLeadingZero(): Unit = { + var pos = head + if ( + (pos < tail || { + pos = loadMore(pos) + pos < tail + }) && { + val b = buf(pos) + b >= '0' && b <= '9' + } + ) leadingZeroError(pos - 1) + } + + override def float() = readFloat(isToken = true) + + private def readFloat(isToken: Boolean): Float = { + var b = + if (isToken) searchNextToken(head) + else nextByte(head) + var isNeg = false + if (b == '-') { + b = nextByte(head) + isNeg = true + } + if (b < '0' || b > '9') numberError() + var pos = head + var buf = this.buf + val from = pos - 1 + val oldMark = mark + val newMark = + if (oldMark < 0) from + else oldMark + mark = newMark + var m10 = (b - '0').toLong + var e10 = 0 + var digits = 1 + if (isToken && m10 == 0) { + if ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) leadingZeroError(pos - 1) + } else { + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (m10 < 922337203685477580L) { + m10 = m10 * 10 + (b - '0') + digits += 1 + } else e10 += 1 + pos += 1 + } + } + if (b == '.') { + pos += 1 + e10 += digits + var noFracDigits = true + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (m10 < 922337203685477580L) { + m10 = m10 * 10 + (b - '0') + digits += 1 + } + noFracDigits = false + pos += 1 + } + e10 -= digits + if (noFracDigits) numberError(pos) + } + if ((b | 0x20) == 'e') { + b = nextByte(pos + 1) + var s = 0 + if (b == '-' || b == '+') { + s = '+' - b >> 31 + b = nextByte(head) + } + if (b < '0' || b > '9') numberError() + var exp = b - '0' + pos = head + buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (exp < 214748364) exp = exp * 10 + (b - '0') + pos += 1 + } + exp ^= s + exp -= s + e10 += exp + } + head = pos + var x: Float = + if (e10 == 0 && m10 < 922337203685477580L) m10.toFloat + else if (m10 < 4294967296L && e10 >= digits - 23 && e10 <= 19 - digits) { + val pow10 = pow10Doubles + (if (e10 < 0) m10 / pow10(-e10) + else m10 * pow10(e10)).toFloat + } else toFloat(m10, e10, from, newMark, pos) + if (isNeg) x = -x + if (mark > oldMark) mark = oldMark + x + } + + // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical + // Here is his inspiring post: https://www.reddit.com/r/rust/comments/a6j5j1/making_rust_float_parsing_fast_and_correct + private def toFloat( + m10: Long, + e10: Int, + from: Int, + newMark: Int, + pos: Int + ): Float = + if (m10 == 0 || e10 < -64) 0.0f + else if (e10 >= 39) Float.PositiveInfinity + else { + var shift = java.lang.Long.numberOfLeadingZeros(m10) + var m2 = unsignedMultiplyHigh( + pow10Mantissas(e10 + 343), + m10 << shift + ) // FIXME: Use Math.unsignedMultiplyHigh after dropping of JDK 17 support + var e2 = + (e10 * 108853 >> 15) - shift + 1 // (e10 * Math.log(10) / Math.log(2)).toInt - shift + 1 + shift = java.lang.Long.numberOfLeadingZeros(m2) + m2 <<= shift + e2 -= shift + val roundingError = + (if (m10 < 922337203685477580L) 1 + else 19) << shift + val truncatedBitNum = Math.max(-149 - e2, 40) + val savedBitNum = 64 - truncatedBitNum + val mask = -1L >>> Math.max(savedBitNum, 0) + val halfwayDiff = (m2 & mask) - (mask >>> 1) + if (Math.abs(halfwayDiff) > roundingError || savedBitNum <= 0) + java.lang.Float.intBitsToFloat { + var mf = 0 + if (savedBitNum > 0) mf = (m2 >>> truncatedBitNum).toInt + e2 += truncatedBitNum + if (savedBitNum >= 0 && halfwayDiff > 0) { + if (mf == 0xffffff) { + mf = 0x800000 + e2 += 1 + } else mf += 1 + } + if (e2 == -149) mf + else if (e2 >= 105) 0x7f800000 + else e2 + 150 << 23 | mf & 0x7fffff + } + else toFloat(from, newMark, pos) + } + + private def toFloat(from: Int, newMark: Int, pos: Int): Float = { + var offset = from + if (mark == 0) offset -= newMark + java.lang.Float.parseFloat(new String(buf, 0, offset, pos - offset)) + } + + override def double() = readDouble(isToken = true) + + private def readDouble(isToken: Boolean): Double = { + var b = + if (isToken) searchNextToken(head) + else nextByte(head) + var isNeg = false + if (b == '-') { + b = nextByte(head) + isNeg = true + } + if (b < '0' || b > '9') numberError() + var pos = head + var buf = this.buf + val from = pos - 1 + val oldMark = mark + val newMark = + if (oldMark < 0) from + else oldMark + mark = newMark + var m10 = (b - '0').toLong + var e10 = 0 + var digits = 1 + if (isToken && m10 == 0) { + if ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) leadingZeroError(pos - 1) + } else { + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (m10 < 922337203685477580L) { + m10 = m10 * 10 + (b - '0') + digits += 1 + } else e10 += 1 + pos += 1 + } + } + if (b == '.') { + pos += 1 + e10 += digits + var noFracDigits = true + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (m10 < 922337203685477580L) { + m10 = m10 * 10 + (b - '0') + digits += 1 + } + noFracDigits = false + pos += 1 + } + e10 -= digits + if (noFracDigits) numberError(pos) + } + if ((b | 0x20) == 'e') { + b = nextByte(pos + 1) + var s = 0 + if (b == '-' || b == '+') { + s = '+' - b >> 31 + b = nextByte(head) + } + if (b < '0' || b > '9') numberError() + var exp = b - '0' + pos = head + buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (exp < 214748364) exp = exp * 10 + (b - '0') + pos += 1 + } + exp ^= s + exp -= s + e10 += exp + } + head = pos + var x: Double = + if (e10 == 0 && m10 < 922337203685477580L) m10.toDouble + else if (m10 < 4503599627370496L && e10 >= -22 && e10 <= 38 - digits) { + val pow10 = pow10Doubles + if (e10 < 0) m10 / pow10(-e10) + else if (e10 <= 22) m10 * pow10(e10) + else { + val slop = 16 - digits + (m10 * pow10(slop)) * pow10(e10 - slop) + } + } else toDouble(m10, e10, from, newMark, pos) + if (isNeg) x = -x + if (mark > oldMark) mark = oldMark + x + } + + // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical + // Here is his inspiring post: https://www.reddit.com/r/rust/comments/a6j5j1/making_rust_float_parsing_fast_and_correct + private def toDouble( + m10: Long, + e10: Int, + from: Int, + newMark: Int, + pos: Int + ): Double = + if (m10 == 0 || e10 < -343) 0.0 + else if (e10 >= 310) Double.PositiveInfinity + else { + var shift = java.lang.Long.numberOfLeadingZeros(m10) + var m2 = unsignedMultiplyHigh( + pow10Mantissas(e10 + 343), + m10 << shift + ) // FIXME: Use Math.unsignedMultiplyHigh after dropping of JDK 17 support + var e2 = + (e10 * 108853 >> 15) - shift + 1 // (e10 * Math.log(10) / Math.log(2)).toInt - shift + 1 + shift = java.lang.Long.numberOfLeadingZeros(m2) + m2 <<= shift + e2 -= shift + val roundingError = + (if (m10 < 922337203685477580L) 1 + else 19) << shift + val truncatedBitNum = Math.max(-1074 - e2, 11) + val savedBitNum = 64 - truncatedBitNum + val mask = -1L >>> Math.max(savedBitNum, 0) + val halfwayDiff = (m2 & mask) - (mask >>> 1) + if (Math.abs(halfwayDiff) > roundingError || savedBitNum <= 0) + java.lang.Double.longBitsToDouble { + if (savedBitNum <= 0) m2 = 0 + m2 >>>= truncatedBitNum + e2 += truncatedBitNum + if (savedBitNum >= 0 && halfwayDiff > 0) { + if (m2 == 0x1fffffffffffffL) { + m2 = 0x10000000000000L + e2 += 1 + } else m2 += 1 + } + if (e2 == -1074) m2 + else if (e2 >= 972) 0x7ff0000000000000L + else (e2 + 1075).toLong << 52 | m2 & 0xfffffffffffffL + } + else toDouble(from, newMark, pos) + } + + private def unsignedMultiplyHigh(x: Long, y: Long): Long = + Math.multiplyHigh( + x, + y + ) + x + y // Use implementation that works only when both params are negative + + private def toDouble(from: Int, newMark: Int, pos: Int): Double = { + var offset = from + if (mark == 0) offset -= newMark + java.lang.Double.parseDouble(new String(buf, 0, offset, pos - offset)) + } + + override def boolean() = parseBoolean(isToken = true, head) + + override def skipExpression() = { + skip(token) + token = nextTethysToken(null) + this + } + + def nextTethysToken(previous: Token): Token = { + if (!skipWhitespaces()) return Token.Empty + val b = searchNextToken(head) + val token = + if ( + b == '"' && + objectTrace.nonEmpty + && objectTrace.head && + previous != null && + !previous.isFieldName + ) + rollbackToken() + Token.FieldNameToken + else if (b == '"') + rollbackToken() + Token.StringValueToken + else if ((b >= '0' && b <= '9') || b == '-') + rollbackToken() + Token.NumberValueToken + else if (b == 'n') readNullOrError(Token.NullValueToken, "expected null") + else if (b == 'f' || b == 't') + rollbackToken() + Token.BooleanValueToken + else if (b == '[') + objectTrace.push(false) + Token.ArrayStartToken + else if (b == ']') + objectTrace.pop() + Token.ArrayEndToken + else if (b == '{') + objectTrace.push(true) + Token.ObjectStartToken + else if (b == '}') + objectTrace.pop() + Token.ObjectEndToken + else decodeError("expected value") + token + } + + /** Skips whitespace characters in the input. + * + * @return + * `true` if and only if there are non-whitespace characters left in the + * input after skipping the whitespace + */ + private def skipWhitespaces(): Boolean = { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + val b = buf(pos) + b == ' ' || b == '\n' || (b | 0x4) == '\r' + } + ) pos += 1 + head = pos + pos != tail + } + + /** Finishes reading the `null` JSON value and returns the provided default + * value or throws [[JsonReaderException]]. Before calling it the `n` token + * should be parsed already. + * + * @param default + * the default value to return + * @param msg + * the exception message + * @tparam A + * the type of the default value + * @return + * the default value + * @throws JsonReaderException + * in cases of reaching the end of input or illegal format of JSON value or + * when the provided default value is `null` + */ + @tailrec + def readNullOrError[@specialized A](default: A, msg: String): A = + if (default != null) { + val pos = head + if (pos != 0) { + if (pos + 2 < tail) { + val bs = ByteArrayAccess.getInt(buf, pos - 1) + if (bs == 0x6c6c756e) { + head = pos + 3 + default + } else decodeError(msg, bs, pos) + } else if (buf(pos - 1) == 'n') { + head = loadMoreOrError(pos - 1) + 1 + readNullOrError(default, msg) + } else decodeError(msg, pos - 1) + } else illegalTokenOperation() + } else decodeError(msg) + + private def leadingZeroError(pos: Int): Nothing = + decodeError("illegal number with leading zero", pos) + + private def numberError(pos: Int = head - 1): Nothing = + decodeError("illegal number", pos) + + private def scaleLimitError(pos: Int = head - 1): Nothing = + decodeError("value exceeds limit for scale", pos) + + private def digitsLimitError(pos: Int): Nothing = + decodeError("value exceeds limit for number of digits", pos) + + private def longOverflowError(pos: Int): Nothing = + decodeError("value is too large for long", pos) + + /** Rolls back the current reading position by one. + * + * @throws java.lang.IllegalStateException + * if no any token was parsed yet + */ + def rollbackToken(): Unit = { + val pos = head + if (pos == 0) illegalTokenOperation() + head = pos - 1 + } + + private def illegalTokenOperation(): Nothing = + throw new IllegalStateException( + "expected preceding call of 'nextToken()' or 'isNextToken()'" + ) + + /** Skips the next JSON value. + * + * @throws JsonReaderException + * in cases of reaching the end of input + */ + def skip(current: Token): Unit = { + val b: Byte = + if (current.isObjectStart) + objectTrace.pop() + '{' + else if (current.isArrayStart) + objectTrace.pop() + '[' + else searchNextToken(head) + + var pos = head + if (b == '"') pos = skipString(evenBackSlashes = true, pos) + else if ((b >= '0' && b <= '9') || b == '-') pos = skipNumber(pos) + else if (b == 'n' || b == 't') pos = skipFixedBytes(3, pos) + else if (b == 'f') pos = skipFixedBytes(4, pos) + else if (b == '[') pos = skipArray(0, pos) + else if (b == '{') pos = skipObject(0, pos) + else decodeError("expected value") + head = pos + } + + @tailrec + private def skipObject(level: Int, pos: Int): Int = + if (pos < tail) { + val b = buf(pos) + if (b == '"') + skipObject(level, skipString(evenBackSlashes = true, pos + 1)) + else if (b == '{') skipObject(level + 1, pos + 1) + else if (b != '}') skipObject(level, pos + 1) + else if (level != 0) skipObject(level - 1, pos + 1) + else pos + 1 + } else skipObject(level, loadMoreOrError(pos)) + + @tailrec + private def skipArray(level: Int, pos: Int): Int = + if (pos < tail) { + val b = buf(pos) + if (b == '"') + skipArray(level, skipString(evenBackSlashes = true, pos + 1)) + else if (b == '[') skipArray(level + 1, pos + 1) + else if (b != ']') skipArray(level, pos + 1) + else if (level != 0) skipArray(level - 1, pos + 1) + else pos + 1 + } else skipArray(level, loadMoreOrError(pos)) + + @tailrec + private def skipString(evenBackSlashes: Boolean, pos: Int): Int = + if (pos < tail) { + if (evenBackSlashes) { + val b = buf(pos) + if (b == '"') pos + 1 + else skipString(b != '\\', pos + 1) + } else skipString(evenBackSlashes = true, pos + 1) + } else skipString(evenBackSlashes, loadMoreOrError(pos)) + + private def skipNumber(p: Int): Int = { + var pos = p + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + val b = buf(pos) + (b >= '0' && b <= '9') || b == '.' || (b | 0x20) == 'e' || b == '-' || b == '+' + } + ) pos += 1 + pos + } + + @tailrec + private def skipFixedBytes(n: Int, pos: Int): Int = { + val newPos = pos + n + if (newPos <= tail) newPos + else skipFixedBytes(n, loadMoreOrError(pos)) + } + + @tailrec + private def parseBoolean(isToken: Boolean, pos: Int): Boolean = + if (pos + 3 < tail) { + val bs = ByteArrayAccess.getInt(buf, pos) + if (bs == 0x65757274) { + head = pos + 4 + true + } else if (bs == 0x736c6166) { + if (nextByte(pos + 4) != 'e') booleanError(pos + 4) + false + } else if ( + isToken && { + val b1 = bs.toByte + b1 == ' ' || b1 == '\n' || (b1 | 0x4) == '\r' + } + ) parseBoolean(isToken, pos + 1) + else booleanError(bs, pos) + } else parseBoolean(isToken, loadMoreOrError(pos)) + + private def booleanError(bs: Int, pos: Int): Nothing = + booleanError( + (Math.max( + java.lang.Integer.numberOfTrailingZeros(bs ^ 0x65757274), + java.lang.Integer.numberOfTrailingZeros(bs ^ 0x736c6166) + ) >> 3) + pos + ) + + private def escapeSequenceError(pos: Int): Nothing = + decodeError("illegal escape sequence", pos) + + private def unescapedControlCharacterError(pos: Int): Nothing = + decodeError("unescaped control character", pos) + + private def booleanError(pos: Int): Nothing = + decodeError("illegal boolean", pos) + + @tailrec + private def nextByte(pos: Int): Byte = + if (pos < tail) { + head = pos + 1 + buf(pos) + } else nextByte(loadMoreOrError(pos)) + + @tailrec + private def nextByteOrError(t: Byte, pos: Int): Unit = + if (pos < tail) { + if (buf(pos) != t) tokenError(t, pos) + head = pos + 1 + } else nextByteOrError(t, loadMoreOrError(pos)) + + private def tokenError(t: Byte, pos: Int = head - 1): Nothing = { + var i = appendString("expected '", 0) + i = appendChar(t.toChar, i) + i = appendChar('\'', i) + decodeError(i, pos, null) + } + +} + +object DefaultTokenIterator: + private final val hexDigits: Array[Char] = + Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f') + + private final val pow10Doubles: Array[Double] = + Array(1, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, + 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, + 1e+22) + + /* Use the following code to generate `dumpBorder` in Scala REPL: + "\n+----------+-------------------------------------------------+------------------+".toCharArray + .grouped(16).map(_.map(_.toInt).mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val dumpBorder: Array[Char] = Array( + 10, 43, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 43, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 43, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 43 + ) + + /* Use the following code to generate `dumpHeader` in Scala REPL: + "\n| | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 0123456789abcdef |".toCharArray + .grouped(16).map(_.map(_.toInt).mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val dumpHeader: Array[Char] = Array( + 10, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 48, 32, 32, + 49, 32, 32, 50, 32, 32, 51, 32, 32, 52, 32, 32, 53, 32, 32, 54, 32, 32, 55, + 32, 32, 56, 32, 32, 57, 32, 32, 97, 32, 32, 98, 32, 32, 99, 32, 32, 100, 32, + 32, 101, 32, 32, 102, 32, 124, 32, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 97, 98, 99, 100, 101, 102, 32, 124 + ) + + /* Use the following code to generate `pow10Mantissas` in Scala REPL: + val ms = new Array[Long](653) + var pow10 = BigInt(10) + var i = 342 + while (i >= 0) { + ms(i) = ((BigInt(1) << (pow10.bitLength + 63)) / pow10).longValue + pow10 *= 10 + i -= 1 + } + pow10 = BigInt(1) << 63 + i = 343 + while (i < 653) { + ms(i) = (pow10 >> (pow10.bitLength - 64)).longValue + pow10 *= 10 + i += 1 + } + ms.grouped(4).map(_.mkString("L, ")).mkString("Array(\n", "L,\n", "L\n)") + */ + private final val pow10Mantissas: Array[Long] = Array( + -4671960508600951122L, -1228264617323800998L, -7685194413468457480L, + -4994806998408183946L, -1631822729582842029L, -7937418233630358124L, + -5310086773610559751L, -2025922448585811785L, -8183730558007214222L, + -5617977179081629873L, -2410785455424649437L, -8424269937281487754L, + -5918651403174471789L, -2786628235540701832L, -8659171674854020501L, + -6212278575140137722L, -3153662200497784248L, -8888567902952197011L, + -6499023860262858360L, -3512093806901185046L, -9112587656954322510L, + -6779048552765515233L, -3862124672529506138L, -215969822234494768L, + -7052510166537641086L, -4203951689744663454L, -643253593753441413L, + -7319562523736982739L, -4537767136243840520L, -1060522901877412746L, + -7580355841314464822L, -4863758783215693124L, -1468012460592228501L, + -7835036815511224669L, -5182110000961642932L, -1865951482774665761L, + -8083748704375247957L, -5492999862041672042L, -2254563809124702148L, + -8326631408344020699L, -5796603242002637969L, -2634068034075909558L, + -8563821548938525330L, -6093090917745768758L, -3004677628754823043L, + -8795452545612846258L, -6382629663588669919L, -3366601061058449494L, + -9021654690802612790L, -6665382345075878084L, -3720041912917459700L, + -38366372719436721L, -6941508010590729807L, -4065198994811024355L, + -469812725086392539L, -7211161980820077193L, -4402266457597708587L, + -891147053569747830L, -7474495936122174250L, -4731433901725329908L, + -1302606358729274481L, -7731658001846878407L, -5052886483881210105L, + -1704422086424124727L, -7982792831656159810L, -5366805021142811859L, + -2096820258001126919L, -8228041688891786181L, -5673366092687344822L, + -2480021597431793123L, -8467542526035952558L, -5972742139117552794L, + -2854241655469553088L, -8701430062309552536L, -6265101559459552766L, + -3219690930897053053L, -8929835859451740015L, -6550608805887287114L, + -3576574988931720989L, -9152888395723407474L, -6829424476226871438L, + -3925094576856201394L, -294682202642863838L, -7101705404292871755L, + -4265445736938701790L, -720121152745989333L, -7367604748107325189L, + -4597819916706768583L, -1135588877456072824L, -7627272076051127371L, + -4922404076636521310L, -1541319077368263733L, -7880853450996246689L, + -5239380795317920458L, -1937539975720012668L, -8128491512466089774L, + -5548928372155224313L, -2324474446766642487L, -8370325556870233411L, + -5851220927660403859L, -2702340141148116920L, -8606491615858654931L, + -6146428501395930760L, -3071349608317525546L, -8837122532839535322L, + -6434717147622031249L, -3431710416100151157L, -9062348037703676329L, + -6716249028702207507L, -3783625267450371480L, -117845565885576446L, + -6991182506319567135L, -4127292114472071014L, -547429124662700864L, + -7259672230555269896L, -4462904269766699466L, -966944318780986428L, + -7521869226879198374L, -4790650515171610063L, -1376627125537124675L, + -7777920981101784778L, -5110715207949843068L, -1776707991509915931L, + -8027971522334779313L, -5423278384491086237L, -2167411962186469893L, + -8272161504007625539L, -5728515861582144020L, -2548958808550292121L, + -8510628282985014432L, -6026599335303880135L, -2921563150702462265L, + -8743505996830120772L, -6317696477610263061L, -3285434578585440922L, + -8970925639256982432L, -6601971030643840136L, -3640777769877412266L, + -9193015133814464522L, -6879582898840692749L, -3987792605123478032L, + -373054737976959636L, -7150688238876681629L, -4326674280168464132L, + -796656831783192261L, -7415439547505577019L, -4657613415954583370L, + -1210330751515841308L, -7673985747338482674L, -4980796165745715438L, + -1614309188754756393L, -7926472270612804602L, -5296404319838617848L, + -2008819381370884406L, -8173041140997884610L, -5604615407819967859L, + -2394083241347571919L, -8413831053483314306L, -5905602798426754978L, + -2770317479606055818L, -8648977452394866743L, -6199535797066195524L, + -3137733727905356501L, -8878612607581929669L, -6486579741050024183L, + -3496538657885142324L, -9102865688819295809L, -6766896092596731857L, + -3846934097318526917L, -196981603220770742L, -7040642529654063570L, + -4189117143640191558L, -624710411122851544L, -7307973034592864071L, + -4523280274813692185L, -1042414325089727327L, -7569037980822161435L, + -4849611457600313890L, -1450328303573004458L, -7823984217374209643L, + -5168294253290374149L, -1848681798185579782L, -8072955151507069220L, + -5479507920956448621L, -2237698882768172872L, -8316090829371189901L, + -5783427518286599473L, -2617598379430861437L, -8553528014785370254L, + -6080224000054324913L, -2988593981640518238L, -8785400266166405755L, + -6370064314280619289L, -3350894374423386208L, -9011838011655698236L, + -6653111496142234891L, -3704703351750405709L, -19193171260619233L, + -6929524759678968877L, -4050219931171323192L, -451088895536766085L, + -7199459587351560659L, -4387638465762062920L, -872862063775190746L, + -7463067817500576073L, -4717148753448332187L, -1284749923383027329L, + -7720497729755473937L, -5038936143766954517L, -1686984161281305242L, + -7971894128441897632L, -5353181642124984136L, -2079791034228842266L, + -8217398424034108273L, -5660062011615247437L, -2463391496091671392L, + -8457148712698376476L, -5959749872445582691L, -2838001322129590460L, + -8691279853972075893L, -6252413799037706963L, -3203831230369745799L, + -8919923546622172981L, -6538218414850328322L, -3561087000135522498L, + -9143208402725783417L, -6817324484979841368L, -3909969587797413806L, + -275775966319379353L, -7089889006590693952L, -4250675239810979535L, + -701658031336336515L, -7356065297226292178L, -4583395603105477319L, + -1117558485454458744L, -7616003081050118571L, -4908317832885260310L, + -1523711272679187483L, -7869848573065574033L, -5225624697904579637L, + -1920344853953336643L, -8117744561361917258L, -5535494683275008668L, + -2307682335666372931L, -8359830487432564938L, -5838102090863318269L, + -2685941595151759932L, -8596242524610931813L, -6133617137336276863L, + -3055335403242958174L, -8827113654667930715L, -6422206049907525490L, + -3416071543957018958L, -9052573742614218705L, -6704031159840385477L, + -3768352931373093942L, -98755145788979524L, -6979250993759194058L, + -4112377723771604669L, -528786136287117932L, -7248020362820530564L, + -4448339435098275301L, -948738275445456222L, -7510490449794491995L, + -4776427043815727089L, -1358847786342270957L, -7766808894105001205L, + -5096825099203863602L, -1759345355577441598L, -8017119874876982855L, + -5409713825168840664L, -2150456263033662926L, -8261564192037121185L, + -5715269221619013577L, -2532400508596379068L, -8500279345513818773L, + -6013663163464885563L, -2905392935903719049L, -8733399612580906262L, + -6305063497298744923L, -3269643353196043250L, -8961056123388608887L, + -6589634135808373205L, -3625356651333078602L, -9183376934724255983L, + -6867535149977932074L, -3972732919045027189L, -354230130378896082L, + -7138922859127891907L, -4311967555482476980L, -778273425925708321L, + -7403949918844649557L, -4643251380128424042L, -1192378206733142148L, + -7662765406849295699L, -4966770740134231719L, -1596777406740401745L, + -7915514906853832947L, -5282707615139903279L, -1991698500497491195L, + -8162340590452013853L, -5591239719637629412L, -2377363631119648861L, + -8403381297090862394L, -5892540602936190089L, -2753989735242849707L, + -8638772612167862923L, -6186779746782440750L, -3121788665050663033L, + -8868646943297746252L, -6474122660694794911L, -3480967307441105734L, + -9093133594791772940L, -6754730975062328271L, -3831727700400522434L, + -177973607073265139L, -7028762532061872568L, -4174267146649952806L, + -606147914885053103L, -7296371474444240046L, -4508778324627912153L, + -1024286887357502287L, -7557708332239520786L, -4835449396872013078L, + -1432625727662628443L, -7812920107430224633L, -5154464115860392887L, + -1831394126398103205L, -8062150356639896359L, -5466001927372482545L, + -2220816390788215277L, -8305539271883716405L, -5770238071427257602L, + -2601111570856684098L, -8543223759426509417L, -6067343680855748868L, + -2972493582642298180L, -8775337516792518219L, -6357485877563259869L, + -3335171328526686933L, -9002011107970261189L, -6640827866535438582L, + -3689348814741910324L, -9223372036854775808L, -6917529027641081856L, + -4035225266123964416L, -432345564227567616L, -7187745005283311616L, + -4372995238176751616L, -854558029293551616L, -7451627795949551616L, + -4702848726509551616L, -1266874889709551616L, -7709325833709551616L, + -5024971273709551616L, -1669528073709551616L, -7960984073709551616L, + -5339544073709551616L, -2062744073709551616L, -8206744073709551616L, + -5646744073709551616L, -2446744073709551616L, -8446744073709551616L, + -5946744073709551616L, -2821744073709551616L, -8681119073709551616L, + -6239712823709551616L, -3187955011209551616L, -8910000909647051616L, + -6525815118631426616L, -3545582879861895366L, -9133518327554766460L, + -6805211891016070171L, -3894828845342699810L, -256850038250986858L, + -7078060301547948643L, -4235889358507547899L, -683175679707046970L, + -7344513827457986212L, -4568956265895094861L, -1099509313941480672L, + -7604722348854507276L, -4894216917640746191L, -1506085128623544835L, + -7858832233030797378L, -5211854272861108819L, -1903131822648998119L, + -8106986416796705681L, -5522047002568494197L, -2290872734783229842L, + -8349324486880600507L, -5824969590173362730L, -2669525969289315508L, + -8585982758446904049L, -6120792429631242157L, -3039304518611664792L, + -8817094351773372351L, -6409681921289327535L, -3400416383184271515L, + -9042789267131251553L, -6691800565486676537L, -3753064688430957767L, + -79644842111309304L, -6967307053960650171L, -4097447799023424810L, + -510123730351893109L, -7236356359111015049L, -4433759430461380907L, + -930513269649338230L, -7499099821171918250L, -4762188758037509908L, + -1341049929119499481L, -7755685233340769032L, -5082920523248573386L, + -1741964635633328828L, -8006256924911912374L, -5396135137712502563L, + -2133482903713240300L, -8250955842461857044L, -5702008784649933400L, + -2515824962385028846L, -8489919629131724885L, -6000713517987268202L, + -2889205879056697349L, -8723282702051517699L, -6292417359137009220L, + -3253835680493873621L, -8951176327949752869L, -6577284391509803182L, + -3609919470959866074L, -9173728696990998152L, -6855474852811359786L, + -3957657547586811828L, -335385916056126881L, -7127145225176161157L, + -4297245513042813542L, -759870872876129024L, -7392448323188662496L, + -4628874385558440216L, -1174406963520662366L, -7651533379841495835L, + -4952730706374481889L, -1579227364540714458L, -7904546130479028392L, + -5268996644671397586L, -1974559787411859078L, -8151628894773493780L, + -5577850100039479321L, -2360626606621961247L, -8392920656779807636L, + -5879464802547371641L, -2737644984756826647L, -8628557143114098510L, + -6174010410465235234L, -3105826994654156138L, -8858670899299929442L, + -6461652605697523899L, -3465379738694516970L, -9083391364325154962L, + -6742553186979055799L, -3816505465296431844L, -158945813193151901L, + -7016870160886801794L, -4159401682681114339L, -587566084924005019L, + -7284757830718584993L, -4494261269970843337L, -1006140569036166268L, + -7546366883288685774L, -4821272585683469313L, -1414904713676948737L, + -7801844473689174817L, -5140619573684080617L, -1814088448677712867L, + -8051334308064652398L, -5452481866653427593L, -2203916314889396588L, + -8294976724446954723L, -5757034887131305500L, -2584607590486743971L, + -8532908771695296838L, -6054449946191733143L, -2956376414312278525L, + -8765264286586255934L, -6344894339805432014L, -3319431906329402113L, + -8992173969096958177L, -6628531442943809817L, -3673978285252374367L, + -9213765455923815836L, -6905520801477381891L, -4020214983419339459L, + -413582710846786420L, -7176018221920323369L, -4358336758973016307L, + -836234930288882479L, -7440175859071633406L, -4688533805412153853L, + -1248981238337804412L, -7698142301602209614L, -5010991858575374113L, + -1652053804791829737L, -7950062655635975442L, -5325892301117581398L, + -2045679357969588844L, -8196078626372074883L, -5633412264537705700L, + -2430079312244744221L, -8436328597794046994L, -5933724728815170839L, + -2805469892591575644L, -8670947710510816634L, -6226998619711132888L, + -3172062256211528206L, -8900067937773286985L, -6513398903789220827L, + -3530062611309138130L, -9123818159709293187L, -6793086681209228580L, + -3879672333084147821L, -237904397927796872L, -7066219276345954901L, + -4221088077005055722L, -664674077828931749L, -7332950326284164199L, + -4554501889427817345L, -1081441343357383777L, -7593429867239446717L, + -4880101315621920492L, -1488440626100012711L, -7847804418953589800L, + -5198069505264599346L, -1885900863153361279L, -8096217067111932656L, + -5508585315462527915L, -2274045625900771990L, -8338807543829064350L, + -5811823411358942533L, -2653093245771290262L, -8575712306248138270L, + -6107954364382784934L, -3023256937051093263L, -8807064613298015146L, + -6397144748195131028L, -3384744916816525881L, -9032994600651410532L, + -6679557232386875260L, -3737760522056206171L, -60514634142869810L, + -6955350673980375487L, -4082502324048081455L, -491441886632713915L, + -7224680206786528053L, -4419164240055772162L, -912269281642327298L, + -7487697328667536418L, -4747935642407032618L, -1323233534581402868L, + -7744549986754458649L, -5069001465015685407L, -1724565812842218855L, + -7995382660667468640L, -5382542307406947896L, -2116491865831296966L, + -8240336443785642460L, -5688734536304665171L, -2499232151953443560L, + -8479549122611984081L, -5987750384837592197L, -2873001962619602342L, + -8713155254278333320L, -6279758049420528746L, -3238011543348273028L, + -8941286242233752499L, -6564921784364802720L, -3594466212028615495L, + -9164070410158966541L, -6843401994271320272L, -3942566474411762436L, + -316522074587315140L, -7115355324258153819L, -4282508136895304370L, + -741449152691742558L, -7380934748073420955L, -4614482416664388289L, + -1156417002403097458L, -7640289654143017767L, -4938676049251384305L, + -1561659043136842477L, -7893565929601608404L, -5255271393574622601L, + -1957403223540890347L, -8140906042354138323L, -5564446534515285000L, + -2343872149716718346L, -8382449121214030822L, -5866375383090150624L, + -2721283210435300376L, -8618331034163144591L, -6161227774276542835L, + -3089848699418290639L, -8848684464777513506L, -6449169562544503978L, + -3449775934753242068L, -9073638986861858149L, -6730362715149934782L, + -3801267375510030573L, -139898200960150313L, -7004965403241175802L, + -4144520735624081848L, -568964901102714406L, -7273132090830278360L, + -4479729095110460046L, -987975350460687153L, -7535013621679011327L, + -4807081008671376254L, -1397165242411832414L, -7790757304148477115L, + -5126760611758208489L, -1796764746270372707L, -8040506994060064798L, + -5438947724147693094L, -2186998636757228463L, -8284403175614349646L, + -5743817951090549153L, -2568086420435798537L, -8522583040413455942L, + -6041542782089432023L, -2940242459184402125L, -8755180564631333184L, + -6332289687361778576L, -3303676090774835316L, -8982326584375353929L, + -6616222212041804507L, -3658591746624867729L, -9204148869281624187L, + -6893500068174642330L, -4005189066790915008L, -394800315061255856L, + -7164279224554366766L, -4343663012265570553L, -817892746904575288L, + -7428711994456441411L, -4674203974643163860L, -1231068949876566920L, + -7686947121313936181L, -4996997883215032323L, -1634561335591402499L, + -7939129862385708418L, -5312226309554747619L, -2028596868516046619L, + -8185402070463610993L, -5620066569652125837L + ) + + /** The default math context used for rounding of `BigDecimal` values when + * parsing. + */ + final val bigDecimalMathContext: MathContext = MathContext.DECIMAL128 + + /** The default limit for number of decimal digits in mantissa of parsed + * `BigDecimal` values. + */ + final val bigDecimalDigitsLimit: Int = 308 + + /** The default limit for scale of parsed `BigDecimal` values. + */ + final val bigDecimalScaleLimit: Int = 6178 + + /** The maximum number of digits in `BigInt` values. + */ + final val bigIntDigitsLimit: Int = 308 + + /* Use the following code to generate `nibbles` in Scala REPL: + val ns = new Array[Byte](256) + java.util.Arrays.fill(ns, -1: Byte) + ns('0') = 0 + ns('1') = 1 + ns('2') = 2 + ns('3') = 3 + ns('4') = 4 + ns('5') = 5 + ns('6') = 6 + ns('7') = 7 + ns('8') = 8 + ns('9') = 9 + ns('A') = 10 + ns('B') = 11 + ns('C') = 12 + ns('D') = 13 + ns('E') = 14 + ns('F') = 15 + ns('a') = 10 + ns('b') = 11 + ns('c') = 12 + ns('d') = 13 + ns('e') = 14 + ns('f') = 15 + ns.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val nibbles: Array[Byte] = Array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, + -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1 + ) diff --git a/modules/core/src/main/scala/tethys/readers/tokens/JsonReader.scala b/modules/core/src/main/scala/tethys/readers/tokens/JsonReader.scala new file mode 100644 index 00000000..21645e96 --- /dev/null +++ b/modules/core/src/main/scala/tethys/readers/tokens/JsonReader.scala @@ -0,0 +1,3247 @@ +package tethys.readers.tokens +/* +import tethys.commons.Token + +import java.io.InputStream +import java.math.MathContext +import java.nio.ByteBuffer +import java.time.* +import java.util.concurrent.ConcurrentHashMap +import java.nio.charset.StandardCharsets.UTF_8 +import scala.annotation.{switch, tailrec} + +/** The reader to parse JSON input iteratively. + * + * @param buf + * the internal buffer with JSON input + * @param head + * the head position in the internal buffer + * @param tail + * the tail position in the internal buffer + * @param mark + * the current mark position + * @param charBuf + * the internal char buffer for parsed strings + * @param bbuf + * the byte buffer with JSON input + * @param in + * the input stream with JSON input + * @param totalRead + * the total number of read bytes + * @param config + * the JSON reader configuration + */ +final class JsonReader( + private[this] var buf: Array[Byte] = new Array[Byte](32768), + private[this] var head: Int = 0, + private[this] var tail: Int = 0, + private[this] var mark: Int = -1, + private[this] var charBuf: Array[Char] = new Array[Char](4096), + private[this] var bbuf: ByteBuffer = null, + private[this] var in: InputStream = null, + private[this] var totalRead: Long = 0, + private[this] var config: ReaderConfig = null +) { + import JsonReader._ + private[this] var magnitude: Array[Byte] = _ + private[this] val objectTrace = new scala.collection.mutable.Stack[Boolean](6) + + /** Throws a [[JsonReaderException]] indicating that a required field with the + * given name is missing. + * + * @param reqField + * the name of the missing required field + * @throws JsonReaderException + * always + */ + def requiredFieldError(reqField: String): Nothing = { + var i = appendString("missing required field \"", 0) + i = appendString(reqField, i) + i = appendChar('"', i) + decodeError(i, head - 1, null) + } + + /** Throws a [[JsonReaderException]] indicating that a field with the given + * name is duplicated. + * + * @param len + * the length of the duplicated field name in the internal char buffer + * @throws JsonReaderException + * always + */ + def duplicatedKeyError(len: Int): Nothing = { + var i = prependString("duplicated field \"", len) + i = appendChar('"', i) + decodeError(i, head - 1, null) + } + + /** Throws a [[JsonReaderException]] indicating that an unexpected field with + * the given name was encountered. + * + * @param len + * the length of the unexpected field name in the internal char buffer + * @throws JsonReaderException + * always + */ + def unexpectedKeyError(len: Int): Nothing = { + var i = prependString("unexpected field \"", len) + i = appendChar('"', i) + decodeError(i, head - 1, null) + } + + /** Throws a [[JsonReaderException]] indicating that an illegal discriminator + * field name was encountered. + */ + def discriminatorError(): Nothing = decodeError("illegal discriminator") + + /** Throws a [[JsonReaderException]] indicating that an illegal value was + * encountered for the given discriminator field. + * + * @param discriminatorFieldName + * the name of the discriminator field + * @throws JsonReaderException + * always + */ + def discriminatorValueError(discriminatorFieldName: String): Nothing = { + var i = appendString("illegal value of discriminator field \"", 0) + i = appendString(discriminatorFieldName, i) + i = appendChar('"', i) + decodeError(i, head - 1, null) + } + + /** Throws a [[JsonReaderException]] indicating that an illegal enum value was + * encountered. + * + * @param value + * an illegal enum value + * @throws JsonReaderException + * always + */ + def enumValueError(value: String): Nothing = { + var i = appendString("illegal enum value \"", 0) + i = appendString(value, i) + i = appendChar('"', i) + decodeError(i, head - 1, null) + } + + /** Throws a [[JsonReaderException]] indicating that an illegal enum value + * with the given length was encountered. + * + * @param len + * the length of the illegal enum value in the internal char buffer. + * @throws JsonReaderException + * always + */ + def enumValueError(len: Int): Nothing = { + var i = prependString("illegal enum value \"", len) + i = appendChar('"', i) + decodeError(i, head - 1, null) + } + + /** Sets the current read head position as a mark. + */ + def setMark(): Unit = mark = head + + /** Skips tokens with in the current JSON object until a key with the given + * name is encountered. + * + * @param key + * the name of the JSON key to skip to + * @return + * `true` if the key was found, `false` otherwise + * @throws JsonReaderException + * in cases of reaching the end of input or invalid encoding of JSON key + */ + def skipToKey(key: String): Boolean = { + while (!isCharBufEqualsTo(readKeyAsCharBuf(), key)) { + skip(current = Token.Empty) + if (!isNextToken(',', head)) return false + } + true + } + + /** Rolls back the read head position to the previously set mark. + * + * @throws java.lang.IllegalStateException + * in cases of reaching the end of input or invalid encoding of JSON key + */ + def rollbackToMark(): Unit = { + if (mark < 0) missingSetMarkOperation() + head = mark + mark = -1 + } + + /** Reads a JSON key into the internal char buffer and returns the length of + * the key. + * + * @return + * the length of the key in the internal char buffer + * @throws JsonReaderException + * in cases of reaching the end of input or invalid encoding of JSON key + */ + def readKeyAsCharBuf(): Int = { + nextTokenOrError('"', head) + val pos = head + val len = parseString(0, Math.min(tail - pos, charBuf.length), charBuf, pos) + nextTokenOrError(':', head) + len + } + + /** Reads a JSON key into the internal char buffer and returns a `String` + * instance. + * + * @return + * a `String` instance of the parsed JSON key + * @throws JsonReaderException + * in cases of reaching the end of input or invalid encoding of JSON key + */ + def readKeyAsString(): String = { + nextTokenOrError('"', head) + val pos = head + val len = parseString(0, Math.min(tail - pos, charBuf.length), charBuf, pos) + nextTokenOrError(':', head) + new String(charBuf, 0, len) + } + + /** Reads a JSON number value into a `Byte` value. + * + * @return + * a `Byte` value of the parsed JSON value + * @throws JsonReaderException + * in cases of reaching the end of input or dection of leading zero or + * illegal format of JSON value or exceeding capacity of `Byte` + */ + def readByte(): Byte = readByte(isToken = true) + + /** Reads a JSON string value into a `Char` value. + * + * @return + * a `Char` value of the parsed JSON value + * @throws JsonReaderException + * in cases of reaching the end of input or invalid encoding of JSON value + * or exceeding capacity of `Char` or when parsed char is a part of a + * surrogate pair + */ + def readChar(): Char = { + nextTokenOrError('"', head) + val x = parseChar(head) + nextByteOrError('"', head) + x + } + + /** Reads a JSON number value into a `Short` value. + * + * @return + * a `Short` value of the parsed JSON value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value or exceeding capacity of `Short` + */ + def readShort(): Short = readShort(isToken = true) + + /** Reads a JSON value into a `Int` value. + * + * @return + * a `Int` value of the parsed JSON value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value or exceeding capacity of `Int` + */ + def readInt(): Int = readInt(isToken = true) + + /** Reads a JSON number value into a `Long` value. + * + * @return + * a `Long` value of the parsed JSON value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value or exceeding capacity of `Long` + */ + def readLong(): Long = readLong(isToken = true) + + /** Reads a JSON number value into a `Double` value. + * + * @return + * a `Double` value of the parsed JSON value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value + */ + def readDouble(): Double = readDouble(isToken = true) + + /** Reads a JSON number value into a `Float` value. + * + * @return + * a `Float` value of the parsed JSON value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value + */ + def readFloat(): Float = readFloat(isToken = true) + + /** Reads a JSON number value into a `BigInt` instance with the default limit + * of allowed digits. In case of `null` JSON value returns the provided + * default value or throws a [[JsonReaderException]] if the provided default + * value is `null`. + * + * @param default + * the default `BigInt` value to return if the JSON value is `null` + * @return + * a `BigInt` instance of the parsed JSON value or the provided default + * value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value or exceeding of the default limit or when + * both the JSON value and the provided default value are `null` + */ + def readBigInt(default: BigInt): BigInt = + readBigInt(isToken = true, default, bigIntDigitsLimit) + + /** Reads a JSON number value into a `BigInt` instance with the provided limit + * of allowed digits. In case of `null` JSON value returns the provided + * default value or throws a [[JsonReaderException]] if the provided default + * value is `null`. + * + * @param default + * the default `BigInt` value to return if the JSON value is `null` + * @param digitsLimit + * the maximum number of decimal digits allowed in the parsed `BigInt` + * value + * @return + * a `BigInt` instance of the parsed JSON value or the provided default + * value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value or exceeding of the default limit or when + * both the JSON value and the provided default value are `null` + */ + def readBigInt(default: BigInt, digitsLimit: Int): BigInt = + readBigInt(isToken = true, default, digitsLimit) + + /** Reads a JSON number value into a `BigDecimal` instance with the default + * limit of allowed digits for mantissa, the default limit for scale, and the + * default instance of [[java.math.MathContext]] for precision. In case of + * `null` JSON value returns the provided default value or throws a + * [[JsonReaderException]] if the provided default value is `null`. + * + * @param default + * the default `BigDecimal` value to return if the JSON value is `null` + * @return + * a `BigDecimal` instance of the parsed JSON value or the provided default + * value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value or exceeding of default limits or when both + * the JSON value and the provided default value are `null` + */ + def readBigDecimal(default: BigDecimal): BigDecimal = + readBigDecimal( + isToken = true, + default, + bigDecimalMathContext, + bigDecimalScaleLimit, + bigDecimalDigitsLimit + ) + + /** Reads a JSON number value into a `BigDecimal` instance with the provided + * limit of allowed digits for mantissa, the provided limit for scale, and + * the provided instance of [[java.math.MathContext]] for precision. In case + * of `null` JSON value returns the provided default value or throws a + * [[JsonReaderException]] if the provided default value is `null`. + * + * @param default + * the default `BigDecimal` value to return if the JSON value is `null` + * @param mc + * the precision to use + * @param scaleLimit + * the maximum number of decimal places (scale) allowed + * @param digitsLimit + * the maximum number of decimal digits allowed + * @return + * a `BigDecimal` instance of the parsed JSON value or the provided default + * value + * @throws JsonReaderException + * in cases of reaching the end of input or detection of leading zero or + * illegal format of JSON value or exceeding of provided limits or when + * both the JSON value and the provided default value are `null` + */ + def readBigDecimal( + default: BigDecimal, + mc: MathContext, + scaleLimit: Int, + digitsLimit: Int + ): BigDecimal = + readBigDecimal(isToken = true, default, mc, scaleLimit, digitsLimit) + + /** Reads a JSON string value into a `String` instance. In case of `null` JSON + * value returns the provided default value or throws a + * [[JsonReaderException]] if the provided default value is `null`. + * + * @param default + * the default `String` value to return if the JSON value is `null`. + * @return + * a `String` instance of the parsed JSON value or the default value if the + * JSON value is `null`. + * @throws JsonReaderException + * in cases of reaching the end of input or invalid encoding of JSON value + * or when both the JSON value and the provided default value are `null` + */ + def readString(default: String): String = { + val pos = head + 1 + val len = + parseString(0, Math.min(tail - pos, charBuf.length), charBuf, pos) + new String(charBuf, 0, len) + } + + /** Reads a JSON boolean value into a `Boolean` value. + * + * @return + * a `Boolean` value of the parsed JSON value. + * @throws JsonReaderException + * in cases of reaching the end of input or unexpected JSON value + */ + def readBoolean(): Boolean = parseBoolean(isToken = true, head) + + /** Reads a raw JSON value into a `Array[Byte]` instance without parsing. + * + * @return + * a `Array[Byte]` instance containing the raw bytes of the JSON value. + * @throws JsonReaderException + * in cases of reaching the end of input or invalid type of JSON value + */ + def readRawValAsBytes(): Array[Byte] = { + var from = head + val oldMark = mark + val newMark = + if (oldMark < 0) from + else oldMark + mark = newMark + skip(Token.Empty) + if (mark == 0) from -= newMark + if (mark > oldMark) mark = oldMark + val len = head - from + val x = new Array[Byte](len) + System.arraycopy(buf, from, x, 0, len) + x + } + + /** Finishes reading the `null` JSON value and returns the provided default + * value or throws [[JsonReaderException]]. Before calling it the `n` token + * should be parsed already. + * + * @param default + * the default value to return + * @param msg + * the exception message + * @tparam A + * the type of the default value + * @return + * the default value + * @throws JsonReaderException + * in cases of reaching the end of input or illegal format of JSON value or + * when the provided default value is `null` + */ + @tailrec + def readNullOrError[@specialized A](default: A, msg: String): A = + if (default != null) { + val pos = head + if (pos != 0) { + if (pos + 2 < tail) { + val bs = ByteArrayAccess.getInt(buf, pos - 1) + if (bs == 0x6c6c756e) { + head = pos + 3 + default + } else decodeError(msg, bs, pos) + } else if (buf(pos - 1) == 'n') { + head = loadMoreOrError(pos - 1) + 1 + readNullOrError(default, msg) + } else decodeError(msg, pos - 1) + } else illegalTokenOperation() + } else decodeError(msg) + + /** Finishes reading the `null` JSON value and returns the provided default + * value or throws [[JsonReaderException]] with a message of expecting `null` + * or the provided token. Before calling it the `n` token should be parsed + * already. + * + * @param default + * the default value to return + * @param t + * the token for an error message + * @tparam A + * the type of the default value + * @return + * the default value + * @throws JsonReaderException + * in cases of reaching the end of input or illegal format of JSON value or + * when the provided default value is `null` + */ + @tailrec + def readNullOrTokenError[@specialized A](default: A, t: Byte): A = + if (default != null) { + val pos = head + if (pos != 0) { + if (pos + 2 < tail) { + val bs = ByteArrayAccess.getInt(buf, pos - 1) + if (bs == 0x6c6c756e) { + head = pos + 3 + default + } else tokenOrNullError(t, bs, pos) + } else if (buf(pos - 1) == 'n') { + head = loadMoreOrError(pos - 1) + 1 + readNullOrTokenError(default, t) + } else tokenOrNullError(t) + } else illegalTokenOperation() + } else tokenError(t) + + /** Reads and returns the next byte from the input. + * + * @return + * the next byte from the input + * @throws JsonReaderException + * in cases of reaching the end of input + */ + def nextByte(): Byte = nextByte(head) + + /** Skips whitespaces, then reads and returns the next byte from the input. + * + * @return + * the next token from the input + * @throws JsonReaderException + * in cases of reaching the end of input + */ + def nextToken(): Byte = nextToken(head) + + /** Skips whitespaces, then checks if the next token in the input matches the + * given one. + * + * @param t + * the token to match + * @return + * `true` if the next token matches `t`, `false` otherwise + * @throws JsonReaderException + * in cases of reaching the end of input + */ + def isNextToken(t: Byte): Boolean = isNextToken(t, head) + + /** Checks if the current token in the input matches the given one. + * + * @param t + * the token to match + * @return + * `true` if the current token matches `t`, `false` otherwise. + * @throws java.lang.IllegalStateException + * if no any token was parsed yet + */ + def isCurrentToken(t: Byte): Boolean = isCurrentToken(t, head) + + /** Checks if there are more bytes available for reading in the input. + * + * @return + * `true` if there are more bytes available, `false` otherwise + */ + def hasRemaining(): Boolean = head < tail || loadMore(head) < tail + + /** Rolls back the current reading position by one. + * + * @throws java.lang.IllegalStateException + * if no any token was parsed yet + */ + def rollbackToken(): Unit = { + val pos = head + if (pos == 0) illegalTokenOperation() + head = pos - 1 + } + + /** Calculates a hash code for the internal char buffer of the given length. + * + * @param len + * the length of the char buffer to hash + * @return + * the hash code for the char buffer + */ + def charBufToHashCode(len: Int): Int = { + val cs = charBuf + var h, i = 0 + while (i < len) { + h = (h << 5) + (cs(i) - h) + i += 1 + } + h + } + + /** Checks if the internal char buffer contains the given string. + * + * @param len + * the length of the char buffer to check + * @param s + * the string to match + * @return + * `true` if the char buffer contains `s`, `false` otherwise + */ + def isCharBufEqualsTo(len: Int, s: String): Boolean = { + if (s.length != len) return false + val charBuf = this.charBuf + var i = 0 + while (i < len) { + if (s.charAt(i) != charBuf(i)) return false + i += 1 + } + true + } + + /** Skips the next JSON value. + * + * @throws JsonReaderException + * in cases of reaching the end of input + */ + def skip(current: Token): Unit = { + val b: Byte = + if (current.isObjectStart) + objectTrace.pop() + '{' + else if (current.isArrayStart) + objectTrace.pop() + '[' + else nextToken(head) + + var pos = head + if (b == '"') pos = skipString(evenBackSlashes = true, pos) + else if ((b >= '0' && b <= '9') || b == '-') pos = skipNumber(pos) + else if (b == 'n' || b == 't') pos = skipFixedBytes(3, pos) + else if (b == 'f') pos = skipFixedBytes(4, pos) + else if (b == '[') pos = skipArray(0, pos) + else if (b == '{') pos = skipObject(0, pos) + else decodeError("expected value") + head = pos + } + + def init(s: String): this.type = { + objectTrace.clear() + this.buf = s.getBytes(UTF_8) + this.config = config + head = 0 + val to = buf.length + tail = to + totalRead = 0 + mark = -1 + this + } + + def nextTethysToken(previous: Token): Token = { + if (!skipWhitespaces()) return Token.Empty + val b = nextToken(head) + val token = + if ( + b == '"' && + objectTrace.nonEmpty + && objectTrace.head && + previous != null && + !previous.isFieldName + ) + rollbackToken() + Token.FieldNameToken + else if (b == '"') + rollbackToken() + Token.StringValueToken + else if ((b >= '0' && b <= '9') || b == '-') + rollbackToken() + Token.NumberValueToken + else if (b == 'n') readNullOrError(Token.NullValueToken, "expected null") + else if (b == 'f' || b == 't') + rollbackToken() + Token.BooleanValueToken + else if (b == '[') + objectTrace.push(false) + Token.ArrayStartToken + else if (b == ']') + objectTrace.pop() + Token.ArrayEndToken + else if (b == '{') + objectTrace.push(true) + Token.ObjectStartToken + else if (b == '}') + objectTrace.pop() + Token.ObjectEndToken + else decodeError("expected value") + token + } + + /** Throws a [[JsonReaderException]] with the message `expected ','`. + * + * @throws JsonReaderException + * always + */ + def commaError(): Nothing = decodeError("expected ','") + + /** Throws a [[JsonReaderException]] with the message `expected '[' or null`. + * + * @throws JsonReaderException + * always + */ + def arrayStartOrNullError(): Nothing = decodeError("expected '[' or null") + + /** Throws a [[JsonReaderException]] with the message `expected ']'`. + * + * @throws JsonReaderException + * always + */ + def arrayEndError(): Nothing = decodeError("expected ']'") + + /** Throws a [[JsonReaderException]] with the message `expected ']' or ','`. + * + * @throws JsonReaderException + * always + */ + def arrayEndOrCommaError(): Nothing = decodeError("expected ']' or ','") + + /** Throws a [[JsonReaderException]] with the message `expected '{' or null`. + * + * @throws JsonReaderException + * always + */ + def objectStartOrNullError(): Nothing = decodeError("expected '{' or null") + + /** Throws a [[JsonReaderException]] with the message `expected '}' or ','`. + * + * @throws JsonReaderException + * always + */ + def objectEndOrCommaError(): Nothing = decodeError("expected '}' or ','") + + /** Throws a [[JsonReaderException]] with the given message. + * + * @param msg + * the exception message + * @throws JsonReaderException + * always + */ + def decodeError(msg: String): Nothing = decodeError(msg, head - 1) + + /* /** Reads a JSON value from the given byte array slice into an instance of + * type `A` using the given [[JsonValueCodec]]. + * + * @param codec + * the JSON value codec + * @param buf + * the byte array with JSON input + * @param from + * the start index of the slice (inclusive) + * @param to + * the end index of the slice (exclusive) + * @param config + * the reader configuration + * @tparam A + * the type of the value to read + * @return + * an instance of type `A` containing the decoded JSON value + * @throws JsonReaderException + * in cases of reaching the end of input during parsing of JSON value or + * unexpected format of JSON value or when configured checking of reaching + * the end of input doesn't pass after reading of the whole JSON value + */ + private[tethys] def read[@sp A]( + codec: JsonValueCodec[A], + buf: Array[Byte], + from: Int, + to: Int, + config: ReaderConfig + ): A = { + val currBuf = this.buf + try { + this.buf = buf + this.config = config + head = from + tail = to + totalRead = 0 + mark = -1 + val x = codec.decodeValue(this, codec.nullValue) + if (head != to && config.checkForEndOfInput) endOfInputOrError() + x + } finally { + this.buf = currBuf + if (charBuf.length > config.preferredCharBufSize) + reallocateCharBufToPreferredSize() + } + } + + /** Reads a JSON value from the given input stream into an instance of type + * `A` using the given [[JsonValueCodec]]. + * + * @param codec + * the JSON value codec + * @param in + * the input stream with the JSON input + * @param config + * the reader configuration + * @tparam A + * the type of the value to read + * @return + * an instance of type `A` containing the decoded JSON value + * @throws JsonReaderException + * in cases of reaching the end of input during parsing of JSON value or + * unexpected format of JSON value or when configured checking of reaching + * the end of input doesn't pass after reading of the whole JSON value + */ + private[tethys] def read[@sp A]( + codec: JsonValueCodec[A], + in: InputStream, + config: ReaderConfig + ): A = + try { + this.config = config + this.in = in + head = 0 + tail = 0 + totalRead = 0 + mark = -1 + if (buf.length < config.preferredBufSize) reallocateBufToPreferredSize() + val x = codec.decodeValue(this, codec.nullValue) + if (config.checkForEndOfInput) endOfInputOrError() + x + } finally { + this.in = null + if (buf.length > config.preferredBufSize) reallocateBufToPreferredSize() + if (charBuf.length > config.preferredCharBufSize) + reallocateCharBufToPreferredSize() + } + + /** Reads a JSON value from the given byte buffer into an instance of type `A` + * using the given [[JsonValueCodec]]. + * + * @param codec + * the JSON value codec + * @param bbuf + * the byte buffer with the JSON input + * @param config + * the reader configuration + * @tparam A + * the type of the value to read + * @return + * an instance of type `A` containing the decoded JSON value + * @throws JsonReaderException + * in cases of reaching the end of input during parsing of JSON value or + * unexpected format of JSON value or when configured checking of reaching + * the end of input doesn't pass after reading of the whole JSON value + */ + private[tethys] def read[@sp A]( + codec: JsonValueCodec[A], + bbuf: ByteBuffer, + config: ReaderConfig + ): A = + if (bbuf.hasArray) { + val offset = bbuf.arrayOffset + val to = offset + bbuf.limit() + val currBuf = this.buf + try { + this.buf = bbuf.array + this.config = config + head = offset + bbuf.position() + tail = to + totalRead = 0 + mark = -1 + val x = codec.decodeValue(this, codec.nullValue) + if (head != to && config.checkForEndOfInput) endOfInputOrError() + x + } finally { + this.buf = currBuf + if (charBuf.length > config.preferredCharBufSize) + reallocateCharBufToPreferredSize() + bbuf.position(head - offset) + } + } else { + val position = bbuf.position() + try { + this.config = config + this.bbuf = bbuf + head = 0 + tail = 0 + totalRead = 0 + mark = -1 + if (buf.length < config.preferredBufSize) reallocateBufToPreferredSize() + val x = codec.decodeValue(this, codec.nullValue) + if (config.checkForEndOfInput) endOfInputOrError() + x + } finally { + this.bbuf = null + if (buf.length > config.preferredBufSize) reallocateBufToPreferredSize() + if (charBuf.length > config.preferredCharBufSize) + reallocateCharBufToPreferredSize() + bbuf.position(totalRead.toInt - tail + head + position) + } + } + + /** Reads a JSON value from the given string into an instance of type `A` + * using the given [[JsonValueCodec]]. + * + * @param codec + * the JSON value codec + * @param s + * the string with the JSON input + * @param config + * The reader configuration. + * @tparam A + * the type of the value to read + * @return + * an instance of type `A` containing the decoded JSON value. + * @throws JsonReaderException + * in cases of reaching the end of input during parsing of JSON value or + * unexpected format of JSON value or when configured checking of reaching + * the end of input doesn't pass after reading of the whole JSON value + */ + private[tethys] def read[@sp A]( + codec: JsonValueCodec[A], + s: String, + config: ReaderConfig + ): A = { + val currBuf = this.buf + try { + this.buf = s.getBytes(UTF_8) + this.config = config + head = 0 + val to = buf.length + tail = to + totalRead = 0 + mark = -1 + val x = codec.decodeValue(this, codec.nullValue) + if (head != to && config.checkForEndOfInput) endOfInputOrError() + x + } finally { + this.buf = currBuf + if (charBuf.length > config.preferredCharBufSize) + reallocateCharBufToPreferredSize() + } + } + + /** Scans JSON values separated by whitespaces from the given input stream and + * applies the given function to each decoded value until it returns `false`. + * + * @param codec + * the JSON value codec + * @param in + * the input stream with the JSON input + * @param config + * the reader configuration + * @param f + * the callback function to apply to each decoded JSON value + * @throws JsonReaderException + * in cases of reaching the end of input during parsing of JSON value or + * unexpected format of JSON value + */ + private[tethys] def scanValueStream[@sp A]( + codec: JsonValueCodec[A], + in: InputStream, + config: ReaderConfig + )(f: A => Boolean): Unit = + try { + this.config = config + this.in = in + head = 0 + tail = 0 + totalRead = 0 + mark = -1 + if (buf.length < config.preferredBufSize) reallocateBufToPreferredSize() + while (f(codec.decodeValue(this, codec.nullValue)) && skipWhitespaces()) + () + } finally { + this.in = null + if (buf.length > config.preferredBufSize) reallocateBufToPreferredSize() + if (charBuf.length > config.preferredCharBufSize) + reallocateCharBufToPreferredSize() + } + + /** Scans JSON array from the given input stream and applies the given + * function to each decoded value until it returns `false`. + * + * @param codec + * the JSON value codec + * @param in + * the input stream with the JSON input + * @param config + * the reader configuration + * @param f + * the function to apply to each decoded JSON value + * @throws JsonReaderException + * in cases of reaching the end of input during parsing of JSON value or + * unexpected format of JSON value or when configured checking of reaching + * the end of input doesn't pass after reading of the whole JSON array + */ + private[tethys] def scanArray[@sp A]( + codec: JsonValueCodec[A], + in: InputStream, + config: ReaderConfig + )(f: A => Boolean): Unit = + try { + this.config = config + this.in = in + head = 0 + tail = 0 + totalRead = 0 + mark = -1 + if (buf.length < config.preferredBufSize) reallocateBufToPreferredSize() + var continue = true + if (isNextToken('[', head)) { + if (!isNextToken(']', head)) { + head -= 1 + while ({ + continue = f(codec.decodeValue(this, codec.nullValue)) + continue && isNextToken(',', head) + }) () + if (continue && !isCurrentToken(']', head)) arrayEndOrCommaError() + } + } else readNullOrTokenError((), '[') + if (continue && config.checkForEndOfInput) endOfInputOrError() + } finally { + this.in = null + if (buf.length > config.preferredBufSize) reallocateBufToPreferredSize() + if (charBuf.length > config.preferredCharBufSize) + reallocateCharBufToPreferredSize() + } + */ + /** Skips whitespace characters and checks if there are non-whitespace + * characters left in the input. + * + * @throws JsonReaderException + * when there is at least one non-whitespace character left in the input + */ + private[tethys] def endOfInputOrError(): Unit = + if (skipWhitespaces()) decodeError("expected end of input", head) + + /** Skips whitespace characters in the input. + * + * @return + * `true` if and only if there are non-whitespace characters left in the + * input after skipping the whitespace + */ + private[tethys] def skipWhitespaces(): Boolean = { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + val b = buf(pos) + b == ' ' || b == '\n' || (b | 0x4) == '\r' + } + ) pos += 1 + head = pos + pos != tail + } + + private[this] def tokenOrDigitError(t: Byte, pos: Int = head - 1): Nothing = { + var i = appendString("expected '", 0) + i = appendChar(t.toChar, i) + i = appendString("' or digit", i) + decodeError(i, pos, null) + } + + private[this] def tokensError( + t1: Byte, + t2: Byte, + pos: Int = head - 1 + ): Nothing = { + var i = appendString("expected '", 0) + i = appendChar(t1.toChar, i) + i = appendString("' or '", i) + i = appendChar(t2.toChar, i) + i = appendChar('\'', i) + decodeError(i, pos, null) + } + + private[this] def tokenOrNullError(t: Byte, pos: Int = head - 1): Nothing = { + var i = appendString("expected '", 0) + i = appendChar(t.toChar, i) + i = appendString("' or null", i) + decodeError(i, pos, null) + } + + private[this] def tokenError(t: Byte, pos: Int = head - 1): Nothing = { + var i = appendString("expected '", 0) + i = appendChar(t.toChar, i) + i = appendChar('\'', i) + decodeError(i, pos, null) + } + + private[this] def decodeError( + msg: String, + pos: Int, + cause: Throwable = null + ): Nothing = + decodeError(appendString(msg, 0), pos, cause) + + private[this] def decodeError( + from: Int, + pos: Int, + cause: Throwable + ): Nothing = { + var i = appendString(", offset: 0x", from) + val offset = + if ((bbuf eq null) && (in eq null)) 0 + else totalRead - tail + i = appendHexOffset(offset + pos, i) + if (config.appendHexDumpToParseException) { + i = appendString(", buf:", i) + i = appendHexDump(pos, offset.toInt, i) + } + throw new JsonReaderException( + new String(charBuf, 0, i), + cause, + config.throwReaderExceptionWithStackTrace + ) + } + + @tailrec + private[this] def nextByte(pos: Int): Byte = + if (pos < tail) { + head = pos + 1 + buf(pos) + } else nextByte(loadMoreOrError(pos)) + + @tailrec + private[this] def nextByteOrError(t: Byte, pos: Int): Unit = + if (pos < tail) { + if (buf(pos) != t) tokenError(t, pos) + head = pos + 1 + } else nextByteOrError(t, loadMoreOrError(pos)) + + @tailrec + private[this] def nextToken(pos: Int): Byte = + if (pos < tail) { + val b = buf(pos) + if (b == ' ' || b == ',' || b == '\n' || (b | 0x4) == '\r') + nextToken(pos + 1) + else { + head = pos + 1 + b + } + } else nextToken(loadMoreOrError(pos)) + + @tailrec + private[this] def nextTokenOrError(t: Byte, pos: Int): Unit = + if (pos < tail) { + val b = buf(pos) + head = pos + 1 + if ( + b != t && ((b != ' ' && b != '\n' && (b | 0x4) != '\r') || nextToken( + pos + 1 + ) != t) + ) tokenError(t, head - 1) + } else nextTokenOrError(t, loadMoreOrError(pos)) + + @tailrec + private[this] def isNextToken(t: Byte, pos: Int): Boolean = + if (pos < tail) { + val b = buf(pos) + head = pos + 1 + b == t || ((b == ' ' || b == '\n' || (b | 0x4) == '\r') && nextToken( + pos + 1 + ) == t) + } else isNextToken(t, loadMoreOrError(pos)) + + private[this] def isCurrentToken(t: Byte, pos: Int): Boolean = { + if (pos == 0) illegalTokenOperation() + buf(pos - 1) == t + } + + private[this] def illegalTokenOperation(): Nothing = + throw new IllegalStateException( + "expected preceding call of 'nextToken()' or 'isNextToken()'" + ) + + private[this] def missingSetMarkOperation(): Nothing = + throw new IllegalStateException("expected preceding call of 'setMark()'") + + private[this] def appendChar(ch: Char, i: Int): Int = { + ensureCharBufCapacity(i + 1) + charBuf(i) = ch + i + 1 + } + + private[this] def appendChars(cs: Array[Char], i: Int): Int = { + val len = cs.length + val required = i + len + ensureCharBufCapacity(required) + System.arraycopy(cs, 0, charBuf, i, len) + required + } + + private[this] def appendString(s: String, i: Int): Int = { + val len = s.length + val required = i + len + ensureCharBufCapacity(required) + s.getChars(0, len, charBuf, i) + required + } + + private[this] def prependString(s: String, i: Int): Int = { + val len = s.length + val required = i + len + ensureCharBufCapacity(required) + var i1 = required - 1 + var i2 = i1 - len + while (i2 >= 0) { + charBuf(i1) = charBuf(i2) + i1 -= 1 + i2 -= 1 + } + s.getChars(0, len, charBuf, 0) + required + } + + @tailrec + private[this] def parseBoolean(isToken: Boolean, pos: Int): Boolean = + if (pos + 3 < tail) { + val bs = ByteArrayAccess.getInt(buf, pos) + if (bs == 0x65757274) { + head = pos + 4 + true + } else if (bs == 0x736c6166) { + if (nextByte(pos + 4) != 'e') booleanError(pos + 4) + false + } else if ( + isToken && { + val b1 = bs.toByte + b1 == ' ' || b1 == '\n' || (b1 | 0x4) == '\r' + } + ) parseBoolean(isToken, pos + 1) + else booleanError(bs, pos) + } else parseBoolean(isToken, loadMoreOrError(pos)) + + private[this] def booleanError(bs: Int, pos: Int): Nothing = + booleanError( + (Math.max( + java.lang.Integer.numberOfTrailingZeros(bs ^ 0x65757274), + java.lang.Integer.numberOfTrailingZeros(bs ^ 0x736c6166) + ) >> 3) + pos + ) + + private[this] def booleanError(pos: Int): Nothing = + decodeError("illegal boolean", pos) + + def readByte(isToken: Boolean): Byte = { + var b = + if (isToken) nextToken(head) + else nextByte(head) + var s = 0 + if (b == '-') { + b = nextByte(head) + s = -1 + } + if (b < '0' || b > '9') numberError() + var x = b - '0' + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + x = x * 10 + (b - '0') + if (x > 128) byteOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if (x == 128) byteOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x.toByte + } + + def readShort(isToken: Boolean): Short = { + var b = + if (isToken) nextToken(head) + else nextByte(head) + var s = 0 + if (b == '-') { + b = nextByte(head) + s = -1 + } + if (b < '0' || b > '9') numberError() + var x = b - '0' + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + x = x * 10 + (b - '0') + if (x > 32768) shortOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if (x == 32768) shortOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x.toShort + } + + def readInt(isToken: Boolean): Int = { + var b = + if (isToken) nextToken(head) + else nextByte(head) + var s = -1 + if (b == '-') { + b = nextByte(head) + s = 0 + } + if (b < '0' || b > '9') numberError() + var x = '0' - b + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if ( + x < -214748364 || { + x = x * 10 + ('0' - b) + x > 0 + } + ) intOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if ((s & x) == -2147483648) intOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x + } + + def readLong(isToken: Boolean): Long = { + var b = + if (isToken) nextToken(head) + else nextByte(head) + var s = -1L + if (b == '-') { + b = nextByte(head) + s = 0L + } + if (b < '0' || b > '9') numberError() + var x = ('0' - b).toLong + if (isToken && x == 0) ensureNotLeadingZero() + else { + var pos = head + var buf = this.buf + var dec = 0L + while ( + (pos + 7 < tail || { + pos = loadMore(pos) + buf = this.buf + pos + 7 < tail + }) && { + val bs = ByteArrayAccess.getLong( + buf, + pos + ) // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + dec = bs - 0x3030303030303030L + ((bs + 0x4646464646464646L | dec) & 0x8080808080808080L) == 0 + } + ) { + if ( + x < -92233720368L || { + dec *= 2561 + x *= 100000000 + x -= ((dec >> 8 & 0xff000000ffL) * 4294967296000100L + (dec >> 24 & 0xff000000ffL) * 42949672960001L >> 32) + x > 0 + } + ) longOverflowError(pos + 2) + pos += 8 + } + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if ( + x < -922337203685477580L || { + x = x * 10 + ('0' - b) + x > 0 + } + ) longOverflowError(pos) + pos += 1 + } + head = pos + x ^= s + x -= s + if ((s & x) == -9223372036854775808L) longOverflowError(pos - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + } + x + } + + private[this] def ensureNotLeadingZero(): Unit = { + var pos = head + if ( + (pos < tail || { + pos = loadMore(pos) + pos < tail + }) && { + val b = buf(pos) + b >= '0' && b <= '9' + } + ) leadingZeroError(pos - 1) + } + + + + def readFloat(isToken: Boolean): Float = { + var b = + if (isToken) nextToken(head) + else nextByte(head) + var isNeg = false + if (b == '-') { + b = nextByte(head) + isNeg = true + } + if (b < '0' || b > '9') numberError() + var pos = head + var buf = this.buf + val from = pos - 1 + val oldMark = mark + val newMark = + if (oldMark < 0) from + else oldMark + mark = newMark + var m10 = (b - '0').toLong + var e10 = 0 + var digits = 1 + if (isToken && m10 == 0) { + if ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) leadingZeroError(pos - 1) + } else { + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (m10 < 922337203685477580L) { + m10 = m10 * 10 + (b - '0') + digits += 1 + } else e10 += 1 + pos += 1 + } + } + if (b == '.') { + pos += 1 + e10 += digits + var noFracDigits = true + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (m10 < 922337203685477580L) { + m10 = m10 * 10 + (b - '0') + digits += 1 + } + noFracDigits = false + pos += 1 + } + e10 -= digits + if (noFracDigits) numberError(pos) + } + if ((b | 0x20) == 'e') { + b = nextByte(pos + 1) + var s = 0 + if (b == '-' || b == '+') { + s = '+' - b >> 31 + b = nextByte(head) + } + if (b < '0' || b > '9') numberError() + var exp = b - '0' + pos = head + buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if (exp < 214748364) exp = exp * 10 + (b - '0') + pos += 1 + } + exp ^= s + exp -= s + e10 += exp + } + head = pos + var x: Float = + if (e10 == 0 && m10 < 922337203685477580L) m10.toFloat + else if (m10 < 4294967296L && e10 >= digits - 23 && e10 <= 19 - digits) { + val pow10 = pow10Doubles + (if (e10 < 0) m10 / pow10(-e10) + else m10 * pow10(e10)).toFloat + } else toFloat(m10, e10, from, newMark, pos) + if (isNeg) x = -x + if (mark > oldMark) mark = oldMark + x + } + + // Based on the 'Moderate Path' algorithm from the awesome library of Alexander Huszagh: https://github.com/Alexhuszagh/rust-lexical + // Here is his inspiring post: https://www.reddit.com/r/rust/comments/a6j5j1/making_rust_float_parsing_fast_and_correct + private[this] def toFloat( + m10: Long, + e10: Int, + from: Int, + newMark: Int, + pos: Int + ): Float = + if (m10 == 0 || e10 < -64) 0.0f + else if (e10 >= 39) Float.PositiveInfinity + else { + var shift = java.lang.Long.numberOfLeadingZeros(m10) + var m2 = unsignedMultiplyHigh( + pow10Mantissas(e10 + 343), + m10 << shift + ) // FIXME: Use Math.unsignedMultiplyHigh after dropping of JDK 17 support + var e2 = + (e10 * 108853 >> 15) - shift + 1 // (e10 * Math.log(10) / Math.log(2)).toInt - shift + 1 + shift = java.lang.Long.numberOfLeadingZeros(m2) + m2 <<= shift + e2 -= shift + val roundingError = + (if (m10 < 922337203685477580L) 1 + else 19) << shift + val truncatedBitNum = Math.max(-149 - e2, 40) + val savedBitNum = 64 - truncatedBitNum + val mask = -1L >>> Math.max(savedBitNum, 0) + val halfwayDiff = (m2 & mask) - (mask >>> 1) + if (Math.abs(halfwayDiff) > roundingError || savedBitNum <= 0) + java.lang.Float.intBitsToFloat { + var mf = 0 + if (savedBitNum > 0) mf = (m2 >>> truncatedBitNum).toInt + e2 += truncatedBitNum + if (savedBitNum >= 0 && halfwayDiff > 0) { + if (mf == 0xffffff) { + mf = 0x800000 + e2 += 1 + } else mf += 1 + } + if (e2 == -149) mf + else if (e2 >= 105) 0x7f800000 + else e2 + 150 << 23 | mf & 0x7fffff + } + else toFloat(from, newMark, pos) + } + + private[this] def toFloat(from: Int, newMark: Int, pos: Int): Float = { + var offset = from + if (mark == 0) offset -= newMark + java.lang.Float.parseFloat(new String(buf, 0, offset, pos - offset)) + } + + private[this] def unsignedMultiplyHigh(x: Long, y: Long): Long = + Math.multiplyHigh( + x, + y + ) + x + y // Use implementation that works only when both params are negative + + def readBigInt( + isToken: Boolean, + default: BigInt, + digitsLimit: Int + ): BigInt = { + var b = + if (isToken) nextToken(head) + else nextByte(head) + if (isToken && b == 'n') readNullOrNumberError(default, head) + else { + var s = 0 + if (b == '-') { + b = nextByte(head) + s = -1 + } + if (b < '0' || b > '9') numberError() + if (isToken && b == '0') { + ensureNotLeadingZero() + BigInt(0) + } else { + var pos = head + var buf = this.buf + var from = pos - 1 + val oldMark = mark + val newMark = + if (oldMark < 0) from + else oldMark + mark = newMark + var m, bs = 0L + while ( + (pos + 7 < tail || { + pos = loadMore(pos) + buf = this.buf + pos + 7 < tail + }) && { + bs = ByteArrayAccess.getLong( + buf, + pos + ) // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + m = (bs + 0x4646464646464646L | bs - 0x3030303030303030L) & 0x8080808080808080L + m == 0 + } + ) pos += 8 + if (m == 0) { + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) pos += 1 + } else { + val offset = java.lang.Long.numberOfTrailingZeros(m) >> 3 + pos += offset + b = (bs >> (offset << 3)).toByte + } + head = pos + if (mark == 0) from -= newMark + if (mark > oldMark) mark = oldMark + val len = pos - from + if (len >= digitsLimit) digitsLimitError(from + digitsLimit - 1) + if ((b | 0x20) == 'e' || b == '.') numberError(pos) + if (len < 19) { + var x = (buf(from) - '0').toLong + from += 1 + while (from < pos) { + x = x * 10 + (buf(from) - '0') + from += 1 + } + BigInt((x ^ s) - s) + } else if (len <= 36) toBigInt36(buf, from, pos, s) + else + new BigInt({ + if (len <= 308) toBigInteger308(buf, from, pos, s) + else { + // Based on the great idea of Eric Obermühlner to use a tree of smaller BigDecimals for parsing huge numbers + // with O(n^1.5) complexity instead of O(n^2) when using the constructor for the decimal representation from JDK: + // https://github.com/eobermuhlner/big-math/commit/7a5419aac8b2adba2aa700ccf00197f97b2ad89f + val mid = len >> 1 + val midPos = pos - mid + toBigDecimal(buf, from, midPos, s, -mid) + .add(toBigDecimal(buf, midPos, pos, s, 0)) + .unscaledValue + } + }) + } + } + } + + def readBigDecimal( + isToken: Boolean, + default: BigDecimal, + mc: MathContext, + scaleLimit: Int, + digitsLimit: Int + ): BigDecimal = { + var b = + if (isToken) nextToken(head) + else nextByte(head) + if (isToken && b == 'n') readNullOrNumberError(default, head) + else { + var s = 0 + if (b == '-') { + b = nextByte(head) + s = -1 + } + if (b < '0' || b > '9') numberError() + var pos = head + var buf = this.buf + var from = pos - 1 + val oldMark = mark + val newMark = + if (oldMark < 0) from + else oldMark + mark = newMark + var digits = 1 + if (isToken && b == '0') { + if ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) leadingZeroError(pos - 1) + } else { + digits -= pos + var m, bs = 0L + while ( + (pos + 7 < tail || { + digits += pos + pos = loadMore(pos) + digits -= pos + buf = this.buf + pos + 7 < tail + }) && { + bs = ByteArrayAccess.getLong( + buf, + pos + ) // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + m = (bs + 0x4646464646464646L | bs - 0x3030303030303030L) & 0x8080808080808080L + m == 0 + } + ) pos += 8 + if (m == 0) { + while ( + (pos < tail || { + digits += pos + pos = loadMore(pos) + digits -= pos + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) pos += 1 + } else { + val offset = java.lang.Long.numberOfTrailingZeros(m) >> 3 + pos += offset + b = (bs >> (offset << 3)).toByte + } + digits += pos + } + var fracLen, scale = 0 + if (digits >= digitsLimit) + digitsLimitError(pos + digitsLimit - digits - 1) + if (b == '.') { + pos += 1 + fracLen -= pos + var m, bs = 0L + while ( + (pos + 7 < tail || { + fracLen += pos + pos = loadMore(pos) + fracLen -= pos + buf = this.buf + pos + 7 < tail + }) && { + bs = ByteArrayAccess.getLong( + buf, + pos + ) // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + m = (bs + 0x4646464646464646L | bs - 0x3030303030303030L) & 0x8080808080808080L + m == 0 + } + ) pos += 8 + if (m == 0) { + while ( + (pos < tail || { + fracLen += pos + pos = loadMore(pos) + fracLen -= pos + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) pos += 1 + } else { + val offset = java.lang.Long.numberOfTrailingZeros(m) >> 3 + pos += offset + b = (bs >> (offset << 3)).toByte + } + fracLen += pos + digits += fracLen + if (fracLen == 0) numberError(pos) + if (digits >= digitsLimit) + digitsLimitError(pos + digitsLimit - digits - 1) + } + if ((b | 0x20) == 'e') { + b = nextByte(pos + 1) + var ss = 0 + if (b == '-' || b == '+') { + ss = '+' - b >> 31 + b = nextByte(head) + } + if (b < '0' || b > '9') numberError() + scale = '0' - b + pos = head + buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + b = buf(pos) + b >= '0' && b <= '9' + } + ) { + if ( + scale < -214748364 || { + scale = scale * 10 + ('0' - b) + scale > 0 + } + ) numberError(pos) + pos += 1 + } + scale ^= ss + scale -= ss + if (scale == -2147483648) numberError(pos - 1) + } + head = pos + if (mark == 0) from -= newMark + if (mark > oldMark) mark = oldMark + var d = + if (fracLen != 0) { + val limit = from + digits + 1 + val fracPos = limit - fracLen + val fracLimit = fracPos - 1 + if (digits < 19) { + var x = (buf(from) - '0').toLong + from += 1 + while (from < fracLimit) { + x = x * 10 + (buf(from) - '0') + from += 1 + } + from += 1 + while (from < limit) { + x = x * 10 + (buf(from) - '0') + from += 1 + } + java.math.BigDecimal.valueOf((x ^ s) - s, scale + fracLen) + } else + toBigDecimal(buf, from, fracLimit, s, scale).add( + toBigDecimal(buf, fracPos, limit, s, scale + fracLen) + ) + } else toBigDecimal(buf, from, from + digits, s, scale) + if (mc.getPrecision < digits) d = d.plus(mc) + if (Math.abs(d.scale) >= scaleLimit) scaleLimitError() + new BigDecimal(d, mc) + } + } + + private[this] def toBigDecimal( + buf: Array[Byte], + p: Int, + limit: Int, + s: Int, + scale: Int + ): java.math.BigDecimal = { + val len = limit - p + if (len < 19) { + var pos = p + var x = (buf(pos) - '0').toLong + pos += 1 + while (pos < limit) { + x = x * 10 + (buf(pos) - '0') + pos += 1 + } + java.math.BigDecimal.valueOf((x ^ s) - s, scale) + } else if (len <= 36) toBigDecimal36(buf, p, limit, s, scale) + else if (len <= 308) + new java.math.BigDecimal(toBigInteger308(buf, p, limit, s), scale) + else { + // Based on the great idea of Eric Obermühlner to use a tree of smaller BigDecimals for parsing really big numbers + // with O(n^1.5) complexity instead of O(n^2) when using the constructor for the decimal representation from JDK: + // https://github.com/eobermuhlner/big-math/commit/7a5419aac8b2adba2aa700ccf00197f97b2ad89f + val mid = len >> 1 + val midPos = limit - mid + toBigDecimal(buf, p, midPos, s, scale - mid).add( + toBigDecimal(buf, midPos, limit, s, scale) + ) + } + } + + private[this] def toBigInt36( + buf: Array[Byte], + p: Int, + limit: Int, + s: Int + ): BigInt = { + val firstBlockLimit = limit - 18 + var pos = p + var x1 = (buf(pos) - '0').toLong + pos += 1 + while (pos < firstBlockLimit) { + x1 = x1 * 10 + (buf(pos) - '0') + pos += 1 + } + val x2 = + ({ // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + val dec = + (ByteArrayAccess.getLong(buf, pos) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 8)) * 1000000000 + { + val dec = + (ByteArrayAccess.getLong(buf, pos + 9) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 17) - 48000000048L + val q = x1 * 1000000000000000000L + val l = q + x2 + val h = Math.multiplyHigh(x1, 1000000000000000000L) + ((~l & q) >>> 63) + if (l >= 0 && h == 0) BigInt((l ^ s) - s) + else { + var magnitude = this.magnitude + if (magnitude eq null) { + magnitude = new Array[Byte](128) + this.magnitude = magnitude + } + ByteArrayAccess.setLongReversed(magnitude, 0, h) + ByteArrayAccess.setLongReversed(magnitude, 8, l) + new BigInt(new java.math.BigInteger(s | 1, magnitude, 0, 16)) + } + } + + private[this] def toBigDecimal36( + buf: Array[Byte], + p: Int, + limit: Int, + s: Int, + scale: Int + ): java.math.BigDecimal = { + val firstBlockLimit = limit - 18 + var pos = p + var x1 = (buf(pos) - '0').toLong + pos += 1 + while (pos < firstBlockLimit) { + x1 = x1 * 10 + (buf(pos) - '0') + pos += 1 + } + val x2 = + ({ // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + val dec = + (ByteArrayAccess.getLong(buf, pos) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 8)) * 1000000000 + { + val dec = + (ByteArrayAccess.getLong(buf, pos + 9) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 17) - 48000000048L + val q = x1 * 1000000000000000000L + val l = q + x2 + val h = Math.multiplyHigh(x1, 1000000000000000000L) + ((~l & q) >>> 63) + if (l >= 0 && h == 0) java.math.BigDecimal.valueOf((l ^ s) - s, scale) + else { + var magnitude = this.magnitude + if (magnitude eq null) { + magnitude = new Array[Byte](128) + this.magnitude = magnitude + } + ByteArrayAccess.setLongReversed(magnitude, 0, h) + ByteArrayAccess.setLongReversed(magnitude, 8, l) + new java.math.BigDecimal( + new java.math.BigInteger(s | 1, magnitude, 0, 16), + scale + ) + } + } + + private[this] def toBigInteger308( + buf: Array[Byte], + p: Int, + limit: Int, + s: Int + ): java.math.BigInteger = { + val len = limit - p + val last = + (len * 222930821L >> 32).toInt << 3 // (len * Math.log(10) / Math.log(1L << 64)).toInt * 8 + var magnitude = this.magnitude + if (magnitude eq null) { + magnitude = new Array[Byte](128) + this.magnitude = magnitude + } else { + var i = 0 + while (i < last) { + ByteArrayAccess.setLong(magnitude, i, 0L) + i += 8 + } + } + var x = 0L + val firstBlockLimit = len % 18 + p + var pos = p + while (pos < firstBlockLimit) { + x = x * 10 + (buf(pos) - '0') + pos += 1 + } + ByteArrayAccess.setLong(magnitude, last, x) + var first = last + while (pos < limit) { + x = + ({ // Based on the fast parsing of numbers by 8-byte words: https://github.com/wrandelshofer/FastDoubleParser/blob/0903817a765b25e654f02a5a9d4f1476c98a80c9/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/FastDoubleSimd.java#L114-L130 + val dec = + (ByteArrayAccess.getLong(buf, pos) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 8)) * 1000000000 + { + val dec = + (ByteArrayAccess.getLong(buf, pos + 9) - 0x3030303030303030L) * 2561 + (dec >> 8 & 0xff000000ffL) * 42949672960001000L + (dec >> 24 & 0xff000000ffL) * 429496729600010L >> 32 + } + buf(pos + 17) - 48000000048L + pos += 18 + first = Math.max(first - 8, 0) + var i = last + val q = 1000000000000000000L + var m, mq = 0L + while ({ + m = ByteArrayAccess.getLong(magnitude, i) + mq = m * q + x += mq + ByteArrayAccess.setLong(magnitude, i, x) + i -= 8 + i >= first + }) { + x = Math.multiplyHigh( + m, + q + ) + (m >> 63 & q) + ((~x & mq) >>> 63) // TODO: when dropping JDK 17 support replace by Math.unsignedMultiplyHigh(m, q) + ((~x & mq) >>> 63) + } + } + var i = 0 + while (i <= last) { + ByteArrayAccess.setLongReversed( + magnitude, + i, + ByteArrayAccess.getLong(magnitude, i) + ) + i += 8 + } + new java.math.BigInteger(s | 1, magnitude, 0, last + 8) + } + + @tailrec + private[this] def readNullOrNumberError[@specialized A]( + default: A, + pos: Int + ): A = + if (default != null) { + if (pos + 2 < tail) { + val bs = ByteArrayAccess.getInt(buf, pos - 1) + if (bs == 0x6c6c756e) { + head = pos + 3 + default + } else decodeError("expected number or null", bs, pos) + } else readNullOrNumberError(default, loadMoreOrError(pos - 1) + 1) + } else numberError(pos - 1) + + private[this] def numberError(pos: Int = head - 1): Nothing = + decodeError("illegal number", pos) + + private[this] def digitsLimitError(pos: Int): Nothing = + decodeError("value exceeds limit for number of digits", pos) + + private[this] def scaleLimitError(pos: Int = head - 1): Nothing = + decodeError("value exceeds limit for scale", pos) + + private[this] def leadingZeroError(pos: Int): Nothing = + decodeError("illegal number with leading zero", pos) + + private[this] def byteOverflowError(pos: Int): Nothing = + decodeError("value is too large for byte", pos) + + private[this] def shortOverflowError(pos: Int): Nothing = + decodeError("value is too large for short", pos) + + private[this] def intOverflowError(pos: Int): Nothing = + decodeError("value is too large for int", pos) + + private[this] def longOverflowError(pos: Int): Nothing = + decodeError("value is too large for long", pos) + + private[this] def decodeError(msg: String, bs: Int, pos: Int): Nothing = + decodeError( + msg, + (java.lang.Integer.numberOfTrailingZeros(bs ^ 0x6c6c756e) >> 3) + pos - 1 + ) + + private[this] def tokenOrNullError(t: Byte, bs: Int, pos: Int): Nothing = + tokenOrNullError( + t, + (java.lang.Integer.numberOfTrailingZeros(bs ^ 0x6c6c756e) >> 3) + pos - 1 + ) + + private[this] def digitError(pos: Int): Nothing = + decodeError("expected digit", pos) + + @tailrec + private[this] def parseString( + i: Int, + minLim: Int, + charBuf: Array[Char], + pos: Int + ): Int = + if (i + 3 < minLim) { // Based on SWAR routine of JSON string parsing: https://github.com/sirthias/borer/blob/fde9d1ce674d151b0fee1dd0c2565020c3f6633a/core/src/main/scala/io/bullet/borer/json/JsonParser.scala#L456 + val bs = ByteArrayAccess.getInt(buf, pos) + val m = + ((bs - 0x20202020 ^ 0x3c3c3c3c) - 0x1010101 | (bs ^ 0x5d5d5d5d) + 0x1010101) & 0x80808080 + charBuf(i) = (bs & 0xff).toChar + charBuf(i + 1) = (bs >> 8 & 0xff).toChar + charBuf(i + 2) = (bs >> 16 & 0xff).toChar + charBuf(i + 3) = (bs >> 24).toChar + if (m != 0) { + val offset = java.lang.Integer.numberOfTrailingZeros(m) >> 3 + if ((bs >> (offset << 3)).toByte == '"') { + head = pos + offset + 1 + i + offset + } else + parseEncodedString( + i + offset, + charBuf.length - 1, + charBuf, + pos + offset + ) + } else parseString(i + 4, minLim, charBuf, pos + 4) + } else if (i < minLim) { + val b = buf(pos) + charBuf(i) = b.toChar + if (b == '"') { + head = pos + 1 + i + } else if ((b - 0x20 ^ 0x3c) <= 0) + parseEncodedString(i, charBuf.length - 1, charBuf, pos) + else parseString(i + 1, minLim, charBuf, pos + 1) + } else if (pos >= tail) { + val newPos = loadMoreOrError(pos) + parseString( + i, + Math.min(charBuf.length, i + tail - newPos), + charBuf, + newPos + ) + } else + parseString( + i, + Math.min(growCharBuf(i + 1), i + tail - pos), + this.charBuf, + pos + ) + + @tailrec + private[this] def parseEncodedString( + i: Int, + lim: Int, + charBuf: Array[Char], + pos: Int + ): Int = { + val remaining = tail - pos + if (i < lim) { + if (remaining > 0) { + val b1 = buf(pos) + if (b1 >= 0) { + if (b1 == '"') { + head = pos + 1 + i + } else if (b1 != '\\') { // 0aaaaaaa (UTF-8 byte) -> 000000000aaaaaaa (UTF-16 char) + if (b1 < ' ') unescapedControlCharacterError(pos) + charBuf(i) = b1.toChar + parseEncodedString(i + 1, lim, charBuf, pos + 1) + } else if (remaining > 1) { + val b2 = buf(pos + 1) + if (b2 != 'u') { + charBuf(i) = (b2: @switch) match { + case '"' => '"' + case 'n' => '\n' + case 'r' => '\r' + case 't' => '\t' + case 'b' => '\b' + case 'f' => '\f' + case '\\' => '\\' + case '/' => '/' + case _ => escapeSequenceError(pos + 1) + } + parseEncodedString(i + 1, lim, charBuf, pos + 2) + } else if (remaining > 5) { + val ch1 = readEscapedUnicode(pos + 2, buf) + charBuf(i) = ch1 + if ((ch1 & 0xf800) != 0xd800) + parseEncodedString(i + 1, lim, charBuf, pos + 6) + else if (remaining > 11) { + if (buf(pos + 6) != '\\') escapeSequenceError(pos + 6) + if (buf(pos + 7) != 'u') escapeSequenceError(pos + 7) + val ch2 = readEscapedUnicode(pos + 8, buf) + charBuf(i + 1) = ch2 + if (ch1 >= 0xdc00 || (ch2 & 0xfc00) != 0xdc00) + decodeError("illegal surrogate character pair", pos + 11) + parseEncodedString(i + 2, lim, charBuf, pos + 12) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else if ((b1 & 0xe0) == 0xc0) { // 110bbbbb 10aaaaaa (UTF-8 bytes) -> 00000bbbbbaaaaaa (UTF-16 char) + if (remaining > 1) { + val b2 = buf(pos + 1) + val ch = + (b1 << 6 ^ b2 ^ 0xf80).toChar // 0xF80 == 0xC0.toByte << 6 ^ 0x80.toByte + charBuf(i) = ch + if ((b2 & 0xc0) != 0x80 || ch < 0x80) + malformedBytesError(b1, b2, pos) + parseEncodedString(i + 1, lim, charBuf, pos + 2) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else if ((b1 & 0xf0) == 0xe0) { // 1110cccc 10bbbbbb 10aaaaaa (UTF-8 bytes) -> ccccbbbbbbaaaaaa (UTF-16 char) + if (remaining > 2) { + val b2 = buf(pos + 1) + val b3 = buf(pos + 2) + val ch = + (b1 << 12 ^ b2 << 6 ^ b3 ^ 0x1f80).toChar // 0x1F80 == (0x80.toByte << 6 ^ 0x80.toByte).toChar + charBuf(i) = ch + if ( + (b2 & 0xc0) != 0x80 || (b3 & 0xc0) != 0x80 || ch < 0x800 || + (ch & 0xf800) == 0xd800 + ) malformedBytesError(b1, b2, b3, pos) + parseEncodedString(i + 1, lim, charBuf, pos + 3) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else if ((b1 & 0xf8) == 0xf0) { // 11110ddd 10ddcccc 10bbbbbb 10aaaaaa (UTF-8 bytes) -> 110110uuuuccccbb 110111bbbbaaaaaa (UTF-16 chars), where uuuu = ddddd - 1 + if (remaining > 3) { + val b2 = buf(pos + 1) + val b3 = buf(pos + 2) + val b4 = buf(pos + 3) + val cp = + b1 << 18 ^ b2 << 12 ^ b3 << 6 ^ b4 ^ 0x381f80 // 0x381F80 == 0xF0.toByte << 18 ^ 0x80.toByte << 12 ^ 0x80.toByte << 6 ^ 0x80.toByte + val ch1 = + ((cp >>> 10) + 0xd7c0).toChar // 0xD7C0 == 0xD800 - (0x10000 >>> 10) + charBuf(i) = ch1 + charBuf(i + 1) = ((cp & 0x3ff) | 0xdc00).toChar + if ( + (b2 & 0xc0) != 0x80 || (b3 & 0xc0) != 0x80 || (b4 & 0xc0) != 0x80 || + (ch1 & 0xf800) != 0xd800 + ) malformedBytesError(b1, b2, b3, b4, pos) + parseEncodedString(i + 2, lim, charBuf, pos + 4) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else malformedBytesError(b1, pos) + } else parseEncodedString(i, lim, charBuf, loadMoreOrError(pos)) + } else + parseEncodedString( + i, + growCharBuf(i + 2) - 1, + this.charBuf, + pos + ) // 2 is length of surrogate pair + } + + @tailrec + private[this] def parseChar(pos: Int): Char = { + val remaining = tail - pos + if (remaining > 0) { + val b1 = buf(pos) + if (b1 >= 0) { + if (b1 == '"') characterError(pos) + else if (b1 != '\\') { // 0aaaaaaa (UTF-8 byte) -> 000000000aaaaaaa (UTF-16 char) + if (b1 < ' ') unescapedControlCharacterError(pos) + head = pos + 1 + return b1.toChar + } else if (remaining > 1) { + val b2 = buf(pos + 1) + if (b2 != 'u') { + head = pos + 2 + (b2: @switch) match { + case 'b' => return '\b' + case 'f' => return '\f' + case 'n' => return '\n' + case 'r' => return '\r' + case 't' => return '\t' + case '"' => return '"' + case '/' => return '/' + case '\\' => return '\\' + case _ => escapeSequenceError(pos + 1) + } + } else if (remaining > 5) { + val ch = readEscapedUnicode(pos + 2, buf) + head = pos + 6 + if ((ch & 0xf800) == 0xd800) surrogateCharacterError(pos + 5) + return ch + } + } + } else if ((b1 & 0xe0) == 0xc0) { // 110bbbbb 10aaaaaa (UTF-8 bytes) -> 00000bbbbbaaaaaa (UTF-16 char) + if (remaining > 1) { + val b2 = buf(pos + 1) + val ch = + (b1 << 6 ^ b2 ^ 0xf80).toChar // 0xF80 == 0xC0.toByte << 6 ^ 0x80.toByte + head = pos + 2 + if ((b2 & 0xc0) != 0x80 || ch < 0x80) malformedBytesError(b1, b2, pos) + return ch + } + } else if ((b1 & 0xf0) == 0xe0) { // 1110cccc 10bbbbbb 10aaaaaa (UTF-8 bytes) -> ccccbbbbbbaaaaaa (UTF-16 char) + if (remaining > 2) { + val b2 = buf(pos + 1) + val b3 = buf(pos + 2) + val ch = + (b1 << 12 ^ b2 << 6 ^ b3 ^ 0x1f80).toChar // 0x1F80 == (0x80.toByte << 6 ^ 0x80.toByte).toChar + head = pos + 3 + if ( + (b2 & 0xc0) != 0x80 || (b3 & 0xc0) != 0x80 || ch < 0x800 || + (ch & 0xf800) == 0xd800 + ) malformedBytesError(b1, b2, b3, pos) + return ch + } + } else if ((b1 & 0xf8) == 0xf0) surrogateCharacterError(pos + 3) + else malformedBytesError(b1, pos) + } + parseChar(loadMoreOrError(pos)) + } + + private[this] def readEscapedUnicode(pos: Int, buf: Array[Byte]): Char = { + val ns = nibbles + val x = + ns(buf(pos) & 0xff) << 12 | + ns(buf(pos + 1) & 0xff) << 8 | + ns(buf(pos + 2) & 0xff) << 4 | + ns(buf(pos + 3) & 0xff) + if (x < 0) hexDigitError(pos) + x.toChar + } + + private[this] def parseBase16(ns: Array[Byte]): Array[Byte] = { + var charBuf = this.charBuf + var len = charBuf.length + var pos = head + var buf = this.buf + var i, bits = 0 + while ( + bits >= 0 && (pos + 3 < tail || { + pos = loadMore(pos) + buf = this.buf + pos + 3 < tail + }) + ) { + if (i >= len) { + len = growCharBuf(i + 1) + charBuf = this.charBuf + } + val posLim = Math.min(tail - 3, (len - i << 2) + pos) + while ( + pos < posLim && { + bits = ns(buf(pos) & 0xff) << 12 | + ns(buf(pos + 1) & 0xff) << 8 | + ns(buf(pos + 2) & 0xff) << 4 | + ns(buf(pos + 3) & 0xff) + bits >= 0 + } + ) { + charBuf(i) = bits.toChar + i += 1 + pos += 4 + } + } + val bLen = i << 1 + var bs: Array[Byte] = null + var b = nextByte(pos) + if (b == '"') bs = new Array[Byte](bLen) + else { + bits = ns(b & 0xff).toInt + if (bits < 0) decodeError("expected '\"' or hex digit") + b = nextByte(head) + bits = bits << 4 | ns(b & 0xff) + if (bits < 0) decodeError("expected hex digit") + b = nextByte(head) + if (b != '"') { + if (ns(b & 0xff) < 0) decodeError("expected '\"' or hex digit") + b = nextByte(head) + decodeError("expected hex digit") + } + bs = new Array[Byte](bLen + 1) + bs(bLen) = bits.toByte + } + i = 0 + var j = 0 + while (j < bLen) { + val ch = charBuf(i) + bs(j) = (ch >> 8).toByte + bs(j + 1) = ch.toByte + i += 1 + j += 2 + } + bs + } + + private[this] def parseBase64(ds: Array[Byte]): Array[Byte] = { + var charBuf = this.charBuf + var lenM1 = charBuf.length - 1 + var pos = head + var buf = this.buf + var i, bits = 0 + while ( + bits >= 0 && (pos + 3 < tail || { + pos = loadMore(pos) + buf = this.buf + pos + 3 < tail + }) + ) { + if (i >= lenM1) { + lenM1 = growCharBuf(i + 1) - 1 + charBuf = this.charBuf + } + val posLim = Math.min(tail - 3, (lenM1 - i << 1) + pos) + while ( + pos < posLim && { + bits = ds(buf(pos) & 0xff) << 18 | + ds(buf(pos + 1) & 0xff) << 12 | + ds(buf(pos + 2) & 0xff) << 6 | + ds(buf(pos + 3) & 0xff) + bits >= 0 + } + ) { + charBuf(i) = (bits >> 8).toChar + charBuf(i + 1) = bits.toChar + i += 2 + pos += 4 + } + } + val bLen = i + (i >> 1) + var bs: Array[Byte] = null + var b = nextByte(pos) + if (b == '"') bs = new Array[Byte](bLen) + else { + bits = ds(b & 0xff).toInt + if (bits < 0) decodeError("expected '\"' or base64 digit") + b = nextByte(head) + bits = bits << 6 | ds(b & 0xff) + if (bits < 0) decodeError("expected base64 digit") + b = nextByte(head) + if (b == '"' || b == '=') { + if (b == '=') { + nextByteOrError('=', head) + nextByteOrError('"', head) + } + bs = new Array[Byte](bLen + 1) + bs(bLen) = (bits >> 4).toByte + } else { + bits = bits << 6 | ds(b & 0xff) + if (bits < 0) decodeError("expected '\"' or '=' or base64 digit") + b = nextByte(head) + if (b == '=') nextByteOrError('"', head) + else if (b != '"') tokensError('"', '=') + bs = new Array[Byte](bLen + 2) + bs(bLen) = (bits >> 10).toByte + bs(bLen + 1) = (bits >> 2).toByte + } + } + i = 0 + var j = 0 + while (j < bLen) { + val ch = charBuf(i) + bs(j) = (ch >> 8).toByte + bs(j + 1) = ch.toByte + bs(j + 2) = charBuf(i + 1).toByte + i += 2 + j += 3 + } + bs + } + + @tailrec + private[this] def hexDigitError(pos: Int): Nothing = { + if (nibbles(buf(pos) & 0xff) < 0) decodeError("expected hex digit", pos) + hexDigitError(pos + 1) + } + + private[this] def characterError(pos: Int): Nothing = + decodeError("illegal character", pos) + + private[this] def escapeSequenceError(pos: Int): Nothing = + decodeError("illegal escape sequence", pos) + + private[this] def surrogateCharacterError(pos: Int): Nothing = + decodeError("illegal surrogate character", pos) + + private[this] def unescapedControlCharacterError(pos: Int): Nothing = + decodeError("unescaped control character", pos) + + private[this] def malformedBytesError(b1: Byte, pos: Int): Nothing = { + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, hexDigits) + decodeError(i, pos, null) + } + + private[this] def malformedBytesError( + b1: Byte, + b2: Byte, + pos: Int + ): Nothing = { + val ds = hexDigits + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b2, i, ds) + decodeError(i, pos + 1, null) + } + + private[this] def malformedBytesError( + b1: Byte, + b2: Byte, + b3: Byte, + pos: Int + ): Nothing = { + val ds = hexDigits + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b2, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b3, i, ds) + decodeError(i, pos + 2, null) + } + + private[this] def malformedBytesError( + b1: Byte, + b2: Byte, + b3: Byte, + b4: Byte, + pos: Int + ): Nothing = { + val ds = hexDigits + var i = appendString("malformed byte(s): 0x", 0) + i = appendHexByte(b1, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b2, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b3, i, ds) + i = appendString(", 0x", i) + i = appendHexByte(b4, i, ds) + decodeError(i, pos + 3, null) + } + + private[this] def appendHexDump(pos: Int, offset: Int, from: Int): Int = { + val hexDumpSizeInBytes = config.hexDumpSize << 4 + val start = Math.max(pos - hexDumpSizeInBytes & 0xfffffff0, 0) + val end = Math.min(pos + hexDumpSizeInBytes + 16 & 0xfffffff0, tail) + val alignedAbsFrom = start + offset & 0xfffffff0 + val alignedAbsTo = end + offset + 15 & 0xfffffff0 + val len = alignedAbsTo - alignedAbsFrom + val bufOffset = alignedAbsFrom - offset + var i = appendChars(dumpBorder, from) + i = appendChars(dumpHeader, i) + i = appendChars(dumpBorder, i) + val buf = this.buf + val ds = hexDigits + var charBuf = this.charBuf + var lim = charBuf.length + var j = 0 + while (j < len) { + val linePos = j & 0xf + if (linePos == 0) { + if (i + 81 >= lim) { // 81 == dumpBorder.length + lim = growCharBuf(i + 81) + charBuf = this.charBuf + } + charBuf(i) = '\n' + charBuf(i + 1) = '|' + charBuf(i + 2) = ' ' + putHexInt(alignedAbsFrom + j, i + 3, charBuf, ds) + charBuf(i + 11) = ' ' + charBuf(i + 12) = '|' + charBuf(i + 13) = ' ' + i += 14 + } + val pos = bufOffset + j + charBuf(i + 50 - (linePos << 1)) = if (pos >= start && pos < end) { + val b = buf(pos) + charBuf(i) = ds(b >> 4 & 0xf) + charBuf(i + 1) = ds(b & 0xf) + charBuf(i + 2) = ' ' + if (b <= 31 || b >= 127) '.' + else b.toChar + } else { + charBuf(i) = ' ' + charBuf(i + 1) = ' ' + charBuf(i + 2) = ' ' + ' ' + } + i += 3 + if (linePos == 15) { + charBuf(i) = '|' + charBuf(i + 1) = ' ' + charBuf(i + 18) = ' ' + charBuf(i + 19) = '|' + i += 20 + } + j += 1 + } + appendChars(dumpBorder, i) + } + + private[this] def appendHexOffset(d: Long, i: Int): Int = { + ensureCharBufCapacity(i + 16) + val ds = hexDigits + var j = i + val dl = d.toInt + if (dl != d) { + val dh = (d >> 32).toInt + var shift = 32 - java.lang.Integer.numberOfLeadingZeros(dh) & 0x1c + while (shift >= 0) { + charBuf(j) = ds(dh >> shift & 0xf) + shift -= 4 + j += 1 + } + } + putHexInt(dl, j, charBuf, ds) + j + 8 + } + + private[this] def appendHexByte(b: Byte, i: Int, ds: Array[Char]): Int = { + ensureCharBufCapacity(i + 2) + charBuf(i) = ds(b >> 4 & 0xf) + charBuf(i + 1) = ds(b & 0xf) + i + 2 + } + + private[this] def putHexInt( + d: Int, + i: Int, + charBuf: Array[Char], + ds: Array[Char] + ): Unit = { + charBuf(i) = ds(d >>> 28) + charBuf(i + 1) = ds(d >> 24 & 0xf) + charBuf(i + 2) = ds(d >> 20 & 0xf) + charBuf(i + 3) = ds(d >> 16 & 0xf) + charBuf(i + 4) = ds(d >> 12 & 0xf) + charBuf(i + 5) = ds(d >> 8 & 0xf) + charBuf(i + 6) = ds(d >> 4 & 0xf) + charBuf(i + 7) = ds(d & 0xf) + } + + private[this] def growCharBuf(required: Int): Int = { + var charBufLen = charBuf.length + val maxCharBufSize = config.maxCharBufSize + if (charBufLen == maxCharBufSize) tooLongStringError() + charBufLen = + (-1 >>> Integer.numberOfLeadingZeros(charBufLen | required)) + 1 + if (Integer.compareUnsigned(charBufLen, maxCharBufSize) > 0) + charBufLen = maxCharBufSize + charBuf = java.util.Arrays.copyOf(charBuf, charBufLen) + charBufLen + } + + private[this] def ensureCharBufCapacity(required: Int): Unit = + if (charBuf.length < required) growCharBuf(required): Unit + + @tailrec + private[this] def skipString(evenBackSlashes: Boolean, pos: Int): Int = + if (pos < tail) { + if (evenBackSlashes) { + val b = buf(pos) + if (b == '"') pos + 1 + else skipString(b != '\\', pos + 1) + } else skipString(evenBackSlashes = true, pos + 1) + } else skipString(evenBackSlashes, loadMoreOrError(pos)) + + private[this] def skipNumber(p: Int): Int = { + var pos = p + var buf = this.buf + while ( + (pos < tail || { + pos = loadMore(pos) + buf = this.buf + pos < tail + }) && { + val b = buf(pos) + (b >= '0' && b <= '9') || b == '.' || (b | 0x20) == 'e' || b == '-' || b == '+' + } + ) pos += 1 + pos + } + + @tailrec + private[this] def skipObject(level: Int, pos: Int): Int = + if (pos < tail) { + val b = buf(pos) + if (b == '"') + skipObject(level, skipString(evenBackSlashes = true, pos + 1)) + else if (b == '{') skipObject(level + 1, pos + 1) + else if (b != '}') skipObject(level, pos + 1) + else if (level != 0) skipObject(level - 1, pos + 1) + else pos + 1 + } else skipObject(level, loadMoreOrError(pos)) + + @tailrec + private[this] def skipArray(level: Int, pos: Int): Int = + if (pos < tail) { + val b = buf(pos) + if (b == '"') + skipArray(level, skipString(evenBackSlashes = true, pos + 1)) + else if (b == '[') skipArray(level + 1, pos + 1) + else if (b != ']') skipArray(level, pos + 1) + else if (level != 0) skipArray(level - 1, pos + 1) + else pos + 1 + } else skipArray(level, loadMoreOrError(pos)) + + @tailrec + private[this] def skipFixedBytes(n: Int, pos: Int): Int = { + val newPos = pos + n + if (newPos <= tail) newPos + else skipFixedBytes(n, loadMoreOrError(pos)) + } + + private[this] def loadMoreOrError(pos: Int): Int = { + if ((bbuf eq null) && (in eq null)) endOfInputError() + loadMore(pos, throwOnEndOfInput = true) + } + + private[this] def loadMore(pos: Int): Int = + if ((bbuf eq null) && (in eq null)) pos + else loadMore(pos, throwOnEndOfInput = false) + + private[this] def loadMore(pos: Int, throwOnEndOfInput: Boolean): Int = { + var newPos = pos + val offset = + if (mark < 0) pos + else mark + if (offset > 0) { + newPos -= offset + val buf = this.buf + val remaining = tail - offset + var i = 0 + while (i < remaining) { + buf(i) = buf(i + offset) + i += 1 + } + if (mark > 0) mark = 0 + tail = remaining + head = newPos + } else growBuf() + var len = buf.length - tail + if (bbuf ne null) { + len = Math.min(bbuf.remaining, len) + bbuf.get(buf, tail, len) + } else len = Math.max(in.read(buf, tail, len), 0) + if (throwOnEndOfInput && len == 0) endOfInputError() + tail += len + totalRead += len + newPos + } + + private[this] def growBuf(): Unit = { + var bufLen = buf.length + val maxBufSize = config.maxBufSize + if (bufLen == maxBufSize) tooLongInputError() + bufLen <<= 1 + if (Integer.compareUnsigned(bufLen, maxBufSize) > 0) bufLen = maxBufSize + buf = java.util.Arrays.copyOf(buf, bufLen) + } + + private[this] def tooLongInputError(): Nothing = + decodeError("too long part of input exceeded 'maxBufSize'", tail) + + private[this] def tooLongStringError(): Nothing = + decodeError("too long string exceeded 'maxCharBufSize'", tail) + + private[this] def endOfInputError(): Nothing = + decodeError("unexpected end of input", tail) + + private[this] def reallocateBufToPreferredSize(): Unit = buf = + new Array[Byte](config.preferredBufSize) + + private[this] def reallocateCharBufToPreferredSize(): Unit = charBuf = + new Array[Char](config.preferredCharBufSize) +} + +object JsonReader { + private final val pow10Doubles: Array[Double] = + Array(1, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, + 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, + 1e+22) + /* Use the following code to generate `pow10Mantissas` in Scala REPL: + val ms = new Array[Long](653) + var pow10 = BigInt(10) + var i = 342 + while (i >= 0) { + ms(i) = ((BigInt(1) << (pow10.bitLength + 63)) / pow10).longValue + pow10 *= 10 + i -= 1 + } + pow10 = BigInt(1) << 63 + i = 343 + while (i < 653) { + ms(i) = (pow10 >> (pow10.bitLength - 64)).longValue + pow10 *= 10 + i += 1 + } + ms.grouped(4).map(_.mkString("L, ")).mkString("Array(\n", "L,\n", "L\n)") + */ + private final val pow10Mantissas: Array[Long] = Array( + -4671960508600951122L, -1228264617323800998L, -7685194413468457480L, + -4994806998408183946L, -1631822729582842029L, -7937418233630358124L, + -5310086773610559751L, -2025922448585811785L, -8183730558007214222L, + -5617977179081629873L, -2410785455424649437L, -8424269937281487754L, + -5918651403174471789L, -2786628235540701832L, -8659171674854020501L, + -6212278575140137722L, -3153662200497784248L, -8888567902952197011L, + -6499023860262858360L, -3512093806901185046L, -9112587656954322510L, + -6779048552765515233L, -3862124672529506138L, -215969822234494768L, + -7052510166537641086L, -4203951689744663454L, -643253593753441413L, + -7319562523736982739L, -4537767136243840520L, -1060522901877412746L, + -7580355841314464822L, -4863758783215693124L, -1468012460592228501L, + -7835036815511224669L, -5182110000961642932L, -1865951482774665761L, + -8083748704375247957L, -5492999862041672042L, -2254563809124702148L, + -8326631408344020699L, -5796603242002637969L, -2634068034075909558L, + -8563821548938525330L, -6093090917745768758L, -3004677628754823043L, + -8795452545612846258L, -6382629663588669919L, -3366601061058449494L, + -9021654690802612790L, -6665382345075878084L, -3720041912917459700L, + -38366372719436721L, -6941508010590729807L, -4065198994811024355L, + -469812725086392539L, -7211161980820077193L, -4402266457597708587L, + -891147053569747830L, -7474495936122174250L, -4731433901725329908L, + -1302606358729274481L, -7731658001846878407L, -5052886483881210105L, + -1704422086424124727L, -7982792831656159810L, -5366805021142811859L, + -2096820258001126919L, -8228041688891786181L, -5673366092687344822L, + -2480021597431793123L, -8467542526035952558L, -5972742139117552794L, + -2854241655469553088L, -8701430062309552536L, -6265101559459552766L, + -3219690930897053053L, -8929835859451740015L, -6550608805887287114L, + -3576574988931720989L, -9152888395723407474L, -6829424476226871438L, + -3925094576856201394L, -294682202642863838L, -7101705404292871755L, + -4265445736938701790L, -720121152745989333L, -7367604748107325189L, + -4597819916706768583L, -1135588877456072824L, -7627272076051127371L, + -4922404076636521310L, -1541319077368263733L, -7880853450996246689L, + -5239380795317920458L, -1937539975720012668L, -8128491512466089774L, + -5548928372155224313L, -2324474446766642487L, -8370325556870233411L, + -5851220927660403859L, -2702340141148116920L, -8606491615858654931L, + -6146428501395930760L, -3071349608317525546L, -8837122532839535322L, + -6434717147622031249L, -3431710416100151157L, -9062348037703676329L, + -6716249028702207507L, -3783625267450371480L, -117845565885576446L, + -6991182506319567135L, -4127292114472071014L, -547429124662700864L, + -7259672230555269896L, -4462904269766699466L, -966944318780986428L, + -7521869226879198374L, -4790650515171610063L, -1376627125537124675L, + -7777920981101784778L, -5110715207949843068L, -1776707991509915931L, + -8027971522334779313L, -5423278384491086237L, -2167411962186469893L, + -8272161504007625539L, -5728515861582144020L, -2548958808550292121L, + -8510628282985014432L, -6026599335303880135L, -2921563150702462265L, + -8743505996830120772L, -6317696477610263061L, -3285434578585440922L, + -8970925639256982432L, -6601971030643840136L, -3640777769877412266L, + -9193015133814464522L, -6879582898840692749L, -3987792605123478032L, + -373054737976959636L, -7150688238876681629L, -4326674280168464132L, + -796656831783192261L, -7415439547505577019L, -4657613415954583370L, + -1210330751515841308L, -7673985747338482674L, -4980796165745715438L, + -1614309188754756393L, -7926472270612804602L, -5296404319838617848L, + -2008819381370884406L, -8173041140997884610L, -5604615407819967859L, + -2394083241347571919L, -8413831053483314306L, -5905602798426754978L, + -2770317479606055818L, -8648977452394866743L, -6199535797066195524L, + -3137733727905356501L, -8878612607581929669L, -6486579741050024183L, + -3496538657885142324L, -9102865688819295809L, -6766896092596731857L, + -3846934097318526917L, -196981603220770742L, -7040642529654063570L, + -4189117143640191558L, -624710411122851544L, -7307973034592864071L, + -4523280274813692185L, -1042414325089727327L, -7569037980822161435L, + -4849611457600313890L, -1450328303573004458L, -7823984217374209643L, + -5168294253290374149L, -1848681798185579782L, -8072955151507069220L, + -5479507920956448621L, -2237698882768172872L, -8316090829371189901L, + -5783427518286599473L, -2617598379430861437L, -8553528014785370254L, + -6080224000054324913L, -2988593981640518238L, -8785400266166405755L, + -6370064314280619289L, -3350894374423386208L, -9011838011655698236L, + -6653111496142234891L, -3704703351750405709L, -19193171260619233L, + -6929524759678968877L, -4050219931171323192L, -451088895536766085L, + -7199459587351560659L, -4387638465762062920L, -872862063775190746L, + -7463067817500576073L, -4717148753448332187L, -1284749923383027329L, + -7720497729755473937L, -5038936143766954517L, -1686984161281305242L, + -7971894128441897632L, -5353181642124984136L, -2079791034228842266L, + -8217398424034108273L, -5660062011615247437L, -2463391496091671392L, + -8457148712698376476L, -5959749872445582691L, -2838001322129590460L, + -8691279853972075893L, -6252413799037706963L, -3203831230369745799L, + -8919923546622172981L, -6538218414850328322L, -3561087000135522498L, + -9143208402725783417L, -6817324484979841368L, -3909969587797413806L, + -275775966319379353L, -7089889006590693952L, -4250675239810979535L, + -701658031336336515L, -7356065297226292178L, -4583395603105477319L, + -1117558485454458744L, -7616003081050118571L, -4908317832885260310L, + -1523711272679187483L, -7869848573065574033L, -5225624697904579637L, + -1920344853953336643L, -8117744561361917258L, -5535494683275008668L, + -2307682335666372931L, -8359830487432564938L, -5838102090863318269L, + -2685941595151759932L, -8596242524610931813L, -6133617137336276863L, + -3055335403242958174L, -8827113654667930715L, -6422206049907525490L, + -3416071543957018958L, -9052573742614218705L, -6704031159840385477L, + -3768352931373093942L, -98755145788979524L, -6979250993759194058L, + -4112377723771604669L, -528786136287117932L, -7248020362820530564L, + -4448339435098275301L, -948738275445456222L, -7510490449794491995L, + -4776427043815727089L, -1358847786342270957L, -7766808894105001205L, + -5096825099203863602L, -1759345355577441598L, -8017119874876982855L, + -5409713825168840664L, -2150456263033662926L, -8261564192037121185L, + -5715269221619013577L, -2532400508596379068L, -8500279345513818773L, + -6013663163464885563L, -2905392935903719049L, -8733399612580906262L, + -6305063497298744923L, -3269643353196043250L, -8961056123388608887L, + -6589634135808373205L, -3625356651333078602L, -9183376934724255983L, + -6867535149977932074L, -3972732919045027189L, -354230130378896082L, + -7138922859127891907L, -4311967555482476980L, -778273425925708321L, + -7403949918844649557L, -4643251380128424042L, -1192378206733142148L, + -7662765406849295699L, -4966770740134231719L, -1596777406740401745L, + -7915514906853832947L, -5282707615139903279L, -1991698500497491195L, + -8162340590452013853L, -5591239719637629412L, -2377363631119648861L, + -8403381297090862394L, -5892540602936190089L, -2753989735242849707L, + -8638772612167862923L, -6186779746782440750L, -3121788665050663033L, + -8868646943297746252L, -6474122660694794911L, -3480967307441105734L, + -9093133594791772940L, -6754730975062328271L, -3831727700400522434L, + -177973607073265139L, -7028762532061872568L, -4174267146649952806L, + -606147914885053103L, -7296371474444240046L, -4508778324627912153L, + -1024286887357502287L, -7557708332239520786L, -4835449396872013078L, + -1432625727662628443L, -7812920107430224633L, -5154464115860392887L, + -1831394126398103205L, -8062150356639896359L, -5466001927372482545L, + -2220816390788215277L, -8305539271883716405L, -5770238071427257602L, + -2601111570856684098L, -8543223759426509417L, -6067343680855748868L, + -2972493582642298180L, -8775337516792518219L, -6357485877563259869L, + -3335171328526686933L, -9002011107970261189L, -6640827866535438582L, + -3689348814741910324L, -9223372036854775808L, -6917529027641081856L, + -4035225266123964416L, -432345564227567616L, -7187745005283311616L, + -4372995238176751616L, -854558029293551616L, -7451627795949551616L, + -4702848726509551616L, -1266874889709551616L, -7709325833709551616L, + -5024971273709551616L, -1669528073709551616L, -7960984073709551616L, + -5339544073709551616L, -2062744073709551616L, -8206744073709551616L, + -5646744073709551616L, -2446744073709551616L, -8446744073709551616L, + -5946744073709551616L, -2821744073709551616L, -8681119073709551616L, + -6239712823709551616L, -3187955011209551616L, -8910000909647051616L, + -6525815118631426616L, -3545582879861895366L, -9133518327554766460L, + -6805211891016070171L, -3894828845342699810L, -256850038250986858L, + -7078060301547948643L, -4235889358507547899L, -683175679707046970L, + -7344513827457986212L, -4568956265895094861L, -1099509313941480672L, + -7604722348854507276L, -4894216917640746191L, -1506085128623544835L, + -7858832233030797378L, -5211854272861108819L, -1903131822648998119L, + -8106986416796705681L, -5522047002568494197L, -2290872734783229842L, + -8349324486880600507L, -5824969590173362730L, -2669525969289315508L, + -8585982758446904049L, -6120792429631242157L, -3039304518611664792L, + -8817094351773372351L, -6409681921289327535L, -3400416383184271515L, + -9042789267131251553L, -6691800565486676537L, -3753064688430957767L, + -79644842111309304L, -6967307053960650171L, -4097447799023424810L, + -510123730351893109L, -7236356359111015049L, -4433759430461380907L, + -930513269649338230L, -7499099821171918250L, -4762188758037509908L, + -1341049929119499481L, -7755685233340769032L, -5082920523248573386L, + -1741964635633328828L, -8006256924911912374L, -5396135137712502563L, + -2133482903713240300L, -8250955842461857044L, -5702008784649933400L, + -2515824962385028846L, -8489919629131724885L, -6000713517987268202L, + -2889205879056697349L, -8723282702051517699L, -6292417359137009220L, + -3253835680493873621L, -8951176327949752869L, -6577284391509803182L, + -3609919470959866074L, -9173728696990998152L, -6855474852811359786L, + -3957657547586811828L, -335385916056126881L, -7127145225176161157L, + -4297245513042813542L, -759870872876129024L, -7392448323188662496L, + -4628874385558440216L, -1174406963520662366L, -7651533379841495835L, + -4952730706374481889L, -1579227364540714458L, -7904546130479028392L, + -5268996644671397586L, -1974559787411859078L, -8151628894773493780L, + -5577850100039479321L, -2360626606621961247L, -8392920656779807636L, + -5879464802547371641L, -2737644984756826647L, -8628557143114098510L, + -6174010410465235234L, -3105826994654156138L, -8858670899299929442L, + -6461652605697523899L, -3465379738694516970L, -9083391364325154962L, + -6742553186979055799L, -3816505465296431844L, -158945813193151901L, + -7016870160886801794L, -4159401682681114339L, -587566084924005019L, + -7284757830718584993L, -4494261269970843337L, -1006140569036166268L, + -7546366883288685774L, -4821272585683469313L, -1414904713676948737L, + -7801844473689174817L, -5140619573684080617L, -1814088448677712867L, + -8051334308064652398L, -5452481866653427593L, -2203916314889396588L, + -8294976724446954723L, -5757034887131305500L, -2584607590486743971L, + -8532908771695296838L, -6054449946191733143L, -2956376414312278525L, + -8765264286586255934L, -6344894339805432014L, -3319431906329402113L, + -8992173969096958177L, -6628531442943809817L, -3673978285252374367L, + -9213765455923815836L, -6905520801477381891L, -4020214983419339459L, + -413582710846786420L, -7176018221920323369L, -4358336758973016307L, + -836234930288882479L, -7440175859071633406L, -4688533805412153853L, + -1248981238337804412L, -7698142301602209614L, -5010991858575374113L, + -1652053804791829737L, -7950062655635975442L, -5325892301117581398L, + -2045679357969588844L, -8196078626372074883L, -5633412264537705700L, + -2430079312244744221L, -8436328597794046994L, -5933724728815170839L, + -2805469892591575644L, -8670947710510816634L, -6226998619711132888L, + -3172062256211528206L, -8900067937773286985L, -6513398903789220827L, + -3530062611309138130L, -9123818159709293187L, -6793086681209228580L, + -3879672333084147821L, -237904397927796872L, -7066219276345954901L, + -4221088077005055722L, -664674077828931749L, -7332950326284164199L, + -4554501889427817345L, -1081441343357383777L, -7593429867239446717L, + -4880101315621920492L, -1488440626100012711L, -7847804418953589800L, + -5198069505264599346L, -1885900863153361279L, -8096217067111932656L, + -5508585315462527915L, -2274045625900771990L, -8338807543829064350L, + -5811823411358942533L, -2653093245771290262L, -8575712306248138270L, + -6107954364382784934L, -3023256937051093263L, -8807064613298015146L, + -6397144748195131028L, -3384744916816525881L, -9032994600651410532L, + -6679557232386875260L, -3737760522056206171L, -60514634142869810L, + -6955350673980375487L, -4082502324048081455L, -491441886632713915L, + -7224680206786528053L, -4419164240055772162L, -912269281642327298L, + -7487697328667536418L, -4747935642407032618L, -1323233534581402868L, + -7744549986754458649L, -5069001465015685407L, -1724565812842218855L, + -7995382660667468640L, -5382542307406947896L, -2116491865831296966L, + -8240336443785642460L, -5688734536304665171L, -2499232151953443560L, + -8479549122611984081L, -5987750384837592197L, -2873001962619602342L, + -8713155254278333320L, -6279758049420528746L, -3238011543348273028L, + -8941286242233752499L, -6564921784364802720L, -3594466212028615495L, + -9164070410158966541L, -6843401994271320272L, -3942566474411762436L, + -316522074587315140L, -7115355324258153819L, -4282508136895304370L, + -741449152691742558L, -7380934748073420955L, -4614482416664388289L, + -1156417002403097458L, -7640289654143017767L, -4938676049251384305L, + -1561659043136842477L, -7893565929601608404L, -5255271393574622601L, + -1957403223540890347L, -8140906042354138323L, -5564446534515285000L, + -2343872149716718346L, -8382449121214030822L, -5866375383090150624L, + -2721283210435300376L, -8618331034163144591L, -6161227774276542835L, + -3089848699418290639L, -8848684464777513506L, -6449169562544503978L, + -3449775934753242068L, -9073638986861858149L, -6730362715149934782L, + -3801267375510030573L, -139898200960150313L, -7004965403241175802L, + -4144520735624081848L, -568964901102714406L, -7273132090830278360L, + -4479729095110460046L, -987975350460687153L, -7535013621679011327L, + -4807081008671376254L, -1397165242411832414L, -7790757304148477115L, + -5126760611758208489L, -1796764746270372707L, -8040506994060064798L, + -5438947724147693094L, -2186998636757228463L, -8284403175614349646L, + -5743817951090549153L, -2568086420435798537L, -8522583040413455942L, + -6041542782089432023L, -2940242459184402125L, -8755180564631333184L, + -6332289687361778576L, -3303676090774835316L, -8982326584375353929L, + -6616222212041804507L, -3658591746624867729L, -9204148869281624187L, + -6893500068174642330L, -4005189066790915008L, -394800315061255856L, + -7164279224554366766L, -4343663012265570553L, -817892746904575288L, + -7428711994456441411L, -4674203974643163860L, -1231068949876566920L, + -7686947121313936181L, -4996997883215032323L, -1634561335591402499L, + -7939129862385708418L, -5312226309554747619L, -2028596868516046619L, + -8185402070463610993L, -5620066569652125837L + ) + /* Use the following code to generate `nibbles` in Scala REPL: + val ns = new Array[Byte](256) + java.util.Arrays.fill(ns, -1: Byte) + ns('0') = 0 + ns('1') = 1 + ns('2') = 2 + ns('3') = 3 + ns('4') = 4 + ns('5') = 5 + ns('6') = 6 + ns('7') = 7 + ns('8') = 8 + ns('9') = 9 + ns('A') = 10 + ns('B') = 11 + ns('C') = 12 + ns('D') = 13 + ns('E') = 14 + ns('F') = 15 + ns('a') = 10 + ns('b') = 11 + ns('c') = 12 + ns('d') = 13 + ns('e') = 14 + ns('f') = 15 + ns.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val nibbles: Array[Byte] = Array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, + -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1 + ) + /* Use the following code to generate `base64Bytes` in Scala REPL: + val bs = new Array[Byte](256) + java.util.Arrays.fill(bs, -1: Byte) + val ds = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + var i = 0 + while (i < ds.length) { + bs(ds.charAt(i).toInt) = i.toByte + i += 1 + } + bs.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val base64Bytes: Array[Byte] = Array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1 + ) + /* Use the following code to generate `base64UrlBytes` in Scala REPL: + val bs = new Array[Byte](256) + java.util.Arrays.fill(bs, -1: Byte) + val ds = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + var i = 0 + while (i < ds.length) { + bs(ds.charAt(i).toInt) = i.toByte + i += 1 + } + bs.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val base64UrlBytes: Array[Byte] = Array( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1 + ) + private final val zoneOffsets: Array[ZoneOffset] = new Array(145) + private final val zoneIds: ConcurrentHashMap[Key, ZoneId] = + new ConcurrentHashMap(256) + private final val hexDigits: Array[Char] = + Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f') + /* Use the following code to generate `dumpBorder` in Scala REPL: + "\n+----------+-------------------------------------------------+------------------+".toCharArray + .grouped(16).map(_.map(_.toInt).mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val dumpBorder: Array[Char] = Array( + 10, 43, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 43, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 43, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 43 + ) + /* Use the following code to generate `dumpHeader` in Scala REPL: + "\n| | 0 1 2 3 4 5 6 7 8 9 a b c d e f | 0123456789abcdef |".toCharArray + .grouped(16).map(_.map(_.toInt).mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val dumpHeader: Array[Char] = Array( + 10, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 48, 32, 32, + 49, 32, 32, 50, 32, 32, 51, 32, 32, 52, 32, 32, 53, 32, 32, 54, 32, 32, 55, + 32, 32, 56, 32, 32, 57, 32, 32, 97, 32, 32, 98, 32, 32, 99, 32, 32, 100, 32, + 32, 101, 32, 32, 102, 32, 124, 32, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 97, 98, 99, 100, 101, 102, 32, 124 + ) + + /** The default math context used for rounding of `BigDecimal` values when + * parsing. + */ + final val bigDecimalMathContext: MathContext = MathContext.DECIMAL128 + + /** The default limit for number of decimal digits in mantissa of parsed + * `BigDecimal` values. + */ + final val bigDecimalDigitsLimit: Int = 308 + + /** The default limit for scale of parsed `BigDecimal` values. + */ + final val bigDecimalScaleLimit: Int = 6178 + + /** The maximum number of digits in `BigInt` values. + */ + final val bigIntDigitsLimit: Int = 308 + + /** Calculates hash code value string represented by sequence of characters + * from beginning of the provided char array up to limit position. + * + * @param cs + * a char array + * @param len + * an exclusive limit + * @return + * a hash code value + * @throws java.lang.NullPointerException + * if the `cs` is null + * @throws java.lang.ArrayIndexOutOfBoundsException + * if the length of `cs` is less than the provided `len` + */ + final def toHashCode(cs: Array[Char], len: Int): Int = { + var h, i = 0 + while (i < len) { + h = (h << 5) + (cs(i) - h) + i += 1 + } + h + } +} + +private class Key { + private[this] var hash: Int = _ + private[this] var bs: Array[Byte] = _ + private[this] var from: Int = _ + private[this] var to: Int = _ + + def set(hash: Int, bs: Array[Byte], from: Int, to: Int): Unit = { + this.hash = hash + this.bs = bs + this.from = from + this.to = to + } + + def copy: Key = { + val len = to - from + val bs1 = new Array[Byte](len) + System.arraycopy(bs, from, bs1, 0, len) + val k = new Key + k.set(hash, bs1, 0, len) + k + } + + override def hashCode: Int = hash + + override def equals(obj: Any): Boolean = { + val k = obj.asInstanceOf[Key] + val off = from + val koff = k.fromIndex + val len = to - off + k.toIndex - koff == len && { + val bs = this.bs + val kbs = k.bytes + var i = 0 + while (i < len && kbs(koff + i) == bs(off + i)) i += 1 + i == len + } + } + + override def toString: String = new String(bs, 0, from, to - from) + + private def bytes: Array[Byte] = bs + + private def fromIndex: Int = from + + private def toIndex: Int = to +} +*/ \ No newline at end of file diff --git a/modules/core/src/main/scala/tethys/readers/tokens/JsonReaderConfig.scala b/modules/core/src/main/scala/tethys/readers/tokens/JsonReaderConfig.scala new file mode 100644 index 00000000..447cfbe4 --- /dev/null +++ b/modules/core/src/main/scala/tethys/readers/tokens/JsonReaderConfig.scala @@ -0,0 +1,164 @@ +package tethys.readers.tokens + + +/** Configuration for [[com.github.plokhotnyuk.jsoniter_scala.core.JsonReader]] + * that contains flags for tuning of parsing exceptions and preferred sizes for + * internal buffers that are created on the reader instantiation and reused in + * runtime for parsing of messages.
All configuration params already + * initialized by recommended default values, but in some cases they should be + * altered for performance reasons: + * @param throwReaderExceptionWithStackTrace + * a flag that allows to turn on a stack traces for debugging purposes in + * development + * @param appendHexDumpToParseException + * a flag that allows to turn off hex dumping of affected by error part of an + * internal byte buffer + * @param maxBufSize + * a max size (in bytes) of an internal byte buffer when parsing from + * [[java.io.InputStream]] or [[java.nio.DirectByteBuffer]] + * @param maxCharBufSize + * a max size (in chars) of an internal char buffer for parsing of string + * values including those one which use Base16 or Base64 encodings + * @param preferredBufSize + * a preferred size (in bytes) of an internal byte buffer when parsing from + * [[java.io.InputStream]] or [[java.nio.DirectByteBuffer]] + * @param preferredCharBufSize + * a preferred size (in chars) of an internal char buffer for parsing of + * string values + * @param checkForEndOfInput + * a flag to check and raise an error if some non whitespace bytes will be + * detected after successful parsing of the value + * @param hexDumpSize + * a size of the hex dump in 16-byte lines before and after the 16-byte line + * where an error occurs + */ +class ReaderConfig private ( + val preferredBufSize: Int, + val preferredCharBufSize: Int, + val maxBufSize: Int, + val maxCharBufSize: Int, + val checkForEndOfInput: Boolean, + val throwReaderExceptionWithStackTrace: Boolean, + val appendHexDumpToParseException: Boolean, + val hexDumpSize: Int +) extends Serializable { + def withThrowReaderExceptionWithStackTrace( + throwReaderExceptionWithStackTrace: Boolean + ): ReaderConfig = + copy(throwReaderExceptionWithStackTrace = + throwReaderExceptionWithStackTrace + ) + + def withAppendHexDumpToParseException( + appendHexDumpToParseException: Boolean + ): ReaderConfig = + copy(appendHexDumpToParseException = appendHexDumpToParseException) + + def withMaxBufSize(maxBufSize: Int): ReaderConfig = { + if (maxBufSize < preferredBufSize) + throw new IllegalArgumentException( + "'maxBufSize' should be not less than 'preferredBufSize'" + ) + if (maxBufSize > 2147483645) + throw new IllegalArgumentException( + "'maxBufSize' should be not greater than 2147483645" + ) + copy(maxBufSize = maxBufSize) + } + + def withMaxCharBufSize(maxCharBufSize: Int): ReaderConfig = { + if (maxCharBufSize < preferredCharBufSize) + throw new IllegalArgumentException( + "'maxCharBufSize' should be not less than 'preferredCharBufSize'" + ) + if (maxCharBufSize > 2147483645) + throw new IllegalArgumentException( + "'maxCharBufSize' should be not greater than 2147483645" + ) + copy(maxCharBufSize = maxCharBufSize) + } + + def withPreferredBufSize(preferredBufSize: Int): ReaderConfig = { + if (preferredBufSize < 12) + throw new IllegalArgumentException( + "'preferredBufSize' should be not less than 12" + ) + if (preferredBufSize > maxBufSize) + throw new IllegalArgumentException( + "'preferredBufSize' should be not greater than 'maxBufSize'" + ) + copy(preferredBufSize = preferredBufSize) + } + + def withPreferredCharBufSize(preferredCharBufSize: Int): ReaderConfig = { + if (preferredCharBufSize < 0) + throw new IllegalArgumentException( + "'preferredCharBufSize' should be not less than 0" + ) + if (preferredCharBufSize > maxCharBufSize) + throw new IllegalArgumentException( + "'preferredCharBufSize' should be not greater than 'maxCharBufSize'" + ) + copy(preferredCharBufSize = preferredCharBufSize) + } + + def withCheckForEndOfInput(checkForEndOfInput: Boolean): ReaderConfig = + copy(checkForEndOfInput = checkForEndOfInput) + + def withHexDumpSize(hexDumpSize: Int): ReaderConfig = { + if (hexDumpSize < 1) + throw new IllegalArgumentException( + "'hexDumpSize' should be not less than 1" + ) + copy(hexDumpSize = hexDumpSize) + } + + private[this] def copy( + preferredBufSize: Int = preferredBufSize, + preferredCharBufSize: Int = preferredCharBufSize, + maxBufSize: Int = maxBufSize, + maxCharBufSize: Int = maxCharBufSize, + checkForEndOfInput: Boolean = checkForEndOfInput, + throwReaderExceptionWithStackTrace: Boolean = + throwReaderExceptionWithStackTrace, + appendHexDumpToParseException: Boolean = appendHexDumpToParseException, + hexDumpSize: Int = hexDumpSize + ): ReaderConfig = + new ReaderConfig( + throwReaderExceptionWithStackTrace = throwReaderExceptionWithStackTrace, + appendHexDumpToParseException = appendHexDumpToParseException, + maxBufSize = maxBufSize, + maxCharBufSize = maxCharBufSize, + preferredBufSize = preferredBufSize, + preferredCharBufSize = preferredCharBufSize, + checkForEndOfInput = checkForEndOfInput, + hexDumpSize = hexDumpSize + ) +} + +object ReaderConfig { + val default: ReaderConfig = ReaderConfig( + preferredBufSize = 32768, + preferredCharBufSize = 4096, + maxBufSize = 33554432, + maxCharBufSize = 4194304, + throwReaderExceptionWithStackTrace = false, + appendHexDumpToParseException = true, + checkForEndOfInput = true, + hexDumpSize = 2 + ) +} diff --git a/modules/core/src/main/scala/tethys/readers/tokens/JsonReaderException.scala b/modules/core/src/main/scala/tethys/readers/tokens/JsonReaderException.scala new file mode 100644 index 00000000..e71ebd31 --- /dev/null +++ b/modules/core/src/main/scala/tethys/readers/tokens/JsonReaderException.scala @@ -0,0 +1,4 @@ +package tethys.readers.tokens + +class JsonReaderException private[tokens](msg: String, cause: Throwable, withStackTrace: Boolean) + extends RuntimeException(msg, cause, true, withStackTrace) \ No newline at end of file diff --git a/modules/core/src/main/scala/tethys/readers/tokens/TokenIterator.scala b/modules/core/src/main/scala/tethys/readers/tokens/TokenIterator.scala index 6c7448ce..c6617aed 100644 --- a/modules/core/src/main/scala/tethys/readers/tokens/TokenIterator.scala +++ b/modules/core/src/main/scala/tethys/readers/tokens/TokenIterator.scala @@ -34,7 +34,6 @@ trait TokenIterator { def skipExpression(): this.type def collectExpression(): TokenIterator with CopySupport - } object TokenIterator { diff --git a/modules/core/src/main/scala/tethys/readers/tokens/TokenIteratorProducer.scala b/modules/core/src/main/scala/tethys/readers/tokens/TokenIteratorProducer.scala index 6f5b690b..237328ad 100644 --- a/modules/core/src/main/scala/tethys/readers/tokens/TokenIteratorProducer.scala +++ b/modules/core/src/main/scala/tethys/readers/tokens/TokenIteratorProducer.scala @@ -1,9 +1,31 @@ package tethys.readers.tokens -import java.io.Reader - +import java.io.{Reader, StringWriter} import tethys.readers.ReaderError trait TokenIteratorProducer { - def fromReader(reader: Reader): Either[ReaderError, TokenIterator] + def produce(json: String): Either[ReaderError, TokenIterator] + + def fromReader(reader: Reader): Either[ReaderError, TokenIterator] = { + val writer = new StringWriter() + reader.transferTo(writer) + val json = writer.toString + reader.close() + writer.close() + produce(json) + } +} + +object TokenIteratorProducer { + implicit val tethysTokenIteratorProducer: TokenIteratorProducer = + new TokenIteratorProducer { + private val readerPool: ThreadLocal[DefaultTokenIterator] = + new ThreadLocal[DefaultTokenIterator] { + override def initialValue(): DefaultTokenIterator = + new DefaultTokenIterator(config = ReaderConfig.default) + } + + override def produce(json: String): Either[ReaderError, TokenIterator] = + Right(readerPool.get.init(json)) + } } diff --git a/modules/core/src/main/scala/tethys/writers/KeyWriter.scala b/modules/core/src/main/scala/tethys/writers/KeyWriter.scala index c624e110..17a58752 100644 --- a/modules/core/src/main/scala/tethys/writers/KeyWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/KeyWriter.scala @@ -6,26 +6,25 @@ trait KeyWriter[A] { } object KeyWriter { - implicit lazy val stringKeyWriter: KeyWriter[String] = identity + implicit val stringKeyWriter: KeyWriter[String] = identity - implicit lazy val uuidKeyWriter: KeyWriter[java.util.UUID] = _.toString + implicit val uuidKeyWriter: KeyWriter[java.util.UUID] = _.toString - implicit lazy val intKeyWriter: KeyWriter[Int] = _.toString + implicit val intKeyWriter: KeyWriter[Int] = _.toString - implicit lazy val longKeyWriter: KeyWriter[Long] = _.toString + implicit val longKeyWriter: KeyWriter[Long] = _.toString - implicit lazy val instantKeyWriter: KeyWriter[java.time.Instant] = _.toString + implicit val instantKeyWriter: KeyWriter[java.time.Instant] = _.toString - implicit lazy val localDateKeyWriter: KeyWriter[java.time.LocalDate] = + implicit val localDateKeyWriter: KeyWriter[java.time.LocalDate] = _.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) - implicit lazy val localDateTimeKeyWriter: KeyWriter[java.time.LocalDateTime] = + implicit val localDateTimeKeyWriter: KeyWriter[java.time.LocalDateTime] = _.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) - implicit lazy val offsetDateTimeKeyWriter - : KeyWriter[java.time.OffsetDateTime] = + implicit val offsetDateTimeKeyWriter: KeyWriter[java.time.OffsetDateTime] = _.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) - implicit lazy val zonedDateTimeKeyWriter: KeyWriter[java.time.ZonedDateTime] = + implicit val zonedDateTimeKeyWriter: KeyWriter[java.time.ZonedDateTime] = _.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME) } diff --git a/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala b/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala index 1ceb1704..422b8914 100644 --- a/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala +++ b/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala @@ -4,64 +4,64 @@ import tethys.JsonWriter import tethys.writers.tokens.TokenWriter trait AllJsonWriters extends OptionWriters with EitherWriters { - implicit lazy val intWriter: JsonWriter[Int] = new JsonWriter[Int] { + implicit val intWriter: JsonWriter[Int] = new JsonWriter[Int] { override def write(value: Int, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val longWriter: JsonWriter[Long] = new JsonWriter[Long] { + implicit val longWriter: JsonWriter[Long] = new JsonWriter[Long] { override def write(value: Long, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val byteWriter: JsonWriter[Byte] = new JsonWriter[Byte] { + implicit val byteWriter: JsonWriter[Byte] = new JsonWriter[Byte] { override def write(value: Byte, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val shortWriter: JsonWriter[Short] = new JsonWriter[Short] { + implicit val shortWriter: JsonWriter[Short] = new JsonWriter[Short] { override def write(value: Short, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val doubleWriter: JsonWriter[Double] = new JsonWriter[Double] { + implicit val doubleWriter: JsonWriter[Double] = new JsonWriter[Double] { override def write(value: Double, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val floatWriter: JsonWriter[Float] = new JsonWriter[Float] { + implicit val floatWriter: JsonWriter[Float] = new JsonWriter[Float] { override def write(value: Float, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val bigDecimalWriter: JsonWriter[BigDecimal] = + implicit val bigDecimalWriter: JsonWriter[BigDecimal] = new JsonWriter[BigDecimal] { override def write(value: BigDecimal, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val bigIntWriter: JsonWriter[BigInt] = new JsonWriter[BigInt] { + implicit val bigIntWriter: JsonWriter[BigInt] = new JsonWriter[BigInt] { override def write(value: BigInt, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val booleanWriter: JsonWriter[Boolean] = + implicit val booleanWriter: JsonWriter[Boolean] = new JsonWriter[Boolean] { override def write(value: Boolean, tokenWriter: TokenWriter): Unit = tokenWriter.writeBoolean(value) } - implicit lazy val stringWriter: JsonWriter[String] = new JsonWriter[String] { + implicit val stringWriter: JsonWriter[String] = new JsonWriter[String] { override def write(value: String, tokenWriter: TokenWriter): Unit = tokenWriter.writeString(value) } - implicit lazy val charWriter: JsonWriter[Char] = new JsonWriter[Char] { + implicit val charWriter: JsonWriter[Char] = new JsonWriter[Char] { override def write(value: Char, tokenWriter: TokenWriter): Unit = tokenWriter.writeString(value.toString) } - implicit lazy val javaIntWriter: JsonWriter[java.lang.Integer] = + implicit val javaIntWriter: JsonWriter[java.lang.Integer] = new JsonWriter[java.lang.Integer] { override def write( value: java.lang.Integer, @@ -69,7 +69,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaLongWriter: JsonWriter[java.lang.Long] = + implicit val javaLongWriter: JsonWriter[java.lang.Long] = new JsonWriter[java.lang.Long] { override def write( value: java.lang.Long, @@ -77,7 +77,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaByteWriter: JsonWriter[java.lang.Byte] = + implicit val javaByteWriter: JsonWriter[java.lang.Byte] = new JsonWriter[java.lang.Byte] { override def write( value: java.lang.Byte, @@ -85,7 +85,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaShortWriter: JsonWriter[java.lang.Short] = + implicit val javaShortWriter: JsonWriter[java.lang.Short] = new JsonWriter[java.lang.Short] { override def write( value: java.lang.Short, @@ -93,7 +93,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaDoubleWriter: JsonWriter[java.lang.Double] = + implicit val javaDoubleWriter: JsonWriter[java.lang.Double] = new JsonWriter[java.lang.Double] { override def write( value: java.lang.Double, @@ -101,7 +101,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaFloatWriter: JsonWriter[java.lang.Float] = + implicit val javaFloatWriter: JsonWriter[java.lang.Float] = new JsonWriter[java.lang.Float] { override def write( value: java.lang.Float, @@ -109,7 +109,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaBigDecimalWriter: JsonWriter[java.math.BigDecimal] = + implicit val javaBigDecimalWriter: JsonWriter[java.math.BigDecimal] = new JsonWriter[java.math.BigDecimal] { override def write( value: java.math.BigDecimal, @@ -117,7 +117,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaBigIntegerWriter: JsonWriter[java.math.BigInteger] = + implicit val javaBigIntegerWriter: JsonWriter[java.math.BigInteger] = new JsonWriter[java.math.BigInteger] { override def write( value: java.math.BigInteger, @@ -125,7 +125,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaBooleanWriter: JsonWriter[java.lang.Boolean] = + implicit val javaBooleanWriter: JsonWriter[java.lang.Boolean] = new JsonWriter[java.lang.Boolean] { override def write( value: java.lang.Boolean, @@ -133,7 +133,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeBoolean(value) } - implicit lazy val uuidWriter: JsonWriter[java.util.UUID] = + implicit val uuidWriter: JsonWriter[java.util.UUID] = new JsonWriter[java.util.UUID] { override def write( value: java.util.UUID, @@ -141,12 +141,12 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeString(value.toString) } - implicit lazy val nullWriter: JsonWriter[Null] = new JsonWriter[Null] { + implicit val nullWriter: JsonWriter[Null] = new JsonWriter[Null] { override def write(value: Null, tokenWriter: TokenWriter): Unit = tokenWriter.writeNull() } - implicit lazy val instantWriter: JsonWriter[java.time.Instant] = + implicit val instantWriter: JsonWriter[java.time.Instant] = new JsonWriter[java.time.Instant] { override def write( value: java.time.Instant, @@ -155,7 +155,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { tokenWriter.writeString(value.toString) } - implicit lazy val localDateWriter: JsonWriter[java.time.LocalDate] = + implicit val localDateWriter: JsonWriter[java.time.LocalDate] = new JsonWriter[java.time.LocalDate] { override def write( value: java.time.LocalDate, diff --git a/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala b/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala index 82f0491e..cfb81845 100644 --- a/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala +++ b/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala @@ -8,22 +8,14 @@ import scala.language.higherKinds private[tethys] trait IterableWriters extends LowPriorityJsonWriters { final implicit def iterableWriter[A, C[X] <: Iterable[X]](implicit valueWriter: JsonWriter[A] - ): JsonWriter[C[A]] = new IterableWriter[A, C](valueWriter) { - override def iterator(c: C[A]): Iterator[A] = c.iterator - } - - abstract class IterableWriter[A, C[_]](valueWriter: JsonWriter[A]) - extends JsonWriter[C[A]] { - def iterator(c: C[A]): Iterator[A] - + ): JsonWriter[C[A]] = new JsonWriter[C[A]] { override def write(value: C[A], tokenWriter: TokenWriter): Unit = { tokenWriter.writeArrayStart() - val valueIterator = iterator(value) - while (valueIterator.hasNext) { - val v = valueIterator.next() - valueWriter.write(v, tokenWriter) - } + val valueIterator = value.iterator + + while (valueIterator.hasNext) + valueWriter.write(valueIterator.next(), tokenWriter) tokenWriter.writeArrayEnd() } diff --git a/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala b/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala index 3047316e..31911059 100644 --- a/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala +++ b/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala @@ -4,7 +4,7 @@ import tethys.JsonWriter import tethys.writers.tokens.TokenWriter private[tethys] trait OptionWriters extends MapWriters { - implicit lazy val noneWriter: JsonWriter[None.type] = + implicit val noneWriter: JsonWriter[None.type] = new JsonWriter[None.type] { override def write( name: String, diff --git a/modules/core/src/main/scala/tethys/writers/tokens/ByteArrayAccess.java b/modules/core/src/main/scala/tethys/writers/tokens/ByteArrayAccess.java new file mode 100644 index 00000000..ced6b066 --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/ByteArrayAccess.java @@ -0,0 +1,46 @@ +package tethys.writers.tokens; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + +class ByteArrayAccess { // FIXME: Use Java wrapper as w/a for missing support of @PolymorphicSignature methods in Scala 3, see: https://github.com/lampepfl/dotty/issues/11332 + private static final VarHandle VH_LONG = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_INT = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_SHORT = + MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_LONG_REVERSED = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle VH_INT_REVERSED = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + static void setLong(byte[] buf, int pos, long value) { + VH_LONG.set(buf, pos, value); + } + + static long getLong(byte[] buf, int pos) { + return (long) VH_LONG.get(buf, pos); + } + + static void setInt(byte[] buf, int pos, int value) { + VH_INT.set(buf, pos, value); + } + + static int getInt(byte[] buf, int pos) { + return (int) VH_INT.get(buf, pos); + } + + static void setShort(byte[] buf, int pos, short value) { + VH_SHORT.set(buf, pos, value); + } + + static void setLongReversed(byte[] buf, int pos, long value) { + VH_LONG_REVERSED.set(buf, pos, value); + } + + static int getIntReversed(byte[] buf, int pos) { + return (int) VH_INT_REVERSED.get(buf, pos); + } +} diff --git a/modules/core/src/main/scala/tethys/writers/tokens/DefaultTokenWriter.scala b/modules/core/src/main/scala/tethys/writers/tokens/DefaultTokenWriter.scala new file mode 100644 index 00000000..db5fc40e --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/DefaultTokenWriter.scala @@ -0,0 +1,1936 @@ +/* + This started as a copy of jsoniter-scala JsonWriter + https://github.com/plokhotnyuk/jsoniter-scala/blob/master/jsoniter-scala-core/jvm/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/JsonWriter.scala + MIT License + + Copyright (c) 2017 Andriy Plokhotnyuk, and respective contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +package tethys.writers.tokens + +import java.io.OutputStream +import java.math.BigInteger +import java.nio.ByteBuffer +import java.nio.charset.{Charset, StandardCharsets} +import java.time._ +import scala.annotation.tailrec + +/** A writer for iterative serialization of JSON keys and values. + * + * @param buf + * an internal buffer for writing JSON data + * @param count + * the current position in the internal buffer + * @param limit + * the last position in the internal buffer + * @param indention + * the current indention level + * @param comma + * a flag indicating if the next element should be preceded by comma + * @param disableBufGrowing + * a flag indicating if growing of the internal buffer is disabled + * @param bbuf + * a byte buffer for writing JSON data + * @param out + * the output stream for writing JSON data + * @param config + * a writer configuration + */ +final class DefaultTokenWriter( + private[this] var buf: Array[Byte] = new Array[Byte](32768), + private[this] var count: Int = 0, + private[this] var limit: Int = 32768, + private[this] var indention: Int = 0, + private[this] var comma: Boolean = false, + private[this] var disableBufGrowing: Boolean = false, + private[this] var bbuf: ByteBuffer = null, + private[this] var out: OutputStream = null, + private[this] var config: TokenWriterConfig = null +) extends TokenWriter { + import DefaultTokenWriter._ + + def withConfig(newConfig: TokenWriterConfig): DefaultTokenWriter = { + config = newConfig + if (newConfig.preferredBufSize == buf.size) () + else if (newConfig.preferredBufSize > buf.size) + growBuf(newConfig.preferredBufSize) + else reallocateBufToPreferredSize() + this + } + + override def flush(): Unit = () + + override def close(): Unit = () + + override def writeFieldName(x: String): DefaultTokenWriter.this.type = { + val indention = this.indention + var pos = ensureBufCapacity(indention + 10) + val buf = this.buf + if (comma) { + comma = false + buf(pos) = ',' + pos += 1 + if (indention != 0) pos = writeIndention(buf, pos, indention) + } + buf(pos) = '"' + pos += 1 + pos = writeString(x, 0, pos, buf, Math.min(x.length, limit - pos - 1) + pos) + if (pos + 4 >= limit) pos = flushAndGrowBuf(4, pos) + ByteArrayAccess.setInt(this.buf, pos, 0x203a22) + if (indention > 0) pos += 1 + count = pos + 2 + this + } + + /** Throws a [[TokenWriterException]] with the given error message. */ + private def encodeError(msg: String): Nothing = + throw new TokenWriterException(msg, null) + + override def writeNumber(x: BigDecimal): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeBigDecimal(x.bigDecimal) + this + } + + override def writeNumber(x: BigInt): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + if (x.isValidLong) writeLong(x.longValue) + else writeBigInteger(x.bigInteger, null) + this + } + + override def writeString(x: String): DefaultTokenWriter.this.type = { + val indention = this.indention + var pos = ensureBufCapacity(indention + 10) + val buf = this.buf + if (comma) { + buf(pos) = ',' + pos += 1 + if (indention != 0) pos = writeIndention(buf, pos, indention) + } else comma = true + buf(pos) = '"' + pos += 1 + pos = writeString(x, 0, pos, buf, Math.min(x.length, limit - pos - 1) + pos) + this.buf(pos) = '"' + count = pos + 1 + this + } + + override def writeBoolean(x: Boolean): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeBooleanInternal(x) + this + } + + override def writeNumber(x: Byte): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeByte(x) + this + } + + override def writeNumber(x: Short): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeShort(x) + this + } + + override def writeNumber(x: Int): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeInt(x) + this + } + + override def writeNumber(x: Long): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeLong(x) + this + } + + override def writeNumber(x: Float): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeFloat(x) + this + } + + override def writeNumber(x: Double): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeDouble(x) + this + } + + override def writeRawJson(json: String): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeRawBytes(json.getBytes(StandardCharsets.UTF_8)) + this + } + + override def writeNull(): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + val pos = ensureBufCapacity(4) + ByteArrayAccess.setInt(buf, pos, 0x6c6c756e) + count = pos + 4 + this + } + + /** Writes a JSON array start marker (`[`). + */ + override def writeArrayStart(): DefaultTokenWriter.this.type = { + writeNestedStart('[') + this + } + + /** Writes a JSON array end marker (`]`). + */ + override def writeArrayEnd(): DefaultTokenWriter.this.type = { + writeNestedEnd(']') + this + } + + /** Writes a JSON array start marker (`{`). + */ + override def writeObjectStart(): DefaultTokenWriter.this.type = { + writeNestedStart('{') + this + } + + override def writeObjectEnd(): DefaultTokenWriter.this.type = { + writeNestedEnd('}') + this + } + + def result(): String = + try { + val res = new String(buf, 0, count, StandardCharsets.UTF_8) + comma = false + count = 0 + indention = 0 + disableBufGrowing = false + res + } finally { + if (limit > config.preferredBufSize) reallocateBufToPreferredSize() + } + + private[this] def writeNestedStart(b: Byte): Unit = { + writeOptionalCommaAndIndentionBeforeKey() + writeBytes(b) + val indentionStep = config.indentionStep + if (indentionStep != 0) { + indention += indentionStep + writeIndention() + } + } + + private[this] def writeNestedEnd(b: Byte): Unit = { + comma = true + if (indention != 0) { + indention -= config.indentionStep + writeIndention() + } + writeBytes(b) + } + + private[this] def writeOptionalCommaAndIndentionBeforeValue(): Unit = + if (comma) { + writeBytes(',') + if (indention != 0) writeIndention() + } else comma = true + + private[this] def writeOptionalCommaAndIndentionBeforeKey(): Unit = + if (comma) { + comma = false + writeBytes(',') + if (indention != 0) writeIndention() + } + + private[this] def writeIndention(): Unit = { + val n = indention + val pos = ensureBufCapacity(n + 8) + count = writeIndention(buf, pos, n) + } + + private[this] def writeIndention(buf: Array[Byte], p: Int, n: Int): Int = { + var pos = p + buf(pos) = '\n' + pos += 1 + val posLim = pos + n + while (pos < posLim) { + ByteArrayAccess.setLong(buf, pos, 0x2020202020202020L) + pos += 8 + } + posLim + } + + private[this] def writeParenthesesWithColon(): Unit = { + var pos = ensureBufCapacity(4) // 4 == size of Int in bytes + ByteArrayAccess.setInt(buf, pos, 0x203a22) + if (indention > 0) pos += 1 + count = pos + 2 + } + + private[this] def writeColon(): Unit = { + var pos = ensureBufCapacity(2) + ByteArrayAccess.setShort(buf, pos, 0x203a) + if (indention > 0) pos += 1 + count = pos + 1 + } + + private[this] def writeBytes(b: Byte): Unit = { + var pos = count + if (pos >= limit) pos = flushAndGrowBuf(1, pos) + buf(pos) = b + count = pos + 1 + } + + private[this] def writeRawBytes(bs: Array[Byte]): Unit = { + var pos = count + var step = Math.max(config.preferredBufSize, limit - pos) + var remaining = bs.length + var offset = 0 + while (remaining > 0) { + step = Math.min(step, remaining) + if (pos + step > limit) pos = flushAndGrowBuf(step, pos) + System.arraycopy(bs, offset, buf, pos, step) + offset += step + pos += step + remaining -= step + } + count = pos + } + + @tailrec + private[this] def writeString( + s: String, + from: Int, + pos: Int, + buf: Array[Byte], + minLim: Int + ): Int = + if (pos < minLim) { + val ch = s.charAt(from).toInt + buf(pos) = ch.toByte + if (ch >= 0x20 && ch < 0x7f && ch != 0x22 && ch != 0x5c) + writeString(s, from + 1, pos + 1, buf, minLim) + else writeEscapedOrEncodedString(s, from, pos) + } else if (s.length == from) pos + else { + val newPos = flushAndGrowBuf(2, pos) + writeString( + s, + from, + newPos, + this.buf, + Math.min(s.length - from, limit - newPos - 1) + newPos + ) + } + + private[this] def writeEscapedOrEncodedString( + s: String, + from: Int, + pos: Int + ): Int = + if (config.escapeUnicode) + writeEscapedString( + s, + from, + s.length, + pos, + limit - 13, + escapedChars, + lowerCaseHexDigits + ) + else writeEncodedString(s, from, s.length, pos, limit - 7, escapedChars) + + @tailrec + private[this] def writeEncodedString( + s: String, + from: Int, + to: Int, + pos: Int, + posLim: Int, + escapedChars: Array[Byte] + ): Int = + if (from >= to) pos + else if (pos >= posLim) + writeEncodedString( + s, + from, + to, + flushAndGrowBuf(7, pos), + limit - 6, + escapedChars + ) + else { + val ch1 = s.charAt(from).toInt + if (ch1 < 0x80) { + val esc = escapedChars(ch1) + if (esc == 0) { // 000000000aaaaaaa (UTF-16 char) -> 0aaaaaaa (UTF-8 byte) + buf(pos) = ch1.toByte + writeEncodedString(s, from + 1, to, pos + 1, posLim, escapedChars) + } else if (esc > 0) { + ByteArrayAccess.setShort(buf, pos, (esc << 8 | 0x5c).toShort) + writeEncodedString(s, from + 1, to, pos + 2, posLim, escapedChars) + } else + writeEncodedString( + s, + from + 1, + to, + writeEscapedUnicode(ch1.toByte, pos, buf, lowerCaseHexDigits), + posLim, + escapedChars + ) + } else if (ch1 < 0x800) { // 00000bbbbbaaaaaa (UTF-16 char) -> 110bbbbb 10aaaaaa (UTF-8 bytes) + ByteArrayAccess.setShort( + buf, + pos, + (ch1 >> 6 | (ch1 << 8 & 0x3f00) | 0x80c0).toShort + ) + writeEncodedString(s, from + 1, to, pos + 2, posLim, escapedChars) + } else if ((ch1 & 0xf800) != 0xd800) { // ccccbbbbbbaaaaaa (UTF-16 char) -> 1110cccc 10bbbbbb 10aaaaaa (UTF-8 bytes) + ByteArrayAccess.setInt( + buf, + pos, + ch1 >> 12 | (ch1 << 2 & 0x3f00) | (ch1 << 16 & 0x3f0000) | 0x8080e0 + ) + writeEncodedString(s, from + 1, to, pos + 3, posLim, escapedChars) + } else { // 110110uuuuccccbb 110111bbbbaaaaaa (UTF-16 chars) -> 11110ddd 10ddcccc 10bbbbbb 10aaaaaa (UTF-8 bytes), where ddddd = uuuu + 1 + var ch2 = 0 + if ( + ch1 >= 0xdc00 || from + 1 >= to || { + ch2 = s.charAt(from + 1).toInt + (ch2 & 0xfc00) != 0xdc00 + } + ) illegalSurrogateError() + val cp = + (ch1 << 10) + (ch2 - 56613888) // -56613888 == 0x10000 - (0xD800 << 10) - 0xDC00 + ByteArrayAccess.setInt( + buf, + pos, + cp >> 18 | (cp >> 4 & 0x3f00) | (cp << 10 & 0x3f0000) | (cp << 24 & 0x3f000000) | 0x808080f0 + ) + writeEncodedString(s, from + 2, to, pos + 4, posLim, escapedChars) + } + } + + @tailrec + private[this] def writeEscapedString( + s: String, + from: Int, + to: Int, + pos: Int, + posLim: Int, + escapedChars: Array[Byte], + ds: Array[Short] + ): Int = + if (from >= to) pos + else if (pos >= posLim) + writeEscapedString( + s, + from, + to, + flushAndGrowBuf(13, pos), + limit - 12, + escapedChars, + ds + ) + else { + val ch1 = s.charAt(from).toInt + if (ch1 < 0x80) { + val esc = escapedChars(ch1) + if (esc == 0) { + buf(pos) = ch1.toByte + writeEscapedString(s, from + 1, to, pos + 1, posLim, escapedChars, ds) + } else if (esc > 0) { + ByteArrayAccess.setShort(buf, pos, (esc << 8 | 0x5c).toShort) + writeEscapedString(s, from + 1, to, pos + 2, posLim, escapedChars, ds) + } else + writeEscapedString( + s, + from + 1, + to, + writeEscapedUnicode(ch1.toByte, pos, buf, ds), + posLim, + escapedChars, + ds + ) + } else if ((ch1 & 0xf800) != 0xd800) { + writeEscapedString( + s, + from + 1, + to, + writeEscapedUnicode(ch1, pos, buf, ds), + posLim, + escapedChars, + ds + ) + } else { + var ch2 = 0 + if ( + ch1 >= 0xdc00 || from + 1 >= to || { + ch2 = s.charAt(from + 1).toInt + (ch2 & 0xfc00) != 0xdc00 + } + ) illegalSurrogateError() + writeEscapedString( + s, + from + 2, + to, + writeEscapedUnicode( + ch2, + writeEscapedUnicode(ch1, pos, buf, ds), + buf, + ds + ), + posLim, + escapedChars, + ds + ) + } + } + + private[this] def writeEscapedUnicode( + ch: Int, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + ByteArrayAccess.setShort(buf, pos, 0x755c) + val d1 = ds(ch >> 8) + val d2 = ds(ch & 0xff) << 16 + ByteArrayAccess.setInt(buf, pos + 2, d1 | d2) + pos + 6 + } + + private[this] def writeEscapedUnicode( + b: Byte, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + ByteArrayAccess.setInt(buf, pos, 0x3030755c) + ByteArrayAccess.setShort(buf, pos + 4, ds(b & 0xff)) + pos + 6 + } + + private[this] def illegalSurrogateError(): Nothing = encodeError( + "illegal char sequence of surrogate pair" + ) + + private[this] def writeBigInteger( + x: BigInteger, + ss: Array[BigInteger] + ): Unit = { + val bitLen = x.bitLength + if (bitLen < 64) writeLong(x.longValue) + else { + val n = calculateTenPow18SquareNumber(bitLen) + val ss1 = + if (ss eq null) getTenPow18Squares(n) + else ss + val qr = x.divideAndRemainder(ss1(n)) + writeBigInteger(qr(0), ss1) + writeBigIntegerRemainder(qr(1), n - 1, ss1) + } + } + + private[this] def writeBigIntegerRemainder( + x: BigInteger, + n: Int, + ss: Array[BigInteger] + ): Unit = + if (n < 0) + count = + write18Digits(Math.abs(x.longValue), ensureBufCapacity(18), buf, digits) + else { + val qr = x.divideAndRemainder(ss(n)) + writeBigIntegerRemainder(qr(0), n - 1, ss) + writeBigIntegerRemainder(qr(1), n - 1, ss) + } + + private[this] def writeBigDecimal(x: java.math.BigDecimal): Unit = { + var exp = writeBigDecimal(x.unscaledValue, x.scale, 0, null) + if (exp != 0) { + var pos = ensureBufCapacity(12) + val buf = this.buf + val ds = digits + var m: Short = 0x2b45 + if (exp < 0) { + m = 0x2d45 + exp = -exp + } + ByteArrayAccess.setShort(buf, pos, m) + pos += 2 + var q = exp + if (exp < 100000000L) { + pos += digitCount(exp) + count = pos + } else { + q = Math.multiplyHigh( + exp, + 6189700196426901375L + ) >>> 25 // divide a positive long by 100000000 + pos += digitCount(q) + count = write8Digits(exp - q * 100000000L, pos, buf, ds) + } + writePositiveIntDigits(q.toInt, pos, buf, ds) + } + } + + private[this] def writeBigDecimal( + x: BigInteger, + scale: Int, + blockScale: Int, + ss: Array[BigInteger] + ): Long = { + val bitLen = x.bitLength + if (bitLen < 64) { + val v = x.longValue + val pos = ensureBufCapacity( + 28 + ) // Long.MinValue.toString.length + 8 (for a leading zero, dot, and padding zeroes) + val buf = this.buf + var lastPos = writeLong(v, pos, buf) + val digits = (v >> 63).toInt + lastPos - pos + val dotOff = scale.toLong - blockScale + val exp = (digits - 1) - dotOff + if (scale >= 0 && exp >= -6) { + if (exp < 0) + lastPos = insertDotWithZeroes(digits, -1 - exp.toInt, lastPos, buf) + else if (dotOff > 0) + lastPos = insertDot(lastPos - dotOff.toInt, lastPos, buf) + count = lastPos + 0 + } else { + if (digits > 1 || blockScale > 0) + lastPos = insertDot(lastPos - digits + 1, lastPos, buf) + count = lastPos + exp + } + } else { + val n = calculateTenPow18SquareNumber(bitLen) + val ss1 = + if (ss eq null) getTenPow18Squares(n) + else ss + val qr = x.divideAndRemainder(ss1(n)) + val exp = writeBigDecimal(qr(0), scale, (18 << n) + blockScale, ss1) + writeBigDecimalRemainder(qr(1), scale, blockScale, n - 1, ss1) + exp + } + } + + private[this] def writeBigDecimalRemainder( + x: BigInteger, + scale: Int, + blockScale: Int, + n: Int, + ss: Array[BigInteger] + ): Unit = + if (n < 0) { + val pos = ensureBufCapacity(19) // 18 digits and a place for optional dot + val buf = this.buf + var lastPos = write18Digits(Math.abs(x.longValue), pos, buf, digits) + val dotOff = scale - blockScale + if (dotOff > 0 && dotOff <= 18) + lastPos = insertDot(lastPos - dotOff, lastPos, buf) + count = lastPos + } else { + val qr = x.divideAndRemainder(ss(n)) + writeBigDecimalRemainder(qr(0), scale, (18 << n) + blockScale, n - 1, ss) + writeBigDecimalRemainder(qr(1), scale, blockScale, n - 1, ss) + } + + private[this] def calculateTenPow18SquareNumber(bitLen: Int): Int = { + val m = Math.max( + (bitLen * 71828554L >> 32).toInt - 1, + 1 + ) // Math.max((x.bitLength * Math.log(2) / Math.log(1e18)).toInt - 1, 1) + 31 - java.lang.Integer.numberOfLeadingZeros(m) + } + + private[this] def insertDotWithZeroes( + digits: Int, + pad: Int, + lastPos: Int, + buf: Array[Byte] + ): Int = { + var pos = lastPos + pad + 1 + val numPos = pos - digits + val off = pad + 2 + while (pos > numPos) { + buf(pos) = buf(pos - off) + pos -= 1 + } + val dotPos = pos - pad + while (pos > dotPos) { + buf(pos) = '0' + pos -= 1 + } + ByteArrayAccess.setShort(buf, dotPos - 1, 0x2e30) + lastPos + off + } + + private[this] def insertDot( + dotPos: Int, + lastPos: Int, + buf: Array[Byte] + ): Int = { + var pos = lastPos + while (pos > dotPos) { + buf(pos) = buf(pos - 1) + pos -= 1 + } + buf(dotPos) = '.' + lastPos + 1 + } + + private[this] def writeBooleanInternal(x: Boolean): Unit = { + var pos = ensureBufCapacity(8) // bytes in Long + if (x) { + ByteArrayAccess.setInt(buf, pos, 0x65757274) + pos += 4 + } else { + ByteArrayAccess.setLong(buf, pos, 0x65736c6166L) + pos += 5 + } + count = pos + } + + private[this] def writeByte(x: Byte): Unit = { + var pos = ensureBufCapacity( + 5 + ) // size of Int in bytes + one byte for the sign + val buf = this.buf + val ds = digits + var q0 = x.toInt + if (q0 < 0) { + q0 = -q0 + buf(pos) = '-' + pos += 1 + } + if (q0 < 10) { + buf(pos) = (q0 | '0').toByte + pos += 1 + } else if (q0 < 100) { + ByteArrayAccess.setShort(buf, pos, ds(q0)) + pos += 2 + } else { + ByteArrayAccess.setInt(buf, pos, ds(q0 - 100) << 8 | '1') + pos += 3 + } + count = pos + } + + private[this] def write3Digits( + x: Int, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val q1 = x * 1311 >> 17 // divide a small positive int by 100 + ByteArrayAccess.setInt(buf, pos, ds(x - q1 * 100) << 8 | q1 | '0') + pos + 3 + } + + private[this] def write4Digits( + x: Int, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val q1 = x * 5243 >> 19 // divide a small positive int by 100 + val d1 = ds(x - q1 * 100) << 16 + val d2 = ds(q1) + ByteArrayAccess.setInt(buf, pos, d1 | d2) + pos + 4 + } + + private[this] def write8Digits( + x: Long, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val y1 = + x * 140737489 // Based on James Anhalt's algorithm for 8 digits: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ + val m1 = 0x7fffffffffffL + val m2 = 100L + val y2 = (y1 & m1) * m2 + val y3 = (y2 & m1) * m2 + val y4 = (y3 & m1) * m2 + val d1 = ds((y1 >> 47).toInt) + val d2 = ds((y2 >> 47).toInt) << 16 + val d3 = ds((y3 >> 47).toInt).toLong << 32 + val d4 = ds((y4 >> 47).toInt).toLong << 48 + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3 | d4) + pos + 8 + } + + private[this] def write18Digits( + x: Long, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val m1 = 6189700196426901375L + val q1 = + Math.multiplyHigh(x, m1) >>> 25 // divide a positive long by 100000000 + val q2 = + Math.multiplyHigh(q1, m1) >>> 25 // divide a positive long by 100000000 + ByteArrayAccess.setShort(buf, pos, ds(q2.toInt)) + write8Digits( + x - q1 * 100000000L, + write8Digits(q1 - q2 * 100000000L, pos + 2, buf, ds), + buf, + ds + ) + } + + private[this] def writeShort(x: Short): Unit = { + var pos = ensureBufCapacity(9) // 8 bytes in long + a byte for the sign + val buf = this.buf + val ds = digits + var q0 = x.toInt + if (q0 < 0) { + q0 = -q0 + buf(pos) = '-' + pos += 1 + } + if (q0 < 100) { + if (q0 < 10) { + buf(pos) = (q0 | '0').toByte + pos += 1 + } else { + ByteArrayAccess.setShort(buf, pos, ds(q0)) + pos += 2 + } + } else if (q0 < 10000) { + val q1 = q0 * 5243 >> 19 // divide a small positive int by 100 + val d2 = ds(q0 - q1 * 100) + if (q0 < 1000) { + ByteArrayAccess.setInt(buf, pos, q1 | '0' | d2 << 8) + pos += 3 + } else { + ByteArrayAccess.setInt(buf, pos, ds(q1) | d2 << 16) + pos += 4 + } + } else { + val y1 = + q0 * 429497L // Based on James Anhalt's algorithm for 5 digits: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ + val y2 = (y1 & 0xffffffffL) * 100 + val y3 = (y2 & 0xffffffffL) * 100 + val d1 = (y1 >> 32).toInt | '0' + val d2 = ds((y2 >> 32).toInt) << 8 + val d3 = ds((y3 >> 32).toInt).toLong << 24 + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3) + pos += 5 + } + count = pos + } + + private[this] def writeInt(x: Int): Unit = { + var pos = ensureBufCapacity(11) // Int.MinValue.toString.length + val buf = this.buf + val ds = digits + var q0 = x + if (x < 0) { + q0 = -q0 + ByteArrayAccess.setShort(buf, pos, 0x322d) + pos += 1 + if (q0 == x) { + q0 = 147483648 + pos += 1 + } + } + pos += digitCount(q0.toLong) + writePositiveIntDigits(q0, pos, buf, ds) + count = pos + } + + private[this] def writeLong(x: Long): Unit = + count = + writeLong(x, ensureBufCapacity(20), buf) // Long.MinValue.toString.length + + private[this] def writeLong(x: Long, p: Int, buf: Array[Byte]): Int = { + var pos = p + val ds = digits + var q0 = x + if (x < 0) { + q0 = -q0 + ByteArrayAccess.setInt(buf, pos, 0x3232392d) + pos += 1 + if (q0 == x) { + q0 = 3372036854775808L + pos += 3 + } + } + val m1 = 100000000L + var q2 = q0 + var lastPos = pos + if (q0 < m1) { + lastPos += digitCount(q0) + pos = lastPos + } else { + val m2 = 6189700196426901375L + val q1 = + Math.multiplyHigh(q0, m2) >>> 25 // divide a positive long by 100000000 + if (q1 < m1) { + q2 = q1 + lastPos += digitCount(q1) + pos = lastPos + } else { + q2 = Math.multiplyHigh( + q1, + m2 + ) >>> 25 // divide a small positive long by 100000000 + lastPos += digitCount(q2) + pos = write8Digits(q1 - q2 * m1, lastPos, buf, ds) + } + pos = write8Digits(q0 - q1 * m1, pos, buf, ds) + } + writePositiveIntDigits(q2.toInt, lastPos, buf, ds) + pos + } + + // Based on the amazing work of Raffaello Giulietti + // "The Schubfach way to render doubles": https://drive.google.com/file/d/1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN/view + // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/FloatToDecimal.java + private[this] def writeFloat(x: Float): Unit = { + var pos = ensureBufCapacity(15) + val buf = this.buf + if (x < 0.0f) { + buf(pos) = '-' + pos += 1 + } + if (x == 0.0f) { + ByteArrayAccess.setInt(buf, pos, 0x302e30) + pos += 3 + } else { + val bits = java.lang.Float.floatToRawIntBits(x) + var e2 = (bits >> 23 & 0xff) - 150 + var m2 = bits & 0x7fffff | 0x800000 + var m10, e10 = 0 + if (e2 == 0) m10 = m2 + else if ((e2 >= -23 && e2 < 0) && m2 << e2 == 0) m10 = m2 >> -e2 + else { + var e10Corr, e2Corr = 0 + var cblCorr = 2 + if (e2 == -150) { + m2 &= 0x7fffff + e2 = -149 + if (m2 < 8) { + m2 *= 10 + e10Corr = 1 + } + } else if (e2 == 105) illegalNumberError(x) + else if (m2 == 0x800000 && e2 > -149) { + e2Corr = 131007 + cblCorr = 1 + } + e10 = e2 * 315653 - e2Corr >> 20 + val g = gs(e10 + 324 << 1) + 1 + val h = (e10 * -108853 >> 15) + e2 + 1 + val cb = m2 << 2 + val vbCorr = (m2 & 0x1) - 1 + val vb = rop(g, cb << h) + val vbl = rop(g, cb - cblCorr << h) + vbCorr + val vbr = rop(g, cb + 2 << h) - vbCorr + var diff = 0 + if ( + vb < 400 || { + m10 = (vb * 107374183L >> 32).toInt // divide a positive int by 40 + val vb40 = m10 * 40 + diff = vbl - vb40 + (vb40 - vbr + 40 ^ diff) >= 0 + } + ) { + m10 = vb >> 2 + val vb4 = m10 << 2 + diff = vbl - vb4 + if ((vb4 - vbr + 4 ^ diff) >= 0) diff = (vb & 0x3) + (m10 & 0x1) - 3 + } else e10Corr = -1 + m10 += ~diff >>> 31 + e10 -= e10Corr + } + val len = digitCount(m10.toLong) + e10 += len - 1 + val ds = digits + if (e10 < -3 || e10 >= 7) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, (buf(pos + 1) | 0x2e00).toShort) + if (lastPos - 3 < pos) { + buf(lastPos) = '0' + pos = lastPos + 1 + } else pos = lastPos + ByteArrayAccess.setShort(buf, pos, 0x2d45) + pos += 1 + if (e10 < 0) { + e10 = -e10 + pos += 1 + } + if (e10 < 10) { + buf(pos) = (e10 | '0').toByte + pos += 1 + } else { + ByteArrayAccess.setShort(buf, pos, ds(e10)) + pos += 2 + } + } else if (e10 < 0) { + val dotPos = pos + 1 + ByteArrayAccess.setInt(buf, pos, 0x30303030) + pos -= e10 + pos = writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + buf(dotPos) = '.' + } else if (e10 < len - 1) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + val bs = ByteArrayAccess.getLong(buf, pos) + val s = e10 << 3 + val m = 0xffffffffffff0000L << s + val d1 = (~m & bs) >> 8 + val d2 = 0x2e00L << s + val d3 = m & bs + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3) + pos = lastPos + } else { + pos += len + writePositiveIntDigits(m10, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, 0x302e) + pos += 2 + } + } + count = pos + } + + private[this] def rop(g: Long, cp: Int): Int = { + val x = Math.multiplyHigh(g, cp.toLong << 32) + (x >>> 31).toInt | -x.toInt >>> 31 + } + + // Based on the amazing work of Raffaello Giulietti + // "The Schubfach way to render doubles": https://drive.google.com/file/d/1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN/view + // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/DoubleToDecimal.java + private[this] def writeDouble(x: Double): Unit = { + var pos = ensureBufCapacity( + 25 + ) // -1.2898455142673966E-135.toString.length + 1 + val buf = this.buf + if (x < 0.0) { + buf(pos) = '-' + pos += 1 + } + if (x == 0.0) { + ByteArrayAccess.setInt(buf, pos, 0x302e30) + pos += 3 + } else { + val bits = java.lang.Double.doubleToRawLongBits(x) + var e2 = ((bits >> 52).toInt & 0x7ff) - 1075 + var m2 = bits & 0xfffffffffffffL | 0x10000000000000L + var m10 = 0L + var e10 = 0 + if (e2 == 0) m10 = m2 + else if ((e2 >= -52 && e2 < 0) && m2 << e2 == 0) m10 = m2 >> -e2 + else { + var e10Corr, e2Corr = 0 + var cblCorr = 2 + if (e2 == -1075) { + m2 &= 0xfffffffffffffL + e2 = -1074 + if (m2 < 3) { + m2 *= 10 + e10Corr = 1 + } + } else if (e2 == 972) illegalNumberError(x) + else if (m2 == 0x10000000000000L && e2 > -1074) { + e2Corr = 131007 + cblCorr = 1 + } + e10 = e2 * 315653 - e2Corr >> 20 + val gs = DefaultTokenWriter.gs + val i = e10 + 324 << 1 + val g1 = gs(i) + val g0 = gs(i + 1) + val h = (e10 * -108853 >> 15) + e2 + 2 + val cb = m2 << 2 + val vbCorr = (m2.toInt & 0x1) - 1 + val vb = rop(g1, g0, cb << h) + val vbl = rop(g1, g0, cb - cblCorr << h) + vbCorr + val vbr = rop(g1, g0, cb + 2 << h) - vbCorr + var diff = 0 + if ( + vb < 400 || { + m10 = Math.multiplyHigh( + vb, + 461168601842738792L + ) // divide a positive long by 40 + val vb40 = m10 * 40 + diff = (vbl - vb40).toInt + ((vb40 - vbr).toInt + 40 ^ diff) >= 0 + } + ) { + m10 = vb >> 2 + val vb4 = m10 << 2 + diff = (vbl - vb4).toInt + if (((vb4 - vbr).toInt + 4 ^ diff) >= 0) + diff = (vb.toInt & 0x3) + (m10.toInt & 0x1) - 3 + } else e10Corr = -1 + m10 += ~diff >>> 31 + e10 -= e10Corr + } + val len = digitCount(m10) + e10 += len - 1 + val ds = digits + if (e10 < -3 || e10 >= 7) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, (buf(pos + 1) | 0x2e00).toShort) + if (lastPos - 3 < pos) { + buf(lastPos) = '0' + pos = lastPos + 1 + } else pos = lastPos + ByteArrayAccess.setShort(buf, pos, 0x2d45) + pos += 1 + if (e10 < 0) { + e10 = -e10 + pos += 1 + } + if (e10 < 10) { + buf(pos) = (e10 | '0').toByte + pos += 1 + } else if (e10 < 100) { + ByteArrayAccess.setShort(buf, pos, ds(e10)) + pos += 2 + } else pos = write3Digits(e10, pos, buf, ds) + } else if (e10 < 0) { + val dotPos = pos + 1 + ByteArrayAccess.setInt(buf, pos, 0x30303030) + pos -= e10 + pos = writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + buf(dotPos) = '.' + } else if (e10 < len - 1) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + val bs = ByteArrayAccess.getLong(buf, pos) + val s = e10 << 3 + val m = 0xffffffffffff0000L << s + val d1 = (~m & bs) >> 8 + val d2 = 0x2e00L << s + val d3 = m & bs + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3) + pos = lastPos + } else { + pos += len + writePositiveIntDigits(m10.toInt, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, 0x302e) + pos += 2 + } + } + count = pos + } + + private[this] def rop(g1: Long, g0: Long, cp: Long): Long = { + val x = Math.multiplyHigh(g0, cp) + (g1 * cp >>> 1) + Math.multiplyHigh(g1, cp) + (x >>> 63) | (-x ^ x) >>> 63 + } + + // Adoption of a nice trick from Daniel Lemire's blog that works for numbers up to 10^18: + // https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/ + private[this] def digitCount(x: Long): Int = + (offsets(java.lang.Long.numberOfLeadingZeros(x)) + x >> 58).toInt + + private[this] def writeSignificantFractionDigits( + x: Long, + p: Int, + pl: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + var q0 = x.toInt + var pos = p + var posLim = pl + if (q0 != x) { + val q1 = + (Math.multiplyHigh( + x, + 6189700196426901375L + ) >>> 25).toInt // divide a positive long by 100000000 + val r1 = (x - q1 * 100000000L).toInt + val posm8 = pos - 8 + if (r1 == 0) { + q0 = q1 + pos = posm8 + } else { + writeFractionDigits(q1, posm8, posLim, buf, ds) + q0 = r1 + posLim = posm8 + } + } + writeSignificantFractionDigits(q0, pos, posLim, buf, ds) + } + + private[this] def writeSignificantFractionDigits( + x: Int, + p: Int, + posLim: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + var q0 = x + var q1 = 0 + var pos = p + while ({ + val qp = q0 * 1374389535L + q1 = (qp >> 37).toInt // divide a positive int by 100 + (qp & 0x1fc0000000L) == 0 // check if q is divisible by 100 + }) { + q0 = q1 + pos -= 2 + } + val d = ds(q0 - q1 * 100) + ByteArrayAccess.setShort(buf, pos - 1, d) + writeFractionDigits(q1, pos - 2, posLim, buf, ds) + pos + ((0x3039 - d) >>> 31) + } + + private[this] def writeFractionDigits( + x: Int, + p: Int, + posLim: Int, + buf: Array[Byte], + ds: Array[Short] + ): Unit = { + var q0 = x + var pos = p + while (pos > posLim) { + val q1 = (q0 * 1374389535L >> 37).toInt // divide a positive int by 100 + ByteArrayAccess.setShort(buf, pos - 1, ds(q0 - q1 * 100)) + q0 = q1 + pos -= 2 + } + } + + private[this] def writePositiveIntDigits( + x: Int, + p: Int, + buf: Array[Byte], + ds: Array[Short] + ): Unit = { + var q0 = x + var pos = p + while ({ + pos -= 2 + q0 >= 100 + }) { + val q1 = (q0 * 1374389535L >> 37).toInt // divide a positive int by 100 + ByteArrayAccess.setShort(buf, pos, ds(q0 - q1 * 100)) + q0 = q1 + } + if (q0 < 10) buf(pos + 1) = (q0 | '0').toByte + else ByteArrayAccess.setShort(buf, pos, ds(q0)) + } + + private[this] def illegalNumberError(x: Float): Nothing = encodeError( + "illegal number: " + x + ) + + private[this] def illegalNumberError(x: Double): Nothing = encodeError( + "illegal number: " + x + ) + + private[this] def ensureBufCapacity(required: Int): Int = { + val pos = count + if (pos + required <= limit) pos + else flushAndGrowBuf(required, pos) + } + + private[this] def flushAndGrowBuf(required: Int, pos: Int): Int = + if (bbuf ne null) { + bbuf.put(buf, 0, pos) + if (required > limit) growBuf(required) + 0 + } else if (out ne null) { + out.write(buf, 0, pos) + if (required > limit) growBuf(required) + 0 + } else if (disableBufGrowing) + throw new ArrayIndexOutOfBoundsException("`buf` length exceeded") + else { + growBuf(pos + required) + pos + } + + private[this] def growBuf(required: Int): Unit = + setBuf( + java.util.Arrays.copyOf( + buf, + (-1 >>> Integer.numberOfLeadingZeros(limit | required)) + 1 + ) + ) + + private[this] def reallocateBufToPreferredSize(): Unit = setBuf( + new Array[Byte](config.preferredBufSize) + ) + + private[this] def setBuf(buf: Array[Byte]): Unit = { + this.buf = buf + limit = buf.length + } +} + +object DefaultTokenWriter { + /* Use the following code to generate `escapedChars` in Scala REPL: + val es = new Array[Byte](128) + java.util.Arrays.fill(es, 0, 32, -1: Byte) + es('\n') = 'n' + es('\r') = 'r' + es('\t') = 't' + es('\b') = 'b' + es('\f') = 'f' + es('\\') = '\\' + es('\"') = '"' + es(127) = -1 + es.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val escapedChars: Array[Byte] = Array( + -1, -1, -1, -1, -1, -1, -1, -1, 98, 116, 110, -1, 102, 114, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 34, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 + ) + private final val offsets = Array(5088146770730811392L, 5088146770730811392L, + 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, + 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, + 4889916394579099648L, 4889916394579099648L, 4889916394579099648L, + 4610686018427387904L, 4610686018427387904L, 4610686018427387904L, + 4610686018427387904L, 4323355642275676160L, 4323355642275676160L, + 4323355642275676160L, 4035215266123964416L, 4035215266123964416L, + 4035215266123964416L, 3746993889972252672L, 3746993889972252672L, + 3746993889972252672L, 3746993889972252672L, 3458764413820540928L, + 3458764413820540928L, 3458764413820540928L, 3170534127668829184L, + 3170534127668829184L, 3170534127668829184L, 2882303760517117440L, + 2882303760517117440L, 2882303760517117440L, 2882303760517117440L, + 2594073385265405696L, 2594073385265405696L, 2594073385265405696L, + 2305843009203693952L, 2305843009203693952L, 2305843009203693952L, + 2017612633060982208L, 2017612633060982208L, 2017612633060982208L, + 2017612633060982208L, 1729382256910170464L, 1729382256910170464L, + 1729382256910170464L, 1441151880758548720L, 1441151880758548720L, + 1441151880758548720L, 1152921504606845976L, 1152921504606845976L, + 1152921504606845976L, 1152921504606845976L, 864691128455135132L, + 864691128455135132L, 864691128455135132L, 576460752303423478L, + 576460752303423478L, 576460752303423478L, 576460752303423478L, + 576460752303423478L, 576460752303423478L, 576460752303423478L) + /* Use the following code to generate `digits` in Scala REPL: + val ds = new Array[Short](100) + var i, j = 0 + while (j < 10) { + var k = 0 + while (k < 10) { + ds(i) = (((k + '0') << 8) + (j + '0')).toShort + i += 1 + k += 1 + } + j += 1 + } + ds.grouped(10).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val digits: Array[Short] = Array( + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 12337, + 12593, 12849, 13105, 13361, 13617, 13873, 14129, 14385, 14641, 12338, 12594, + 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 12339, 12595, 12851, + 13107, 13363, 13619, 13875, 14131, 14387, 14643, 12340, 12596, 12852, 13108, + 13364, 13620, 13876, 14132, 14388, 14644, 12341, 12597, 12853, 13109, 13365, + 13621, 13877, 14133, 14389, 14645, 12342, 12598, 12854, 13110, 13366, 13622, + 13878, 14134, 14390, 14646, 12343, 12599, 12855, 13111, 13367, 13623, 13879, + 14135, 14391, 14647, 12344, 12600, 12856, 13112, 13368, 13624, 13880, 14136, + 14392, 14648, 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, + 14649 + ) + /* Use the following code to generate `lowerCaseHexDigits` in Scala REPL: + val ds = new Array[Short](256) + var i, j = 0 + while (j < 16) { + val d1 = + if (j <= 9) j + '0' + else j + 'a' - 10 + var k = 0 + while (k < 16) { + val d2 = + if (k <= 9) k + '0' + else k + 'a' - 10 + ds(i) = ((d2 << 8) + d1).toShort + i += 1 + k += 1 + } + j += 1 + } + ds.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val lowerCaseHexDigits: Array[Short] = Array( + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 24880, + 25136, 25392, 25648, 25904, 26160, 12337, 12593, 12849, 13105, 13361, 13617, + 13873, 14129, 14385, 14641, 24881, 25137, 25393, 25649, 25905, 26161, 12338, + 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 24882, 25138, + 25394, 25650, 25906, 26162, 12339, 12595, 12851, 13107, 13363, 13619, 13875, + 14131, 14387, 14643, 24883, 25139, 25395, 25651, 25907, 26163, 12340, 12596, + 12852, 13108, 13364, 13620, 13876, 14132, 14388, 14644, 24884, 25140, 25396, + 25652, 25908, 26164, 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, + 14389, 14645, 24885, 25141, 25397, 25653, 25909, 26165, 12342, 12598, 12854, + 13110, 13366, 13622, 13878, 14134, 14390, 14646, 24886, 25142, 25398, 25654, + 25910, 26166, 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, + 14647, 24887, 25143, 25399, 25655, 25911, 26167, 12344, 12600, 12856, 13112, + 13368, 13624, 13880, 14136, 14392, 14648, 24888, 25144, 25400, 25656, 25912, + 26168, 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, 14649, + 24889, 25145, 25401, 25657, 25913, 26169, 12385, 12641, 12897, 13153, 13409, + 13665, 13921, 14177, 14433, 14689, 24929, 25185, 25441, 25697, 25953, 26209, + 12386, 12642, 12898, 13154, 13410, 13666, 13922, 14178, 14434, 14690, 24930, + 25186, 25442, 25698, 25954, 26210, 12387, 12643, 12899, 13155, 13411, 13667, + 13923, 14179, 14435, 14691, 24931, 25187, 25443, 25699, 25955, 26211, 12388, + 12644, 12900, 13156, 13412, 13668, 13924, 14180, 14436, 14692, 24932, 25188, + 25444, 25700, 25956, 26212, 12389, 12645, 12901, 13157, 13413, 13669, 13925, + 14181, 14437, 14693, 24933, 25189, 25445, 25701, 25957, 26213, 12390, 12646, + 12902, 13158, 13414, 13670, 13926, 14182, 14438, 14694, 24934, 25190, 25446, + 25702, 25958, 26214 + ) + /* Use the following code to generate `upperCaseHexDigits` in Scala REPL: + val ds = new Array[Short](256) + var i, j = 0 + while (j < 16) { + val d1 = + if (j <= 9) j + '0' + else j + 'A' - 10 + var k = 0 + while (k < 16) { + val d2 = + if (k <= 9) k + '0' + else k + 'A' - 10 + ds(i) = ((d2 << 8) + d1).toShort + i += 1 + k += 1 + } + j += 1 + } + ds.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val upperCaseHexDigits: Array[Short] = Array( + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 16688, + 16944, 17200, 17456, 17712, 17968, 12337, 12593, 12849, 13105, 13361, 13617, + 13873, 14129, 14385, 14641, 16689, 16945, 17201, 17457, 17713, 17969, 12338, + 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 16690, 16946, + 17202, 17458, 17714, 17970, 12339, 12595, 12851, 13107, 13363, 13619, 13875, + 14131, 14387, 14643, 16691, 16947, 17203, 17459, 17715, 17971, 12340, 12596, + 12852, 13108, 13364, 13620, 13876, 14132, 14388, 14644, 16692, 16948, 17204, + 17460, 17716, 17972, 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, + 14389, 14645, 16693, 16949, 17205, 17461, 17717, 17973, 12342, 12598, 12854, + 13110, 13366, 13622, 13878, 14134, 14390, 14646, 16694, 16950, 17206, 17462, + 17718, 17974, 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, + 14647, 16695, 16951, 17207, 17463, 17719, 17975, 12344, 12600, 12856, 13112, + 13368, 13624, 13880, 14136, 14392, 14648, 16696, 16952, 17208, 17464, 17720, + 17976, 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, 14649, + 16697, 16953, 17209, 17465, 17721, 17977, 12353, 12609, 12865, 13121, 13377, + 13633, 13889, 14145, 14401, 14657, 16705, 16961, 17217, 17473, 17729, 17985, + 12354, 12610, 12866, 13122, 13378, 13634, 13890, 14146, 14402, 14658, 16706, + 16962, 17218, 17474, 17730, 17986, 12355, 12611, 12867, 13123, 13379, 13635, + 13891, 14147, 14403, 14659, 16707, 16963, 17219, 17475, 17731, 17987, 12356, + 12612, 12868, 13124, 13380, 13636, 13892, 14148, 14404, 14660, 16708, 16964, + 17220, 17476, 17732, 17988, 12357, 12613, 12869, 13125, 13381, 13637, 13893, + 14149, 14405, 14661, 16709, 16965, 17221, 17477, 17733, 17989, 12358, 12614, + 12870, 13126, 13382, 13638, 13894, 14150, 14406, 14662, 16710, 16966, 17222, + 17478, 17734, 17990 + ) + /* Use the following code to generate `base64Digits` in Scala REPL: + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes + .grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val base64Digits: Array[Byte] = Array( + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 + ) + /* Use the following code to generate `base64UrlDigits` in Scala REPL: + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".getBytes + .grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val base64UrlDigits: Array[Byte] = Array( + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 45, 95 + ) + /* Use the following code to generate `gs` in Scala REPL: + val gs = new Array[Long](1234) + var i = 0 + var pow5 = BigInt(1) + while (i < 650) { + val av = (pow5 >> (pow5.bitLength - 126)) + 1 + gs(648 - i) = (av >> 63).longValue & 0x7FFFFFFFFFFFFFFFL + gs(649 - i) = av.longValue & 0x7FFFFFFFFFFFFFFFL + pow5 *= 5 + i += 2 + } + pow5 = BigInt(5) + while (i < 1234) { + val inv = ((BigInt(1) << (pow5.bitLength + 125)) / pow5) + 1 + gs(i) = (inv >> 63).longValue & 0x7FFFFFFFFFFFFFFFL + gs(i + 1) = inv.longValue & 0x7FFFFFFFFFFFFFFFL + pow5 *= 5 + i += 2 + } + gs.grouped(4).map(_.mkString("L, ")).mkString("Array(\n", "L,\n", "L\n)") + */ + private final val gs: Array[Long] = Array( + 5696189077778435540L, 6557778377634271669L, 9113902524445496865L, + 1269073367360058862L, 7291122019556397492L, 1015258693888047090L, + 5832897615645117993L, 6346230177223303157L, 4666318092516094394L, + 8766332956520552849L, 7466108948025751031L, 8492109508320019073L, + 5972887158420600825L, 4949013199285060097L, 4778309726736480660L, + 3959210559428048077L, 7645295562778369056L, 6334736895084876923L, + 6116236450222695245L, 3223115108696946377L, 4892989160178156196L, + 2578492086957557102L, 7828782656285049914L, 436238524390181040L, + 6263026125028039931L, 2193665226883099993L, 5010420900022431944L, + 9133629810990300641L, 8016673440035891111L, 9079784475471615541L, + 6413338752028712889L, 5419153173006337271L, 5130671001622970311L, + 6179996945776024979L, 8209073602596752498L, 6198646298499729642L, + 6567258882077401998L, 8648265853541694037L, 5253807105661921599L, + 1384589460720489745L, 8406091369059074558L, 5904691951894693915L, + 6724873095247259646L, 8413102376257665455L, 5379898476197807717L, + 4885807493635177203L, 8607837561916492348L, 438594360332462878L, + 6886270049533193878L, 4040224303007880625L, 5509016039626555102L, + 6921528257148214824L, 8814425663402488164L, 3695747581953323071L, + 7051540530721990531L, 4801272472933613619L, 5641232424577592425L, + 1996343570975935733L, 9025971879324147880L, 3194149713561497173L, + 7220777503459318304L, 2555319770849197738L, 5776622002767454643L, + 3888930224050313352L, 4621297602213963714L, 6800492993982161005L, + 7394076163542341943L, 5346765568258592123L, 5915260930833873554L, + 7966761269348784022L, 4732208744667098843L, 8218083422849982379L, + 7571533991467358150L, 2080887032334240837L, 6057227193173886520L, + 1664709625867392670L, 4845781754539109216L, 1331767700693914136L, + 7753250807262574745L, 7664851543223128102L, 6202600645810059796L, + 6131881234578502482L, 4962080516648047837L, 3060830580291846824L, + 7939328826636876539L, 6742003335837910079L, 6351463061309501231L, + 7238277076041283225L, 5081170449047600985L, 3945947253462071419L, + 8129872718476161576L, 6313515605539314269L, 6503898174780929261L, + 3206138077060496254L, 5203118539824743409L, 720236054277441842L, + 8324989663719589454L, 4841726501585817270L, 6659991730975671563L, + 5718055608639608977L, 5327993384780537250L, 8263793301653597505L, + 8524789415648859601L, 3998697245790980200L, 6819831532519087681L, + 1354283389261828999L, 5455865226015270144L, 8462124340893283845L, + 8729384361624432231L, 8005375723316388668L, 6983507489299545785L, + 4559626171282155773L, 5586805991439636628L, 3647700937025724618L, + 8938889586303418605L, 3991647091870204227L, 7151111669042734884L, + 3193317673496163382L, 5720889335234187907L, 4399328546167885867L, + 9153422936374700651L, 8883600081239572549L, 7322738349099760521L, + 5262205657620702877L, 5858190679279808417L, 2365090118725607140L, + 4686552543423846733L, 7426095317093351197L, 7498484069478154774L, + 813706063123630946L, 5998787255582523819L, 2495639257869859918L, + 4799029804466019055L, 3841185813666843096L, 7678447687145630488L, + 6145897301866948954L, 6142758149716504390L, 8606066656235469486L, + 4914206519773203512L, 6884853324988375589L, 7862730431637125620L, + 3637067690497580296L, 6290184345309700496L, 2909654152398064237L, + 5032147476247760397L, 483048914547496228L, 8051435961996416635L, + 2617552670646949126L, 6441148769597133308L, 2094042136517559301L, + 5152919015677706646L, 5364582523955957764L, 8244670425084330634L, + 4893983223587622099L, 6595736340067464507L, 5759860986241052841L, + 5276589072053971606L, 918539974250931950L, 8442542515286354569L, + 7003687180914356604L, 6754034012229083655L, 7447624152102440445L, + 5403227209783266924L, 5958099321681952356L, 8645163535653227079L, + 3998935692578258285L, 6916130828522581663L, 5043822961433561789L, + 5532904662818065330L, 7724407183888759755L, 8852647460508904529L, + 3135679457367239799L, 7082117968407123623L, 4353217973264747001L, + 5665694374725698898L, 7171923193353707924L, 9065110999561118238L, + 407030665140201709L, 7252088799648894590L, 4014973346854071690L, + 5801671039719115672L, 3211978677483257352L, 4641336831775292537L, + 8103606164099471367L, 7426138930840468060L, 5587072233075333540L, + 5940911144672374448L, 4469657786460266832L, 4752728915737899558L, + 7265075043910123789L, 7604366265180639294L, 556073626030467093L, + 6083493012144511435L, 2289533308195328836L, 4866794409715609148L, + 1831626646556263069L, 7786871055544974637L, 1085928227119065748L, + 6229496844435979709L, 6402765803808118083L, 4983597475548783767L, + 6966887050417449628L, 7973755960878054028L, 3768321651184098759L, + 6379004768702443222L, 6704006135689189330L, 5103203814961954578L, + 1673856093809441141L, 8165126103939127325L, 833495342724150664L, + 6532100883151301860L, 666796274179320531L, 5225680706521041488L, + 533437019343456425L, 8361089130433666380L, 8232196860433350926L, + 6688871304346933104L, 6585757488346680741L, 5351097043477546483L, + 7113280398048299755L, 8561755269564074374L, 313202192651548637L, + 6849404215651259499L, 2095236161492194072L, 5479523372521007599L, + 3520863336564710419L, 8767237396033612159L, 99358116390671185L, + 7013789916826889727L, 1924160900483492110L, 5611031933461511781L, + 7073351942499659173L, 8977651093538418850L, 7628014293257544353L, + 7182120874830735080L, 6102411434606035483L, 5745696699864588064L, + 4881929147684828386L, 9193114719783340903L, 2277063414182859933L, + 7354491775826672722L, 5510999546088198270L, 5883593420661338178L, + 719450822128648293L, 4706874736529070542L, 4264909472444828957L, + 7530999578446512867L, 8668529563282681493L, 6024799662757210294L, + 3245474835884234871L, 4819839730205768235L, 4441054276078343059L, + 7711743568329229176L, 7105686841725348894L, 6169394854663383341L, + 3839875066009323953L, 4935515883730706673L, 1227225645436504001L, + 7896825413969130677L, 118886625327451240L, 6317460331175304541L, + 5629132522374826477L, 5053968264940243633L, 2658631610528906020L, + 8086349223904389813L, 2409136169475294470L, 6469079379123511850L, + 5616657750322145900L, 5175263503298809480L, 4493326200257716720L, + 8280421605278095168L, 7189321920412346751L, 6624337284222476135L, + 217434314217011916L, 5299469827377980908L, 173947451373609533L, + 8479151723804769452L, 7657013551681595899L, 6783321379043815562L, + 2436262026603366396L, 5426657103235052449L, 7483032843395558602L, + 8682651365176083919L, 6438829327320028278L, 6946121092140867135L, + 6995737869226977784L, 5556896873712693708L, 5596590295381582227L, + 8891034997940309933L, 7109870065239576402L, 7112827998352247947L, + 153872830078795637L, 5690262398681798357L, 5657121486175901994L, + 9104419837890877372L, 1672696748397622544L, 7283535870312701897L, + 6872180620830963520L, 5826828696250161518L, 1808395681922860493L, + 4661462957000129214L, 5136065360280198718L, 7458340731200206743L, + 2683681354335452463L, 5966672584960165394L, 5836293898210272294L, + 4773338067968132315L, 6513709525939172997L, 7637340908749011705L, + 1198563204647900987L, 6109872726999209364L, 958850563718320789L, + 4887898181599367491L, 2611754858345611793L, 7820637090558987986L, + 489458958611068546L, 6256509672447190388L, 7770264796372675483L, + 5005207737957752311L, 682188614985274902L, 8008332380732403697L, + 6625525006089305327L, 6406665904585922958L, 1611071190129533939L, + 5125332723668738366L, 4978205766845537474L, 8200532357869981386L, + 4275780412210949635L, 6560425886295985109L, 1575949922397804547L, + 5248340709036788087L, 3105434345289198799L, 8397345134458860939L, + 6813369359833673240L, 6717876107567088751L, 7295369895237893754L, + 5374300886053671001L, 3991621508819359841L, 8598881417685873602L, + 2697245599369065423L, 6879105134148698881L, 7691819701608117823L, + 5503284107318959105L, 4308781353915539097L, 8805254571710334568L, + 6894050166264862555L, 7044203657368267654L, 9204588947753800367L, + 5635362925894614123L, 9208345565573995455L, 9016580681431382598L, + 3665306460692661759L, 7213264545145106078L, 6621593983296039730L, + 5770611636116084862L, 8986624001378742108L, 4616489308892867890L, + 3499950386361083363L, 7386382894228588624L, 5599920618177733380L, + 5909106315382870899L, 6324610901913141866L, 4727285052306296719L, + 6904363128901468655L, 7563656083690074751L, 5512957784129484362L, + 6050924866952059801L, 2565691819932632328L, 4840739893561647841L, + 207879048575150701L, 7745183829698636545L, 5866629699833106606L, + 6196147063758909236L, 4693303759866485285L, 4956917651007127389L, + 1909968600522233067L, 7931068241611403822L, 6745298575577483229L, + 6344854593289123058L, 1706890045720076260L, 5075883674631298446L, + 5054860851317971332L, 8121413879410077514L, 4398428547366843807L, + 6497131103528062011L, 5363417245264430207L, 5197704882822449609L, + 2446059388840589004L, 8316327812515919374L, 7603043836886852730L, + 6653062250012735499L, 7927109476880437346L, 5322449800010188399L, + 8186361988875305038L, 8515919680016301439L, 7564155960087622576L, + 6812735744013041151L, 7895999175441053223L, 5450188595210432921L, + 4472124932981887417L, 8720301752336692674L, 3466051078029109543L, + 6976241401869354139L, 4617515269794242796L, 5580993121495483311L, + 5538686623206349399L, 8929588994392773298L, 5172549782388248714L, + 7143671195514218638L, 7827388640652509295L, 5714936956411374911L, + 727887690409141951L, 9143899130258199857L, 6698643526767492606L, + 7315119304206559886L, 1669566006672083762L, 5852095443365247908L, + 8714350434821487656L, 4681676354692198327L, 1437457125744324640L, + 7490682167507517323L, 4144605808561874585L, 5992545734006013858L, + 7005033461591409992L, 4794036587204811087L, 70003547160262509L, + 7670458539527697739L, 1956680082827375175L, 6136366831622158191L, + 3410018473632855302L, 4909093465297726553L, 883340371535329080L, + 7854549544476362484L, 8792042223940347174L, 6283639635581089987L, + 8878308186523232901L, 5026911708464871990L, 3413297734476675998L, + 8043058733543795184L, 5461276375162681596L, 6434446986835036147L, + 6213695507501100438L, 5147557589468028918L, 1281607591258970028L, + 8236092143148846269L, 205897738643396882L, 6588873714519077015L, + 2009392598285672668L, 5271098971615261612L, 1607514078628538134L, + 8433758354584418579L, 4416696933176616176L, 6747006683667534863L, + 5378031953912248102L, 5397605346934027890L, 7991774377871708805L, + 8636168555094444625L, 3563466967739958280L, 6908934844075555700L, + 2850773574191966624L, 5527147875260444560L, 2280618859353573299L, + 8843436600416711296L, 3648990174965717279L, 7074749280333369037L, + 1074517732601618662L, 5659799424266695229L, 6393637408194160414L, + 9055679078826712367L, 4695796630997791177L, 7244543263061369894L, + 67288490056322619L, 5795634610449095915L, 1898505199416013257L, + 4636507688359276732L, 1518804159532810606L, 7418412301374842771L, + 4274761062623452130L, 5934729841099874217L, 1575134442727806543L, + 4747783872879899373L, 6794130776295110719L, 7596454196607838997L, + 9025934834701221989L, 6077163357286271198L, 3531399053019067268L, + 4861730685829016958L, 6514468057157164137L, 7778769097326427133L, + 8578474484080507458L, 6223015277861141707L, 1328756365151540482L, + 4978412222288913365L, 6597028314234097870L, 7965459555662261385L, + 1331873265919780784L, 6372367644529809108L, 1065498612735824627L, + 5097894115623847286L, 4541747704930570025L, 8156630584998155658L, + 3577447513147001717L, 6525304467998524526L, 6551306825259511697L, + 5220243574398819621L, 3396371052836654196L, 8352389719038111394L, + 1744844869796736390L, 6681911775230489115L, 3240550303208344274L, + 5345529420184391292L, 2592440242566675419L, 8552847072295026067L, + 5992578795477635832L, 6842277657836020854L, 1104714221640198342L, + 5473822126268816683L, 2728445784683113836L, 8758115402030106693L, + 2520838848122026975L, 7006492321624085354L, 5706019893239531903L, + 5605193857299268283L, 6409490321962580684L, 8968310171678829253L, + 8410510107769173933L, 7174648137343063403L, 1194384864102473662L, + 5739718509874450722L, 4644856706023889253L, 9183549615799121156L, + 53073100154402158L, 7346839692639296924L, 7421156109607342373L, + 5877471754111437539L, 7781599295056829060L, 4701977403289150031L, + 8069953843416418410L, 7523163845262640050L, 9222577334724359132L, + 6018531076210112040L, 7378061867779487306L, 4814824860968089632L, + 5902449494223589845L, 7703719777548943412L, 2065221561273923105L, + 6162975822039154729L, 7186200471132003969L, 4930380657631323783L, + 7593634784276558337L, 7888609052210118054L, 1081769210616762369L, + 6310887241768094443L, 2710089775864365057L, 5048709793414475554L, + 5857420635433402369L, 8077935669463160887L, 3837849794580578305L, + 6462348535570528709L, 8604303057777328129L, 5169878828456422967L, + 8728116853592817665L, 8271806125530276748L, 6586289336264687617L, + 6617444900424221398L, 8958380283753660417L, 5293955920339377119L, + 1632681004890062849L, 8470329472543003390L, 6301638422566010881L, + 6776263578034402712L, 5041310738052808705L, 5421010862427522170L, + 343699775700336641L, 8673617379884035472L, 549919641120538625L, + 6938893903907228377L, 5973958935009296385L, 5551115123125782702L, + 1089818333265526785L, 8881784197001252323L, 3588383740595798017L, + 7105427357601001858L, 6560055807218548737L, 5684341886080801486L, + 8937393460516749313L, 9094947017729282379L, 1387108685230112769L, + 7275957614183425903L, 2954361355555045377L, 5820766091346740722L, + 6052837899185946625L, 4656612873077392578L, 1152921504606846977L, + 7450580596923828125L, 1L, 5960464477539062500L, 1L, 4768371582031250000L, + 1L, 7629394531250000000L, 1L, 6103515625000000000L, 1L, + 4882812500000000000L, 1L, 7812500000000000000L, 1L, 6250000000000000000L, + 1L, 5000000000000000000L, 1L, 8000000000000000000L, 1L, + 6400000000000000000L, 1L, 5120000000000000000L, 1L, 8192000000000000000L, + 1L, 6553600000000000000L, 1L, 5242880000000000000L, 1L, + 8388608000000000000L, 1L, 6710886400000000000L, 1L, 5368709120000000000L, + 1L, 8589934592000000000L, 1L, 6871947673600000000L, 1L, + 5497558138880000000L, 1L, 8796093022208000000L, 1L, 7036874417766400000L, + 1L, 5629499534213120000L, 1L, 9007199254740992000L, 1L, + 7205759403792793600L, 1L, 5764607523034234880L, 1L, 4611686018427387904L, + 1L, 7378697629483820646L, 3689348814741910324L, 5902958103587056517L, + 1106804644422573097L, 4722366482869645213L, 6419466937650923963L, + 7555786372591432341L, 8426472692870523179L, 6044629098073145873L, + 4896503746925463381L, 4835703278458516698L, 7606551812282281028L, + 7737125245533626718L, 1102436455425918676L, 6189700196426901374L, + 4571297979082645264L, 4951760157141521099L, 5501712790637071373L, + 7922816251426433759L, 3268717242906448711L, 6338253001141147007L, + 4459648201696114131L, 5070602400912917605L, 9101741783469756789L, + 8112963841460668169L, 5339414816696835055L, 6490371073168534535L, + 6116206260728423206L, 5192296858534827628L, 4892965008582738565L, + 8307674973655724205L, 5984069606361426541L, 6646139978924579364L, + 4787255685089141233L, 5316911983139663491L, 5674478955442268148L, + 8507059173023461586L, 5389817513965718714L, 6805647338418769269L, + 2467179603801619810L, 5444517870735015415L, 3818418090412251009L, + 8711228593176024664L, 6109468944659601615L, 6968982874540819731L, + 6732249563098636453L, 5575186299632655785L, 3541125243107954001L, + 8920298079412249256L, 5665800388972726402L, 7136238463529799405L, + 2687965903807225960L, 5708990770823839524L, 2150372723045780768L, + 9134385233318143238L, 7129945171615159552L, 7307508186654514591L, + 169932915179262157L, 5846006549323611672L, 7514643961627230372L, + 4676805239458889338L, 2322366354559873974L, 7482888383134222941L, + 1871111759924843197L, 5986310706507378352L, 8875587037423695204L, + 4789048565205902682L, 3411120815197045840L, 7662477704329444291L, + 7302467711686228506L, 6129982163463555433L, 3997299761978027643L, + 4903985730770844346L, 6887188624324332438L, 7846377169233350954L, + 7330152984177021577L, 6277101735386680763L, 7708796794712572423L, + 5021681388309344611L, 633014213657192454L, 8034690221294951377L, + 6546845963964373411L, 6427752177035961102L, 1548127956429588405L, + 5142201741628768881L, 6772525587256536209L, 8227522786606030210L, + 7146692124868547611L, 6582018229284824168L, 5717353699894838089L, + 5265614583427859334L, 8263231774657780795L, 8424983333484574935L, + 7687147617339583786L, 6739986666787659948L, 6149718093871667029L, + 5391989333430127958L, 8609123289839243947L, 8627182933488204734L, + 2706550819517059345L, 6901746346790563787L, 4009915062984602637L, + 5521397077432451029L, 8741955272500547595L, 8834235323891921647L, + 8453105213888010667L, 7067388259113537318L, 3073135356368498210L, + 5653910607290829854L, 6147857099836708891L, 9046256971665327767L, + 4302548137625868741L, 7237005577332262213L, 8976061732213560478L, + 5789604461865809771L, 1646826163657982898L, 4631683569492647816L, + 8696158560410206965L, 7410693711188236507L, 1001132845059645012L, + 5928554968950589205L, 6334929498160581494L, 4742843975160471364L, + 5067943598528465196L, 7588550360256754183L, 2574686535532678828L, + 6070840288205403346L, 5749098043168053386L, 4856672230564322677L, + 2754604027163487547L, 7770675568902916283L, 6252040850832535236L, + 6216540455122333026L, 8690981495407938512L, 4973232364097866421L, + 5108110788955395648L, 7957171782556586274L, 4483628447586722714L, + 6365737426045269019L, 5431577165440333333L, 5092589940836215215L, + 6189936139723221828L, 8148143905337944345L, 680525786702379117L, + 6518515124270355476L, 544420629361903293L, 5214812099416284380L, + 7814234132973343281L, 8343699359066055009L, 3279402575902573442L, + 6674959487252844007L, 4468196468093013915L, 5339967589802275205L, + 9108580396587276617L, 8543948143683640329L, 5350356597684866779L, + 6835158514946912263L, 6124959685518848585L, 5468126811957529810L, + 8589316563156989191L, 8749002899132047697L, 4519534464196406897L, + 6999202319305638157L, 9149650793469991003L, 5599361855444510526L, + 3630371820034082479L, 8958978968711216842L, 2119246097312621643L, + 7167183174968973473L, 7229420099962962799L, 5733746539975178779L, + 249512857857504755L, 9173994463960286046L, 4088569387313917931L, + 7339195571168228837L, 1426181102480179183L, 5871356456934583069L, + 6674968104097008831L, 4697085165547666455L, 7184648890648562227L, + 7515336264876266329L, 2272066188182923754L, 6012269011901013063L, + 3662327357917294165L, 4809815209520810450L, 6619210701075745655L, + 7695704335233296721L, 1367365084866417240L, 6156563468186637376L, + 8472589697376954439L, 4925250774549309901L, 4933397350530608390L, + 7880401239278895842L, 4204086946107063100L, 6304320991423116673L, + 8897292778998515965L, 5043456793138493339L, 1583811001085947287L, + 8069530869021589342L, 6223446416479425982L, 6455624695217271474L, + 1289408318441630463L, 5164499756173817179L, 2876201062124259532L, + 8263199609878107486L, 8291270514140725574L, 6610559687902485989L, + 4788342003941625298L, 5288447750321988791L, 5675348010524255400L, + 8461516400515182066L, 5391208002096898316L, 6769213120412145653L, + 2468291994306563491L, 5415370496329716522L, 5663982410187161116L, + 8664592794127546436L, 1683674226815637140L, 6931674235302037148L, + 8725637010936330358L, 5545339388241629719L, 1446486386636198802L, + 8872543021186607550L, 6003727033359828406L, 7098034416949286040L, + 4802981626687862725L, 5678427533559428832L, 3842385301350290180L, + 9085484053695086131L, 7992490889531419449L, 7268387242956068905L, + 4549318304254180398L, 5814709794364855124L, 3639454643403344318L, + 4651767835491884099L, 4756238122093630616L, 7442828536787014559L, + 2075957773236943501L, 5954262829429611647L, 3505440625960509963L, + 4763410263543689317L, 8338375722881273455L, 7621456421669902908L, + 5962703527126216881L, 6097165137335922326L, 8459511636442883828L, + 4877732109868737861L, 4922934901783351901L, 7804371375789980578L, + 4187347028111452718L, 6243497100631984462L, 7039226437231072498L, + 4994797680505587570L, 1942032335042947675L, 7991676288808940112L, + 3107251736068716280L, 6393341031047152089L, 8019824610967838509L, + 5114672824837721671L, 8260534096145225969L, 8183476519740354675L, + 304133702235675419L, 6546781215792283740L, 243306961788540335L, + 5237424972633826992L, 194645569430832268L, 8379879956214123187L, + 2156107318460286790L, 6703903964971298549L, 7258909076881094917L, + 5363123171977038839L, 7651801668875831096L, 8580997075163262143L, + 6708859448088464268L, 6864797660130609714L, 9056436373212681737L, + 5491838128104487771L, 9089823505941100552L, 8786941004967180435L, + 1630996757909074751L, 7029552803973744348L, 1304797406327259801L, + 5623642243178995478L, 4733186739803718164L, 8997827589086392765L, + 5728424376314993901L, 7198262071269114212L, 4582739501051995121L, + 5758609657015291369L, 9200214822954461581L, 9213775451224466191L, + 9186320494614273045L, 7371020360979572953L, 5504381988320463275L, + 5896816288783658362L, 8092854405398280943L, 4717453031026926690L, + 2784934709576714431L, 7547924849643082704L, 4455895535322743090L, + 6038339879714466163L, 5409390835629149634L, 4830671903771572930L, + 8016861483245230030L, 7729075046034516689L, 3603606336337592240L, + 6183260036827613351L, 4727559476441028954L, 4946608029462090681L, + 1937373173781868001L, 7914572847139345089L, 8633820300163854287L, + 6331658277711476071L, 8751730647502038591L, 5065326622169180857L, + 5156710110630675711L, 8104522595470689372L, 872038547525260492L, + 6483618076376551497L, 6231654060133073878L, 5186894461101241198L, + 1295974433364548779L, 8299031137761985917L, 228884686012322885L, + 6639224910209588733L, 5717130970922723793L, 5311379928167670986L, + 8263053591480089358L, 8498207885068273579L, 308164894771456841L, + 6798566308054618863L, 2091206323188120634L, 5438853046443695090L, + 5362313873292406831L, 8702164874309912144L, 8579702197267850929L, + 6961731899447929715L, 8708436165185235905L, 5569385519558343772L, + 6966748932148188724L, 8911016831293350036L, 3768100661953281312L, + 7128813465034680029L, 1169806122191669888L, 5703050772027744023L, + 2780519305124291072L, 9124881235244390437L, 2604156480827910553L, + 7299904988195512349L, 7617348406775193928L, 5839923990556409879L, + 7938553132791110304L, 4671939192445127903L, 8195516913603843405L, + 7475102707912204646L, 2044780617540418478L, 5980082166329763716L, + 9014522123516155429L, 4784065733063810973L, 5366943291441969181L, + 7654505172902097557L, 6742434858936195528L, 6123604138321678046L, + 1704599072407046100L, 4898883310657342436L, 8742376887409457526L, + 7838213297051747899L, 1075082168258445910L, 6270570637641398319L, + 2704740141977711890L, 5016456510113118655L, 4008466520953124674L, + 8026330416180989848L, 6413546433524999478L, 6421064332944791878L, + 8820185961561909905L, 5136851466355833503L, 1522125547136662440L, + 8218962346169333605L, 590726468047704741L, 6575169876935466884L, + 472581174438163793L, 5260135901548373507L, 2222739346921486196L, + 8416217442477397611L, 5401057362445333075L, 6732973953981918089L, + 2476171482585311299L, 5386379163185534471L, 3825611593439204201L, + 8618206661096855154L, 2431629734760816398L, 6894565328877484123L, + 3789978195179608280L, 5515652263101987298L, 6721331370885596947L, + 8825043620963179677L, 8909455786045999954L, 7060034896770543742L, + 3438215814094889640L, 5648027917416434993L, 8284595873388777197L, + 9036844667866295990L, 2187306953196312545L, 7229475734293036792L, + 1749845562557050036L, 5783580587434429433L, 6933899672158505514L, + 4626864469947543547L, 13096515613938926L, 7402983151916069675L, + 1865628832353257443L, 5922386521532855740L, 1492503065882605955L, + 4737909217226284592L, 1194002452706084764L, 7580654747562055347L, + 3755078331700690783L, 6064523798049644277L, 8538085887473418112L, + 4851619038439715422L, 3141119895236824166L, 7762590461503544675L, + 6870466239749873827L, 6210072369202835740L, 5496372991799899062L, + 4968057895362268592L, 4397098393439919250L, 7948892632579629747L, + 8880031836874825961L, 6359114106063703798L, 3414676654757950445L, + 5087291284850963038L, 6421090138548270680L, 8139666055761540861L, + 8429069814306277926L, 6511732844609232689L, 4898581444074067179L, + 5209386275687386151L, 5763539562630208905L, 8335018041099817842L, + 5532314485466423924L, 6668014432879854274L, 736502773631228816L, + 5334411546303883419L, 2433876626275938215L, 8535058474086213470L, + 7583551416783411467L, 6828046779268970776L, 6066841133426729173L, + 5462437423415176621L, 3008798499370428177L, 8739899877464282594L, + 1124728784250774760L, 6991919901971426075L, 2744457434771574970L, + 5593535921577140860L, 2195565947817259976L, 8949657474523425376L, + 3512905516507615961L, 7159725979618740301L, 965650005835137607L, + 5727780783694992240L, 8151217634151930732L, 9164449253911987585L, + 3818576177788313364L, 7331559403129590068L, 3054860942230650691L, + 5865247522503672054L, 6133237568526430876L, 4692198018002937643L, + 6751264462192099863L, 7507516828804700229L, 8957348732136404618L, + 6006013463043760183L, 9010553393080078856L, 4804810770435008147L, + 1674419492351197600L, 7687697232696013035L, 4523745595132871322L, + 6150157786156810428L, 3618996476106297057L, 4920126228925448342L, + 6584545995626947969L, 7872201966280717348L, 3156575963519296104L, + 6297761573024573878L, 6214609585557347207L, 5038209258419659102L, + 8661036483187788089L, 8061134813471454564L, 6478960743616640295L, + 6448907850777163651L, 7027843002264267398L, 5159126280621730921L, + 3777599994440458757L, 8254602048994769474L, 2354811176362823687L, + 6603681639195815579L, 3728523348461214111L, 5282945311356652463L, + 4827493086139926451L, 8452712498170643941L, 5879314530452927160L, + 6762169998536515153L, 2858777216991386566L, 5409735998829212122L, + 5976370588335019576L, 8655577598126739396L, 2183495311852210675L, + 6924462078501391516L, 9125493878965589187L, 5539569662801113213L, + 5455720695801516188L, 8863311460481781141L, 6884478705911470739L, + 7090649168385424913L, 3662908557358221429L, 5672519334708339930L, + 6619675660628487467L, 9076030935533343889L, 1368109020150804139L, + 7260824748426675111L, 2939161623491598473L, 5808659798741340089L, + 506654891422323617L, 4646927838993072071L, 2249998320508814055L, + 7435084542388915313L, 9134020534926967972L, 5948067633911132251L, + 1773193205828708893L, 4758454107128905800L, 8797252194146787761L, + 7613526571406249281L, 4852231473780084609L, 6090821257124999425L, + 2037110771653112526L, 4872657005699999540L, 1629688617322490021L, + 7796251209119999264L, 2607501787715984033L, 6237000967295999411L, + 3930675837543742388L, 4989600773836799529L, 1299866262664038749L, + 7983361238138879246L, 5769134835004372321L, 6386688990511103397L, + 2770633460632542696L, 5109351192408882717L, 7750529990618899641L, + 8174961907854212348L, 5022150355506418780L, 6539969526283369878L, + 7707069099147045347L, 5231975621026695903L, 631632057204770793L, + 8371160993642713444L, 8389308921011453915L, 6696928794914170755L, + 8556121544180118293L, 5357543035931336604L, 6844897235344094635L, + 8572068857490138567L, 5417812354437685931L, 6857655085992110854L, + 644901068808238421L, 5486124068793688683L, 2360595262417545899L, + 8777798510069901893L, 1932278012497118276L, 7022238808055921514L, + 5235171224739604944L, 5617791046444737211L, 6032811387162639117L, + 8988465674311579538L, 5963149404718312264L, 7190772539449263630L, + 8459868338516560134L, 5752618031559410904L, 6767894670813248108L, + 9204188850495057447L, 5294608251188331487L + ) + @volatile private[this] var tenPow18Squares: Array[BigInteger] = Array( + BigInteger.valueOf(1000000000000000000L) + ) + + final private def getTenPow18Squares(n: Int): Array[BigInteger] = { + var ss = tenPow18Squares + var i = ss.length + if (n >= i) { + var s = ss(i - 1) + ss = java.util.Arrays.copyOf(ss, n + 1) + while (i <= n) { + s = s.multiply(s) + ss(i) = s + i += 1 + } + tenPow18Squares = ss + } + ss + } + + /** Checks if a character does not require JSON escaping or encoding. + * + * @param ch + * the character to check + * @return + * `true` if the character is a basic ASCII character (code point less than + * `0x80`) that does not need JSON escaping + */ + final def isNonEscapedAscii(ch: Char): Boolean = + ch < 0x80 && escapedChars(ch.toInt) == 0 +} diff --git a/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala b/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala index e72be059..9497ee1d 100644 --- a/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala @@ -93,6 +93,9 @@ class SimpleTokenWriter extends TokenWriter { this } } + + override def result(): String = + throw new UnsupportedOperationException("SimpleTokenWriter.outString") } object SimpleTokenWriter { diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala index d1172d0a..e4046454 100644 --- a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala @@ -1,32 +1,97 @@ package tethys.writers.tokens trait TokenWriter { + + /** Writes a JSON array start marker [ . */ def writeArrayStart(): this.type + /** Writes a JSON array end marker ] . */ def writeArrayEnd(): this.type + /** Writes a JSON object start marker { . */ def writeObjectStart(): this.type + /** Writes a JSON object end marker } . */ def writeObjectEnd(): this.type + /** Writes a `String` value as a JSON key. + * + * @param name + * the `String` value to write + * @throws TokenWriterException + * if the provided string has an illegal surrogate pair + */ def writeFieldName(name: String): this.type + /** Writes a `String` value as a JSON value. + * + * @param v + * the `String` value to write + * @throws TokenWriterException + * if the provided string has an illegal surrogate pair + */ def writeString(v: String): this.type + /** Writes a `Byte` value as a JSON value. + * + * @param v + * the `Byte` value to write + */ def writeNumber(v: Byte): this.type + /** Writes a `Short` value as a JSON value. + * + * @param v + * the `Short` value to write + */ def writeNumber(v: Short): this.type + /** Writes a `Int` value as a JSON value. + * + * @param v + * the `Int` value to write + */ def writeNumber(v: Int): this.type + /** Writes a `Long` value as a JSON value. + * + * @param v + * the `Long` value to write + */ def writeNumber(v: Long): this.type - def writeNumber(v: BigInt): this.type - + /** Writes a `Double` value as a JSON value. + * + * @param v + * the `Double` value to write + * + * @throws TokenWriterException + * if the value is non-finite + */ def writeNumber(v: Double): this.type + /** Writes a `Float` value as a JSON value. + * + * @param v + * the `Float` value to write + * + * @throws TokenWriterException + * if the value is non-finite + */ def writeNumber(v: Float): this.type + /** Writes a `BigInt` value as a JSON value. + * + * @param v + * the `BigInt` value to write + */ + def writeNumber(v: BigInt): this.type + + /** Writes a `BigDecimal` value as a JSON value. + * + * @param v + * the `BigDecimal` value to write + */ def writeNumber(v: BigDecimal): this.type def writeRawNumber(n: Number): this.type = n match { @@ -43,14 +108,22 @@ trait TokenWriter { case num => writeNumber(num.doubleValue()) } + /** Writes a `Boolean` value as a JSON value. + * + * @param v + * the `Boolean` value to write + */ def writeBoolean(v: Boolean): this.type + /** Writes a JSON `null` value. */ def writeNull(): this.type @throws[UnsupportedOperationException] def writeRawJson(json: String): this.type + def flush(): Unit + def close(): Unit - def flush(): Unit + def result(): String } diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterConfig.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterConfig.scala new file mode 100644 index 00000000..81774a8d --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterConfig.scala @@ -0,0 +1,84 @@ +/* + This is copy of jsoniter-scala WriterConfig + https://github.com/plokhotnyuk/jsoniter-scala/blob/master/jsoniter-scala-core/shared/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/WriterConfig.scala + MIT License + + Copyright (c) 2017 Andriy Plokhotnyuk, and respective contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +package tethys.writers.tokens + +/** Configuration for [[tethys.writers.tokens.DefaultTokenWriter]] that contains + * params for formatting of output JSON and for tuning of preferred size for + * internal byte buffer that is created on the writer instantiation and reused + * in runtime for serialization of messages using any type of output except + * pre-allocated byte arrays or heap byte buffers supplied as arguments.
+ * All configuration params already initialized to default values, but in some + * cases they should be altered: + * @param indentionStep + * a size of indention for pretty-printed formatting or 0 for compact output + * @param escapeUnicode + * a flag to turn on hexadecimal escaping of all non-ASCII chars + * @param preferredBufSize + * a preferred size (in bytes) of an internal byte buffer when writing to any + * type of output except pre-allocated byte arrays or heap byte buffers + * supplied as arguments + */ +case class TokenWriterConfig private[tethys] ( + private[tethys] val indentionStep: Int, + private[tethys] val preferredBufSize: Int, + private[tethys] val escapeUnicode: Boolean +) extends Serializable { + + /** Pretty prints output with 2 spaces indentation. Implementation is backend + * specific and result may vary + */ + def withDefaultPrettyPrinter: TokenWriterConfig = + copy(indentionStep = 2) + + /** Escapes all non-ASCII characters. Implementation is backend specific and + * result may vary. + */ + def withEscapeUnicode(escapeUnicode: Boolean): TokenWriterConfig = + copy(escapeUnicode = escapeUnicode) + + /** Sets preferred buffer size. Not all backends support this setting + */ + def withBufferSize(size: Int): TokenWriterConfig = { + if (size <= 0) + throw new IllegalArgumentException( + "'preferredBufSize' should be not less than 1" + ) + copy(preferredBufSize = size) + } +} + +object TokenWriterConfig { + implicit val default: TokenWriterConfig = new TokenWriterConfig( + indentionStep = 0, + preferredBufSize = 32768, + escapeUnicode = false + ) +} diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterException.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterException.scala new file mode 100644 index 00000000..8bfba5ac --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterException.scala @@ -0,0 +1,6 @@ +package tethys.writers.tokens + +class TokenWriterException( + msg: String, + cause: Throwable +) extends RuntimeException(msg, cause, true, true) diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala index a6fbff20..4bfd2110 100644 --- a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala @@ -1,7 +1,20 @@ package tethys.writers.tokens -import java.io.Writer - trait TokenWriterProducer { - def forWriter(writer: Writer): TokenWriter + def produce(config: TokenWriterConfig): TokenWriter +} + +object TokenWriterProducer { + implicit val tethysTokenWriterProducer: TokenWriterProducer = + new TokenWriterProducer { + private val writerPool: ThreadLocal[DefaultTokenWriter] = + new ThreadLocal[DefaultTokenWriter] { + override def initialValue(): DefaultTokenWriter = + new DefaultTokenWriter(config = TokenWriterConfig.default) + } + + override def produce(config: TokenWriterConfig): TokenWriter = + writerPool.get().withConfig(config) + + } } diff --git a/modules/core/src/test/scala/tethys/JsonReaderSpec.scala b/modules/core/src/test/scala/tethys/JsonReaderSpec.scala new file mode 100644 index 00000000..5f00cc8a --- /dev/null +++ b/modules/core/src/test/scala/tethys/JsonReaderSpec.scala @@ -0,0 +1,228 @@ +package tethys + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import tethys.readers.ReaderError +import tethys.readers.tokens.TokenIteratorProducer + +class DefaultJsonReaderSpec extends JsonReaderSpec { + override def producer: TokenIteratorProducer = + TokenIteratorProducer.tethysTokenIteratorProducer +} + +trait JsonReaderSpec extends AnyFlatSpec with Matchers { + behavior of "JsonReader" + + implicit def producer: TokenIteratorProducer + + def testCase[A: JsonReader]( + json: String, + expected: Either[String, A] + )(implicit pos: org.scalactic.source.Position): Unit = + it should s"read $json" in { + json.jsonAs[A].left.map(_.getMessage) shouldBe expected + } + + testCase[Int]("1", Right(1)) + testCase[Int]("-1", Right(-1)) + testCase[Double]("1.5", Right(1.5)) + testCase[Double]("-1.5", Right(-1.5)) + testCase[String]( + "", + Left("Illegal json at '[ROOT]': Expected string value but found: Empty") + ) + testCase[String](""" "" """, Right("")) + testCase[String](""" "1.5" """, Right("1.5")) + + testCase[Boolean]("true", Right(true)) + testCase[Boolean]("false", Right(false)) + testCase[Boolean]( + "\"true\"", + Left( + "Illegal json at '[ROOT]': Expected boolean value but found: StringValueToken" + ) + ) + + testCase[Long]("9223372036854775807", Right(9223372036854775807L)) + testCase[Long]("-9223372036854775808", Right(-9223372036854775808L)) + testCase[BigDecimal]("123.456789", Right(BigDecimal("123.456789"))) + testCase[Float]("3.14", Right(3.14f)) + testCase[Byte]("127", Right(127.toByte)) + testCase[Short]("32767", Right(32767.toShort)) + + testCase[List[Int]]("[1,2,3]", Right(List(1, 2, 3))) + testCase[List[Int]]("[]", Right(List.empty)) + testCase[Vector[String]]("""["a","b","c"]""", Right(Vector("a", "b", "c"))) + testCase[Set[Int]]("[1,2,2,3]", Right(Set(1, 2, 3))) + testCase[Seq[Double]]("[1.1, 2.2, 3.3]", Right(Seq(1.1, 2.2, 3.3))) + + testCase[Map[String, Int]]( + """{"a":1,"b":2}""", + Right(Map("a" -> 1, "b" -> 2)) + ) + + testCase[Option[Int]]("null", Right(None)) + testCase[Option[Int]]("42", Right(Some(42))) + testCase[Option[String]]("\"test\"", Right(Some("test"))) + testCase[Option[List[Int]]]("[3,4,5]", Right(Some(List(3, 4, 5)))) + + testCase[Map[String, List[Option[Int]]]]( + """{"a":[1,null,3],"b":[]}""", + Right(Map("a" -> List(Some(1), None, Some(3)), "b" -> List())) + ) + + testCase[List[Map[String, String]]]( + """[{"k1":"v1"},{"k2":"v2"}]""", + Right(List(Map("k1" -> "v1"), Map("k2" -> "v2"))) + ) + + testCase[Int]( + "\"not_a_number\"", + Left( + "Illegal json at '[ROOT]': Expected int value but found: StringValueToken" + ) + ) + + testCase[List[Int]]( + "[1,\"not_a_number\",3]", + Left( + "Illegal json at '[ROOT][1]': Expected int value but found: StringValueToken" + ) + ) + + testCase[Map[String, Int]]( + """{"a":"not_a_number"}""", + Left( + "Illegal json at '[ROOT].a': Expected int value but found: StringValueToken" + ) + ) + + /* //TODO unify error messages + + testCase[Int]( + "9223372036854775808", + Left( + "Numeric value (9223372036854775808) out of range of int (-2147483648 - 2147483647)\n at [Source: (String)\"9223372036854775808\"; line: 1, column: 20]" + ) + )*/ + + testCase[String]( + "\"\\\"escaped_quotes\\\"\"", + Right("\"escaped_quotes\"") + ) + + testCase[String]( + "\"special\\nchars\\t\\r\"", + Right("special\nchars\t\r") + ) + + testCase[String]( + "\"unicode\\u0026char\"", + Right("unicode&char") + ) + + testCase[List[Int]]( + " [ 1 , 2 , 3 ] ", + Right(List(1, 2, 3)) + ) + + testCase[Map[String, String]]( + """ { "key" : "value" } """, + Right(Map("key" -> "value")) + ) + + testCase[Map[String, List[Map[String, Option[Int]]]]]( + """ + { + "first": [ + {"a": 1, "b": null}, + {"c": 3} + ], + "second": [] + } + """, + Right( + Map( + "first" -> List( + Map("a" -> Some(1), "b" -> None), + Map("c" -> Some(3)) + ), + "second" -> List() + ) + ) + ) + + testCase[Double]("1.23e-4", Right(1.23e-4)) + testCase[Double]("1.23E+4", Right(1.23e+4)) + testCase[BigDecimal]("1.23e-5", Right(BigDecimal("1.23e-5"))) + + testCase[String]( + """[1,"text",true,null,{"k":"v"}]""", + Left( + "Illegal json at '[ROOT]': Expected string value but found: ArrayStartToken" + ) + ) + /* TODO unify error messages + testCase[Map[String, Int]]( + "{a:1}", + Left( + "Unexpected character ('a' (code 97)): was expecting double-quote to start field name\n at [Source: (String)\"{a:1}\"; line: 1, column: 3]" + ) + )*/ + + /* //TODO unify error messages + testCase[List[Int]]( + "[1,2,3", + Left( + "Unexpected end-of-input: expected close marker for Array (start marker at [Source: (String)\"[1,2,3\"; line: 1, column: 1])\n at [Source: (String)\"[1,2,3\"; line: 1, column: 7]" + ) + )*/ + + /* //TODO unify error messages + testCase[Map[String, String]]( + """{"key": "value" "key2": "value2"}""", + Left( + "Unexpected character ('\"' (code 34)): was expecting comma to separate Object entries\n at [Source: (String)\"{\"key\": \"value\" \"key2\": \"value2\"}\"; line: 1, column: 18]" + ) + )*/ + + case class Data( + string: String, + int: Int, + boolean: Boolean, + bigDecimal: BigDecimal, + seqInt: Seq[Int], + mapStringInt: Map[String, Int] + ) + + implicit val dataReader: JsonReader[Data] = JsonReader.builder + .addField[String]("string") + .addField[Int]("int") + .addField[Boolean]("boolean") + .addField[BigDecimal]("bigDecimal") + .addField[Seq[Int]]("seqInt") + .addField[Map[String, Int]]("mapStringInt") + .buildReader(Data(_, _, _, _, _, _)) + + testCase[Data]( + """{ + "string": "first", + "int": 1, + "boolean": true, + "bigDecimal": 11.11, + "seqInt": [1, 2], + "mapStringInt": {"a": 1, "b": 2} + }""", + Right( + Data( + string = "first", + int = 1, + boolean = true, + bigDecimal = BigDecimal(11.11), + seqInt = List(1, 2), + mapStringInt = Map("a" -> 1, "b" -> 2) + ) + ) + ) + +} diff --git a/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala b/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala index 354d12c5..bedf2eea 100644 --- a/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala +++ b/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala @@ -7,7 +7,7 @@ import tethys.commons.{Token, TokenNode} import tethys.commons.TokenNode._ import tethys.readers.DefaultReadersTest.TestDefinition import tethys.readers.tokens._ -import tethys.TokenIteratorOps +import tethys._ import scala.reflect.ClassTag @@ -117,7 +117,8 @@ class DefaultReadersTest extends AnyFlatSpec { case (TestDefinition(result, jsonReader, name), nodes) => it should s"correctly read $name" in { val iterator = QueueIterator(nodes) - iterator.readJson(jsonReader).fold(throw _, identity) shouldBe result + implicit val reader = jsonReader + iterator.readJson.fold(throw _, identity) shouldBe result iterator.currentToken() shouldBe Token.Empty } diff --git a/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala b/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala index 587c0cd5..1866f9f1 100644 --- a/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala +++ b/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala @@ -7,7 +7,7 @@ import tethys.commons.{Token, TokenNode} import tethys.commons.TokenNode._ import tethys.readers.JsonReaderBuilderTest._ import tethys.readers.tokens.QueueIterator -import tethys.TokenIteratorOps +import tethys._ class JsonReaderBuilderTest extends AnyFlatSpec with Matchers { behavior of "JsonReaderBuilder" diff --git a/modules/core/src/test/scala/tethys/readers/tokens/TokenIteratorSpec.scala b/modules/core/src/test/scala/tethys/readers/tokens/TokenIteratorSpec.scala new file mode 100644 index 00000000..cbd471fe --- /dev/null +++ b/modules/core/src/test/scala/tethys/readers/tokens/TokenIteratorSpec.scala @@ -0,0 +1,710 @@ +package tethys.readers.tokens + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import tethys.* + +class DefaultTokenIteratorSpec extends TokenIteratorSpec { + override def producer: TokenIteratorProducer = + TokenIteratorProducer.tethysTokenIteratorProducer +} + +trait TokenIteratorSpec extends AnyFlatSpec with Matchers { + + def producer: TokenIteratorProducer + + behavior of "TokenIterator" + + def testCase( + json: String, + assertions: TokenIterator => Unit + ): Unit = { + it should s"iterate over $json" in { + val iter = json.toTokenIterator(using producer).fold(throw _, identity) + assertions(iter) + } + } + + testCase( + json = """true""", + assertions = iterator => { + iterator.currentToken().isBooleanValue shouldBe true + iterator.boolean() shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """false""", + assertions = iterator => { + iterator.currentToken().isBooleanValue shouldBe true + iterator.boolean() shouldBe false + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """"foo-bar"""", + assertions = iterator => { + iterator.currentToken().isStringValue shouldBe true + iterator.string() shouldBe "foo-bar" + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """["special \"quotes\""]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isStringValue shouldBe true + iterator.string() shouldBe """special "quotes"""" + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """123""", + assertions = iterator => { + iterator.currentToken().isNumberValue shouldBe true + iterator.int() shouldBe 123 + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """-123""", + assertions = iterator => { + iterator.currentToken().isNumberValue shouldBe true + iterator.int() shouldBe -123 + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """-123.5""", + assertions = iterator => { + iterator.currentToken().isNumberValue shouldBe true + iterator.double() shouldBe -123.5 + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """123.5""", + assertions = iterator => { + iterator.currentToken().isNumberValue shouldBe true + iterator.double() shouldBe 123.5 + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """null""", + assertions = iterator => { + iterator.currentToken().isNullValue shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[null]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isNullValue shouldBe true + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[true]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isBooleanValue shouldBe true + iterator.boolean() shouldBe true + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[123]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isNumberValue shouldBe true + iterator.number().longValue() shouldBe 123 + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """["test"]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isStringValue shouldBe true + iterator.string() shouldBe "test" + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[1, 2]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isNumberValue shouldBe true + iterator.int() shouldBe 1 + iterator.nextToken().isNumberValue shouldBe true + iterator.int() shouldBe 2 + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[1, "text", 123.5, true, null]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + + iterator.nextToken().isNumberValue shouldBe true + iterator.number().intValue() shouldBe 1 + + iterator.nextToken().isStringValue shouldBe true + iterator.string() shouldBe "text" + + iterator.nextToken().isNumberValue shouldBe true + iterator.number().doubleValue() shouldBe 123.5 + + iterator.nextToken().isBooleanValue shouldBe true + iterator.boolean() shouldBe true + + iterator.nextToken().isNullValue shouldBe true + + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[[1, 2]]""", + assertions = iterator => { + iterator.currentToken().isArrayStart shouldBe true + iterator.nextToken().isArrayStart shouldBe true + + iterator.nextToken().isNumberValue shouldBe true + iterator.number().intValue() shouldBe 1 + + iterator.nextToken().isNumberValue shouldBe true + iterator.number().intValue() shouldBe 2 + + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isArrayEnd shouldBe true + iterator.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = "{}", + assertions = reader => { + reader.currentToken().isObjectStart shouldBe true + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """{"name": "test"}""", + assertions = reader => { + reader.currentToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "name" + + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "test" + + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = + """{"id": 123, "name": "test", "active": true, "score": 98.6, "tags": null}""", + assertions = reader => { + reader.currentToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "id" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 123 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "name" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "test" + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "active" + reader.nextToken().isBooleanValue shouldBe true + reader.boolean() shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "score" + reader.nextToken().isNumberValue shouldBe true + reader.number().doubleValue() shouldBe 98.6 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "tags" + reader.nextToken().isNullValue shouldBe true + + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """{"user": {"id": 1, "profile": {"name": "John", "age": 30}}}""", + assertions = reader => { + reader.currentToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "user" + + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "id" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 1 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "profile" + + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "name" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "John" + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "age" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 30 + + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[{"id": 1, "value": "first"}, {"id": 2, "value": "second"}]""", + assertions = reader => { + reader.currentToken().isArrayStart shouldBe true + + reader.nextToken().isObjectStart shouldBe true + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "id" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 1 + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "value" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "first" + reader.nextToken().isObjectEnd shouldBe true + + reader.nextToken().isObjectStart shouldBe true + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "id" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 2 + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "value" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "second" + reader.nextToken().isObjectEnd shouldBe true + + reader.nextToken().isArrayEnd shouldBe true + reader.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """{ + "id": 1, + "metadata": { + "created": "2023-01-01", + "tags": ["important", "urgent"] + }, + "data": { + "items": [ + { + "name": "item1", + "properties": { + "color": "red", + "size": 10, + "features": [true, false, null] + } + }, + { + "name": "item2", + "properties": { + "color": "blue", + "size": 20, + "features": [false, true, null] + } + } + ], + "summary": { + "total": 2, + "active": true + } + } + }""", + assertions = reader => { + reader.currentToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "id" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 1 + + // Metadata object + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "metadata" + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "created" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "2023-01-01" + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "tags" + reader.nextToken().isArrayStart shouldBe true + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "important" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "urgent" + reader.nextToken().isArrayEnd shouldBe true + reader.nextToken().isObjectEnd shouldBe true + + // Data object + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "data" + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "items" + reader.nextToken().isArrayStart shouldBe true + + // First item + reader.nextToken().isObjectStart shouldBe true + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "name" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "item1" + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "properties" + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "color" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "red" + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "size" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 10 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "features" + reader.nextToken().isArrayStart shouldBe true + reader.nextToken().isBooleanValue shouldBe true + reader.boolean() shouldBe true + reader.nextToken().isBooleanValue shouldBe true + reader.boolean() shouldBe false + reader.nextToken().isNullValue shouldBe true + reader.nextToken().isArrayEnd shouldBe true + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isObjectEnd shouldBe true + + // Second item + reader.nextToken().isObjectStart shouldBe true + reader.skipExpression() + reader.currentToken().isArrayEnd shouldBe true + // Summary object + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "summary" + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "total" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 2 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "active" + reader.nextToken().isBooleanValue shouldBe true + reader.boolean() shouldBe true + + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isObjectEnd shouldBe true + reader.nextToken().isEmpty shouldBe true + } + ) + + testCase( + json = """[ + { + "string": "first", + "int": 1, + "boolean": true, + "bigDecimal": 11.11, + "seqInt": [1, 2], + "mapStringInt": {"a": 1, "b": 2} + }, + { + "string": "second", + "int": 2, + "boolean": false, + "bigDecimal": 22.22, + "seqInt": [3, 4], + "mapStringInt": {"c": 3, "d": 4} + } + ]""", + assertions = reader => { + reader.currentToken().isArrayStart shouldBe true + + // First Data object + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "string" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "first" + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "int" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 1 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "boolean" + reader.nextToken().isBooleanValue shouldBe true + reader.boolean() shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "bigDecimal" + reader.nextToken().isNumberValue shouldBe true + reader.number().doubleValue() shouldBe 11.11 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "seqInt" + reader.nextToken().isArrayStart shouldBe true + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 1 + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 2 + reader.nextToken().isArrayEnd shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "mapStringInt" + reader.nextToken().isObjectStart shouldBe true + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "a" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 1 + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "b" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 2 + reader.nextToken().isObjectEnd shouldBe true + + reader.nextToken().isObjectEnd shouldBe true + + // Second Data object + reader.nextToken().isObjectStart shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "string" + reader.nextToken().isStringValue shouldBe true + reader.string() shouldBe "second" + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "int" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 2 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "boolean" + reader.nextToken().isBooleanValue shouldBe true + reader.boolean() shouldBe false + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "bigDecimal" + reader.nextToken().isNumberValue shouldBe true + reader.number().doubleValue() shouldBe 22.22 + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "seqInt" + reader.nextToken().isArrayStart shouldBe true + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 3 + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 4 + reader.nextToken().isArrayEnd shouldBe true + + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "mapStringInt" + reader.nextToken().isObjectStart shouldBe true + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "c" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 3 + reader.nextToken().isFieldName shouldBe true + reader.fieldName() shouldBe "d" + reader.nextToken().isNumberValue shouldBe true + reader.number().intValue() shouldBe 4 + reader.nextToken().isObjectEnd shouldBe true + + reader.nextToken().isObjectEnd shouldBe true + + reader.nextToken().isArrayEnd shouldBe true + reader.nextToken().isEmpty shouldBe true + } + ) + +// testCase( +// json = """["escaped\\backslash"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// iterator.string() shouldBe """escaped\backslash""" +// iterator.nextToken().isArrayEnd shouldBe true +// iterator.nextToken().isEmpty shouldBe true +// } +// ) +// +// testCase( +// json = """["unicode\u0020char"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// iterator.string() shouldBe "unicode char" +// iterator.nextToken().isArrayEnd shouldBe true +// iterator.nextToken().isEmpty shouldBe true +// } +// ) +// +// testCase( +// json = """["multi +// line +// string"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// iterator.string() shouldBe "multi\nline\nstring" +// iterator.nextToken().isArrayEnd shouldBe true +// iterator.nextToken().isEmpty shouldBe true +// } +// ) +// +// testCase( +// json = """["special chars: \b\f\n\r\t"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// iterator.string() shouldBe "special chars: \b\f\n\r\t" +// iterator.nextToken().isArrayEnd shouldBe true +// iterator.nextToken().isEmpty shouldBe true +// } +// ) +// +// testCase( +// json = """["\u0001\u001F control chars"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// iterator.string() shouldBe "\u0001\u001F control chars" +// iterator.nextToken().isArrayEnd shouldBe true +// iterator.nextToken().isEmpty shouldBe true +// } +// ) +// +// testCase( +// json = """["emoji: 🚀"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// iterator.string() shouldBe "emoji: 🚀" +// iterator.nextToken().isArrayEnd shouldBe true +// iterator.nextToken().isEmpty shouldBe true +// } +// ) +// +// testCase( +// json = """["mixed: special \"quote\" and unicode \u0020 and emoji 🎉"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// iterator +// .string() shouldBe """mixed: special "quote" and unicode and emoji 🎉""" +// iterator.nextToken().isArrayEnd shouldBe true +// iterator.nextToken().isEmpty shouldBe true +// } +// ) +// +// // Error cases +// testCase( +// json = """["unclosed string""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// assertThrows[RuntimeException] { +// iterator.string() +// } +// } +// ) +// +// testCase( +// json = """["invalid escape \z"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// assertThrows[RuntimeException] { +// iterator.string() +// } +// } +// ) +// +// testCase( +// json = """["invalid unicode \u123"]""", +// assertions = iterator => { +// iterator.currentToken().isArrayStart shouldBe true +// iterator.nextToken().isStringValue shouldBe true +// assertThrows[RuntimeException] { +// iterator.string() +// } +// } +// ) + +} diff --git a/modules/core/src/test/scala/tethys/writers/tokens/DefaultJsonWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/DefaultJsonWriterSpec.scala new file mode 100644 index 00000000..f0e22dcd --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/DefaultJsonWriterSpec.scala @@ -0,0 +1,79 @@ +package tethys.writers.tokens + +import tethys._ + +class DefaultJsonWriterSpec extends JsonWriterSpec { + { + import TestModels._ + + configurableTestCase( + description = "unicode characters in object keys", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Map("café" -> "coffee", "résumé" -> "CV", "über" -> "super"), + json = "{\"caf\\u00e9\":\"coffee\",\"r\\u00e9sum\\u00e9\":\"CV\",\"\\u00fcber\":\"super\"}" + ) + + case class MenuItem(name: String, price: Double) + case class Menu(items: List[MenuItem]) + case class Restaurant(menu: Menu) + + implicit val menuItemWriter: JsonWriter[MenuItem] = JsonWriter.obj[MenuItem] + .addField("name")(_.name) + .addField("price")(_.price) + + implicit val menuWriter: JsonWriter[Menu] = JsonWriter.obj[Menu] + .addField("items")(_.items) + + implicit val restaurantWriter: JsonWriter[Restaurant] = JsonWriter.obj[Restaurant] + .addField("menu")(_.menu) + + configurableTestCase( + description = "unicode characters in nested objects", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Restaurant(Menu(List( + MenuItem("Café au lait", 3.50), + MenuItem("Crème brûlée", 5.75) + ))), + json = "{\"menu\":{\"items\":[{\"name\":\"Caf\\u00e9 au lait\",\"price\":3.5},{\"name\":\"Cr\\u00e8me br\\u00fbl\\u00e9e\",\"price\":5.75}]}}" + ) + + configurableTestCase( + description = "mixed ASCII and unicode in arrays", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = List("hello", "世界", "こんにちは", "안녕하세요"), + json = "[\"hello\",\"\\u4e16\\u754c\",\"\\u3053\\u3093\\u306b\\u3061\\u306f\",\"\\uc548\\ub155\\ud558\\uc138\\uc694\"]" + ) + + configurableTestCase( + description = "control characters that should always be escaped", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Control chars: \u0000\u0001\u0008\u0009\u000A\u000C\u000D\u001F", + json = "\"Control chars: \\u0000\\u0001\\b\\t\\n\\f\\r\\u001f\"" + ) + + configurableTestCase( + description = "unicode characters with escapeUnicode set to false", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: © € ☃\"" + ) + + case class SurrogatePairsTest(text: String, list: List[String], keyValue: (String, String)) + + implicit val surrogatePairsTestWriter: JsonWriter[SurrogatePairsTest] = JsonWriter.obj[SurrogatePairsTest] + .addField("text")(_.text) + .addField("list")(_.list) + .addField("nested")(test => Map(test.keyValue)) + + configurableTestCase( + description = "surrogate pairs in different contexts", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = SurrogatePairsTest( + "Surrogate pairs: 𝄞 𝌆 𝓐", + List("𝄞", "𝌆", "𝓐"), + ("key𝄞", "value𝌆") + ), + json = "{\"text\":\"Surrogate pairs: \\ud834\\udd1e \\ud834\\udf06 \\ud835\\udcd0\",\"list\":[\"\\ud834\\udd1e\",\"\\ud834\\udf06\",\"\\ud835\\udcd0\"],\"nested\":{\"key\\ud834\\udd1e\":\"value\\ud834\\udf06\"}}" + ) + } +} \ No newline at end of file diff --git a/modules/core/src/test/scala/tethys/writers/tokens/DefaultTokenWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/DefaultTokenWriterSpec.scala new file mode 100644 index 00000000..e8a50bc2 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/DefaultTokenWriterSpec.scala @@ -0,0 +1,3 @@ +package tethys.writers.tokens + +class DefaultTokenWriterSpec extends TokenWriterSpec diff --git a/modules/core/src/test/scala/tethys/writers/tokens/JsonWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/JsonWriterSpec.scala new file mode 100644 index 00000000..23116868 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/JsonWriterSpec.scala @@ -0,0 +1,293 @@ +package tethys.writers.tokens + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import tethys._ + +abstract class JsonWriterSpec(implicit producer: TokenWriterProducer) + extends AnyFlatSpec + with Matchers { + + import TestModels._ + import java.time.{LocalDate, LocalDateTime, Duration} + import java.util.UUID + + def testCase[A: JsonWriter]( + value: A, + json: String + )(implicit + config: TokenWriterConfig, + pos: org.scalactic.source.Position + ): Unit = + it should s"write $value as $json" in { + value.asJson.filterNot(Set('\n', '\t', ' ')) shouldBe json.filterNot( + Set('\n', '\t', ' ') + ) + } + + def configurableTestCase[A: JsonWriter]( + description: String, + config: TokenWriterConfig, + value: A, + json: String + )(implicit + pos: org.scalactic.source.Position + ): Unit = + it should description in { + implicit val cfg: TokenWriterConfig = config + value.asJson shouldBe json + } + + testCase( + value = 2, + json = "2" + ) + + testCase( + value = 1L, + json = "1" + ) + + testCase( + value = 1.0, + json = "1.0" + ) + + testCase( + value = "мама", + json = """"мама"""" + ) + + testCase( + value = true, + json = "true" + ) + + testCase( + value = false, + json = "false" + ) + + testCase( + value = null, + json = "null" + ) + + testCase( + value = List(1, 2, 3), + json = "[1,2,3]" + ) + + testCase( + value = Map("a" -> 1, "b" -> 2), + json = """{"a":1,"b":2}""" + ) + + testCase( + value = Option(1), + json = "1" + ) + + testCase( + value = Option.empty[Int], + json = "null" + ) + + testCase( + value = Person("John Doe", 30, Some("john@example.com")), + json = """ + { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }""" + ) + + testCase( + value = Person("Jane Doe", 25, None), + json = """ + { + "name": "Jane Doe", + "age": 25 + }""" + ) + + val address = Address("123 Main St", "New York", "USA", Some("10001")) + val person = Person("John Doe", 30, Some("john@example.com")) + + testCase( + value = + Employee(1L, person, address, "Engineering", BigDecimal("100000.00")), + json = """ + { + "id": 1, + "person": { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }, + "address": { + "street": "123 Main St", + "city": "New York", + "country": "USA", + "postalCode": "10001" + }, + "department": "Engineering", + "salary": 100000.00 + }""" + ) + + testCase( + value = Department( + "Engineering", + List( + Employee(1L, person, address, "Engineering", BigDecimal("100000.00")) + ), + Set("tech", "development"), + Map("location" -> "Floor 3", "manager" -> "Jane Smith") + ), + json = """ + { + "name": "Engineering", + "employees": [{ + "id": 1, + "person": { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }, + "address": { + "street": "123 Main St", + "city": "New York", + "country": "USA", + "postalCode": "10001" + }, + "department": "Engineering", + "salary": 100000.00 + }], + "tags": ["tech", "development"], + "metadata": { + "location": "Floor 3", + "manager": "Jane Smith" + } + }""" + ) + + testCase( + value = Container("123", person, 1234567890L), + json = """ + { + "id": "123", + "data": { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }, + "timestamp": 1234567890 + }""" + ) + + testCase( + value = CustomTypes( + UUID.fromString("550e8400-e29b-41d4-a716-446655440000"), + LocalDate.of(2023, 1, 1), + LocalDateTime.of(2023, 1, 1, 12, 0), + BigInt("123456789"), + BigDecimal("123456.789"), + Duration.ofHours(24) + ), + json = + """{"uuid":"550e8400-e29b-41d4-a716-446655440000","date":"2023-01-01","datetime":"2023-01-01T12:00","bigInt":123456789,"bigDecimal":123456.789,"duration":"PT24H"}""" + ) + + testCase( + value = Department("Empty", List(), Set(), Map()), + json = """{"name":"Empty","employees":[],"tags":[],"metadata":{}}""" + ) + + testCase( + value = Container("empty", List[String](), 0L), + json = """{"id":"empty","data":[],"timestamp":0}""" + ) + + testCase( + value = Department( + "Global Engineering", + List( + Employee( + 1L, + Person("John Doe", 30, Some("john@example.com")), + Address("123 Main St", "New York", "USA", Some("10001")), + "Backend", + BigDecimal("100000.00") + ), + Employee( + 2L, + Person("Jane Smith", 28, None), + Address("456 Park Ave", "Boston", "USA", None), + "Frontend", + BigDecimal("95000.50") + ) + ), + Set("global", "engineering", "tech"), + Map( + "headquarters" -> "New York", + "founded" -> "2020", + "status" -> "active" + ) + ), + json = """ + |{ + | "name": "Global Engineering", + | "employees": [ + | { + | "id": 1, + | "person": { + | "name": "John Doe", + | "age": 30, + | "email": "john@example.com" + | }, + | "address": { + | "street": "123 Main St", + | "city": "New York", + | "country": "USA", + | "postalCode": "10001" + | }, + | "department": "Backend", + | "salary": 100000.00 + | }, + | { + | "id": 2, + | "person": { + | "name": "Jane Smith", + | "age": 28 + | }, + | "address": { + | "street": "456 Park Ave", + | "city": "Boston", + | "country": "USA" + | }, + | "department": "Frontend", + | "salary": 95000.50 + | } + | ], + | "tags": ["global", "engineering", "tech"], + | "metadata": { + | "headquarters": "New York", + | "founded": "2020", + | "status": "active" + | } + |} + |""".stripMargin + ) + + testCase( + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: © € ☃\"" + ) + + testCase( + value = "Emoji: 😀 🚀 🌍", + json = "\"Emoji: 😀 🚀 🌍\"" + ) +} diff --git a/modules/core/src/test/scala/tethys/writers/tokens/TestModels.scala b/modules/core/src/test/scala/tethys/writers/tokens/TestModels.scala new file mode 100644 index 00000000..29d31114 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/TestModels.scala @@ -0,0 +1,120 @@ +package tethys.writers.tokens + +import tethys.JsonWriter + +import java.time.{Duration, LocalDate, LocalDateTime} +import java.util.UUID + +object TestModels { + + implicit val uuidWriter: JsonWriter[UUID] = + JsonWriter.stringWriter.contramap(_.toString) + + implicit val localDateWriter: JsonWriter[LocalDate] = + JsonWriter.stringWriter.contramap(_.toString) + + implicit val localDateTimeWriter: JsonWriter[LocalDateTime] = + JsonWriter.stringWriter.contramap(_.toString) + + implicit val durationWriter: JsonWriter[Duration] = + JsonWriter.stringWriter.contramap(_.toString) + + case class Person(name: String, age: Int, email: Option[String]) + + object Person { + implicit val personWriter: JsonWriter[Person] = JsonWriter + .obj[Person] + .addField("name")(_.name) + .addField("age")(_.age) + .addField("email")(_.email) + } + case class Address( + street: String, + city: String, + country: String, + postalCode: Option[String] + ) + + object Address { + implicit val addressWriter: JsonWriter[Address] = JsonWriter + .obj[Address] + .addField("street")(_.street) + .addField("city")(_.city) + .addField("country")(_.country) + .addField("postalCode")(_.postalCode) + } + + case class Employee( + id: Long, + person: Person, + address: Address, + department: String, + salary: BigDecimal + ) + + object Employee { + implicit val employeeWriter: JsonWriter[Employee] = JsonWriter + .obj[Employee] + .addField("id")(_.id) + .addField("person")(_.person) + .addField("address")(_.address) + .addField("department")(_.department) + .addField("salary")(_.salary) + } + + case class Department( + name: String, + employees: List[Employee], + tags: Set[String], + metadata: Map[String, String] + ) + + object Department { + implicit val departmentWriter: JsonWriter[Department] = JsonWriter + .obj[Department] + .addField("name")(_.name) + .addField("employees")(_.employees) + .addField("tags")(_.tags) + .addField("metadata")(_.metadata) + } + + case class Container[T]( + id: String, + data: T, + timestamp: Long + ) + + object Container { + implicit def containerWriter[T: JsonWriter]: JsonWriter[Container[T]] = + JsonWriter + .obj[Container[T]] + .addField("id")(_.id) + .addField("data")(_.data) + .addField("timestamp")(_.timestamp) + } + + case class TreeNode( + value: String, + children: List[TreeNode] + ) + + case class CustomTypes( + uuid: java.util.UUID, + date: java.time.LocalDate, + datetime: java.time.LocalDateTime, + bigInt: BigInt, + bigDecimal: BigDecimal, + duration: java.time.Duration + ) + + object CustomTypes { + implicit val customTypesWriter: JsonWriter[CustomTypes] = JsonWriter + .obj[CustomTypes] + .addField("uuid")(_.uuid) + .addField("date")(_.date) + .addField("datetime")(_.datetime) + .addField("bigInt")(_.bigInt) + .addField("bigDecimal")(_.bigDecimal) + .addField("duration")(_.duration) + } +} diff --git a/modules/core/src/test/scala/tethys/writers/tokens/TokenWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/TokenWriterSpec.scala new file mode 100644 index 00000000..cdad18b6 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/TokenWriterSpec.scala @@ -0,0 +1,246 @@ +package tethys.writers.tokens + +import org.scalatest.flatspec.AnyFlatSpecLike +import org.scalatest.matchers.should.Matchers + +abstract class TokenWriterSpec(implicit producer: TokenWriterProducer) + extends AnyFlatSpecLike + with Matchers { + + behavior of "TokenWriter" + + def testCase( + json: String, + iterate: TokenWriter => Unit + ): Unit = { + it should s"write $json" in { + val writer = producer.produce(TokenWriterConfig.default) + iterate(writer) + writer.flush() + writer.result() shouldBe json + } + } + + testCase( + json = """true""", + iterate = _.writeBoolean(true) + ) + + testCase( + json = """false""", + iterate = _.writeBoolean(false) + ) + + testCase( + json = """null""", + iterate = _.writeNull() + ) + + testCase( + json = """"hello"""", + iterate = _.writeString("hello") + ) + + testCase( + json = """"special chars: \"\\\n\r\t\b\f"""", + iterate = _.writeString("special chars: \"\\\n\r\t\b\f") + ) + + testCase( + json = """42""", + iterate = _.writeNumber(42) + ) + + testCase( + json = """42.5""", + iterate = _.writeNumber(42.5) + ) + + testCase( + json = """9223372036854775807""", + iterate = _.writeNumber(Long.MaxValue) + ) + + testCase( + json = """3.141592653589793""", + iterate = _.writeNumber(Math.PI) + ) + + testCase( + json = """1.23456789E10""", + iterate = _.writeNumber(1.23456789e+10) + ) + + testCase( + json = """{}""", + iterate = _.writeObjectStart().writeObjectEnd() + ) + + testCase( + json = """[]""", + iterate = _.writeArrayStart().writeArrayEnd() + ) + + testCase( + json = """{"name":"John","age":30}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("name") + writer.writeString("John") + writer.writeFieldName("age") + writer.writeNumber(30) + writer.writeObjectEnd() + } + ) + + testCase( + json = """[1,2,3,4,5]""", + iterate = writer => { + writer.writeArrayStart() + for (i <- 1 to 5) writer.writeNumber(i) + writer.writeArrayEnd() + } + ) + + testCase( + json = """[1,"two",true,null,3.14]""", + iterate = writer => { + writer.writeArrayStart() + writer.writeNumber(1) + writer.writeString("two") + writer.writeBoolean(true) + writer.writeNull() + writer.writeNumber(3.14) + writer.writeArrayEnd() + } + ) + + testCase( + json = + """{"person":{"name":"John","age":30,"address":{"city":"New York","zip":"10001"}}}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("person") + writer.writeObjectStart() + writer.writeFieldName("name") + writer.writeString("John") + writer.writeFieldName("age") + writer.writeNumber(30) + writer.writeFieldName("address") + writer.writeObjectStart() + writer.writeFieldName("city") + writer.writeString("New York") + writer.writeFieldName("zip") + writer.writeString("10001") + writer.writeObjectEnd() + writer.writeObjectEnd() + writer.writeObjectEnd() + } + ) + + testCase( + json = """[{"id":1,"value":"first"},{"id":2,"value":"second"}]""", + iterate = writer => { + writer.writeArrayStart() + for ((id, value) <- List((1, "first"), (2, "second"))) { + writer.writeObjectStart() + writer.writeFieldName("id") + writer.writeNumber(id) + writer.writeFieldName("value") + writer.writeString(value) + writer.writeObjectEnd() + } + writer.writeArrayEnd() + } + ) + + testCase( + json = + """{"data":{"users":[{"id":1,"name":"John","active":true,"scores":[10,20,30]},{"id":2,"name":"Jane","active":false,"scores":[15,25,35]}],"metadata":{"count":2,"version":"1.0"}}}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("data") + writer.writeObjectStart() + + writer.writeFieldName("users") + writer.writeArrayStart() + + // First user + writer.writeObjectStart() + writer.writeFieldName("id") + writer.writeNumber(1) + writer.writeFieldName("name") + writer.writeString("John") + writer.writeFieldName("active") + writer.writeBoolean(true) + writer.writeFieldName("scores") + writer.writeArrayStart() + writer.writeNumber(10) + writer.writeNumber(20) + writer.writeNumber(30) + writer.writeArrayEnd() + writer.writeObjectEnd() + + writer.writeObjectStart() + writer.writeFieldName("id") + writer.writeNumber(2) + writer.writeFieldName("name") + writer.writeString("Jane") + writer.writeFieldName("active") + writer.writeBoolean(false) + writer.writeFieldName("scores") + writer.writeArrayStart() + writer.writeNumber(15) + writer.writeNumber(25) + writer.writeNumber(35) + writer.writeArrayEnd() + writer.writeObjectEnd() + + writer.writeArrayEnd() + + writer.writeFieldName("metadata") + writer.writeObjectStart() + writer.writeFieldName("count") + writer.writeNumber(2) + writer.writeFieldName("version") + writer.writeString("1.0") + writer.writeObjectEnd() + + writer.writeObjectEnd() + writer.writeObjectEnd() + } + ) + + testCase( + json = """{"raw":{"some":"json"}}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("raw") + writer.writeRawJson("""{"some":"json"}""") + writer.writeObjectEnd() + } + ) + + testCase( + json = """""""", + iterate = _.writeString("") + ) + + testCase( + json = + """{"empty_string":"","empty_array":[],"empty_object":{},"null_value":null}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("empty_string") + writer.writeString("") + writer.writeFieldName("empty_array") + writer.writeArrayStart().writeArrayEnd() + writer.writeFieldName("empty_object") + writer.writeObjectStart().writeObjectEnd() + writer.writeFieldName("null_value") + writer.writeNull() + writer.writeObjectEnd() + } + ) + +} diff --git a/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala b/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala index 1b7cc6f8..e9ab7802 100644 --- a/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala +++ b/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala @@ -7,7 +7,7 @@ import tethys.commons.{Token, TokenNode} import tethys.commons.TokenNode._ import tethys.derivation.auto._ import tethys.readers.tokens.QueueIterator -import tethys.TokenIteratorOps +import tethys._ class AutoReaderDerivationTest extends AnyFlatSpec with Matchers { diff --git a/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala b/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala index c0518ea7..920bee59 100644 --- a/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala +++ b/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala @@ -9,7 +9,7 @@ import tethys.derivation.RedundantJsonReaderTest.* import tethys.derivation.semiauto.* import tethys.readers.tokens.QueueIterator -import tethys.TokenIteratorOps +import tethys._ object RedundantJsonReaderTest { case class RedundantClass(i: Int)