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

org.mdedetrich.pekko.json.stream.JsonStreamParser.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 – 2018 Paul Horn
 * Copyright 2018 – 2021 Matthew de Detrich
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mdedetrich.pekko.json.stream

import org.apache.pekko
import pekko.NotUsed
import pekko.stream.Attributes.name
import pekko.stream.scaladsl.{Flow, Keep, Sink}
import pekko.stream.stage.{GraphStage, GraphStageLogic, InHandler, OutHandler}
import pekko.stream._
import pekko.util.ByteString
import org.typelevel.jawn.AsyncParser.ValueStream
import org.typelevel.jawn._

import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
import scala.util.Try
import java.nio.ByteBuffer

object JsonStreamParser {

  private[this] val jsonStream = name("json-stream")

  def apply[J: Facade]: Graph[FlowShape[ByteString, J], NotUsed] =
    apply[J](ValueStream)

  def apply[J: Facade](mode: AsyncParser.Mode): Graph[FlowShape[ByteString, J], NotUsed] =
    new JsonStreamParser(mode, multiValue = false)

  def apply[J: Facade](mode: AsyncParser.Mode, multiValue: Boolean): Graph[FlowShape[ByteString, J], NotUsed] =
    new JsonStreamParser(mode, multiValue)

  def flow[J: Facade]: Flow[ByteString, J, NotUsed] =
    Flow.fromGraph(apply[J]).withAttributes(jsonStream)

  def flow[J: Facade](mode: AsyncParser.Mode): Flow[ByteString, J, NotUsed] =
    Flow.fromGraph(apply[J](mode)).withAttributes(jsonStream)

  def flow[J: Facade](mode: AsyncParser.Mode, multiValue: Boolean): Flow[ByteString, J, NotUsed] =
    Flow.fromGraph(apply[J](mode, multiValue)).withAttributes(jsonStream)

  def head[J: Facade]: Sink[ByteString, Future[J]] =
    flow.toMat(Sink.head)(Keep.right)

  def head[J: Facade](mode: AsyncParser.Mode): Sink[ByteString, Future[J]] =
    flow(mode).toMat(Sink.head)(Keep.right)

  def headOption[J: Facade]: Sink[ByteString, Future[Option[J]]] =
    flow.toMat(Sink.headOption)(Keep.right)

  def headOption[J: Facade](mode: AsyncParser.Mode): Sink[ByteString, Future[Option[J]]] =
    flow(mode).toMat(Sink.headOption)(Keep.right)

  def parse[J: Facade](bytes: ByteString): Try[J] =
    Parser.parseFromByteBuffer(bytes.asByteBuffer)

  private final class ParserLogic[J: Facade](parser: AsyncParser[J], shape: FlowShape[ByteString, J])
      extends GraphStageLogic(shape) {
    private[this] val in      = shape.in
    private[this] val out     = shape.out
    private[this] val scratch = new ArrayBuffer[J](64)

    setHandler(out,
               new OutHandler {
                 override def onPull(): Unit                                 = pull(in)
                 override def onDownstreamFinish(throwable: Throwable): Unit = downstreamFinish()
               }
    )
    setHandler(in,
               new InHandler {
                 override def onPush(): Unit           = upstreamPush()
                 override def onUpstreamFinish(): Unit = finishParser()
               }
    )

    private def upstreamPush(): Unit = {
      scratch.clear()
      val input = grab(in).asByteBuffers
      emitOrPullLoop(input.iterator, scratch)
    }

    private def downstreamFinish(): Unit = {
      parser.finish()
      cancel(in)
    }

    private def finishParser(): Unit =
      parser.finish() match {
        case Left(ParseException("exhausted input", _, _, _)) => complete(out)
        case Left(e)                                          => failStage(e)
        case Right(jsons)                                     => emitMultiple(out, jsons.iterator, () => complete(out))
      }

    @tailrec
    private[this] def emitOrPullLoop(bs: Iterator[ByteBuffer], results: ArrayBuffer[J]): Unit =
      if (bs.hasNext) {
        val next   = bs.next()
        val absorb = parser.absorb(next)
        absorb match {
          case Left(e) => failStage(e)
          case Right(jsons) =>
            if (jsons.nonEmpty) {
              results ++= jsons
            }
            emitOrPullLoop(bs, results)
        }
      } else {
        if (results.nonEmpty) {
          emitMultiple(out, results.iterator)
        } else {
          pull(in)
        }
      }
  }
}

final class JsonStreamParser[J: Facade] private (mode: AsyncParser.Mode, multiValue: Boolean)
    extends GraphStage[FlowShape[ByteString, J]] {
  private[this] val in  = Inlet[ByteString]("Json.in")
  private[this] val out = Outlet[J]("Json.out")
  override val shape    = FlowShape(in, out)
  override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
    new JsonStreamParser.ParserLogic[J](AsyncParser[J](mode, multiValue), shape)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy