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

scala.meta.jsonrpc.BaseProtocolMessageParser.scala Maven / Gradle / Ivy

The newest version!
package scala.meta.jsonrpc

import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.Future
import monix.execution.Ack
import monix.execution.Scheduler
import monix.reactive.observables.ObservableLike.Operator
import monix.reactive.observers.Subscriber
import scribe.LoggerSupport

final class BaseProtocolMessageParser(logger: LoggerSupport)
    extends Operator[ByteBuffer, BaseProtocolMessage] {
  override def apply(
      out: Subscriber[BaseProtocolMessage]
  ): Subscriber[ByteBuffer] = {
    new Subscriber[ByteBuffer] {
      import Ack._
      // NOTE(olafur): We should first benchmark before going into any
      // optimization, but my intuition tells me ArrayBuffer[Byte] with many .remove
      // and ++= is wasteful and can probably be replaced with a Queue[ByteBuffer]
      private[this] val data = ArrayBuffer.empty[Byte]
      private[this] var contentLength = -1
      private[this] var header = Map.empty[String, String]
      private[this] def atDelimiter(idx: Int): Boolean = {
        data.size >= idx + 4 &&
        data(idx) == '\r' &&
        data(idx + 1) == '\n' &&
        data(idx + 2) == '\r' &&
        data(idx + 3) == '\n'
      }
      private[this] val EmptyPair = "" -> ""
      private[this] def readHeaders(): Future[Ack] = {
        if (data.size < 4) Continue
        else {
          var i = 0
          while (i + 4 < data.size && !atDelimiter(i)) {
            i += 1
          }
          if (!atDelimiter(i)) Continue
          else {
            val bytes = new Array[Byte](i)
            data.copyToArray(bytes)
            data.remove(0, i + 4)
            val headers = new String(bytes, StandardCharsets.US_ASCII)
            // NOTE(olafur) all LSP messages from vscode seem to start with
            // Content-Length: N and include no other headers.
            // However, the spec says there can be other headers so we parse them
            // here anyways, even if we don't use any other values than Content-Length.
            val pairs: Map[String, String] = headers
              .split("\r\n")
              .iterator
              .filterNot(_.trim.isEmpty)
              .map { line =>
                line.split(":") match {
                  case Array(key, value) => key.trim -> value.trim
                  case _ =>
                    logger.error(s"Malformed input: $line")
                    EmptyPair
                }
              }
              .toMap
            pairs.get("Content-Length") match {
              case Some(n) =>
                try {
                  contentLength = n.toInt
                  header = pairs
                  readContent()
                } catch {
                  case _: NumberFormatException =>
                    logger.error(
                      s"Expected Content-Length to be a number, obtained $n"
                    )
                    Continue
                }
              case _ =>
                logger.error(s"Missing Content-Length key in headers $pairs")
                Continue
            }
          }
        }
      }
      private[this] def readContent(): Future[Ack] = {
        if (contentLength > data.size) Continue
        else {
          val contentBytes = new Array[Byte](contentLength)
          data.copyToArray(contentBytes)
          data.remove(0, contentLength)
          contentLength = -1
          val message = new BaseProtocolMessage(header, contentBytes)
          out.onNext(message).flatMap {
            case Continue => readHeaders()
            case Stop => Stop
          }
        }
      }
      override implicit val scheduler: Scheduler = out.scheduler
      override def onError(ex: Throwable): Unit = out.onError(ex)
      override def onComplete(): Unit = {
        out.onComplete()
      }
      override def onNext(elem: ByteBuffer): Future[Ack] = {
        val array = new Array[Byte](elem.remaining())
        elem.get(array)
        data ++= array
        if (contentLength < 0) readHeaders()
        else readContent()
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy