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

loci.messaging.Message.scala Maven / Gradle / Ivy

The newest version!
package loci
package messaging

import scala.util.Try

case class Message[M: Message.Method](
    method: M,
    properties: Map[String, Seq[String]],
    payload: MessageBuffer) {
  override def toString: String = {
    val builder = new StringBuilder

    builder ++= "["
    builder ++= implicitly[Message.Method[M]].apply(method)

    if (properties.nonEmpty) {
      builder ++= " {"
      val entries = properties map { case (key, values) => s"$key: ${values mkString ","}" }
      builder ++= entries mkString "; "
      builder ++= "}"
    }

    if (payload.nonEmpty) {
      builder ++= ": "
      builder ++= payload.toString
    }

    builder ++= "]"
    builder.toString
  }
}

object Message {
  trait Method[M] {
    def apply(method: M): String
    def apply(method: String): Option[M]
  }

  object Method {
    def apply[M](methods: (M, String)*) = new Method[M] {
      def apply(method: M) = methods collectFirst {
        case (`method`, value) => value
      } getOrElse {
        throw new IllegalArgumentException(
          s"No string representation for method $method")
      }

      def apply(method: String) = methods collectFirst {
        case (value, `method`) => value
      }
    }
  }

  class Exception(msg: String) extends IllegalArgumentException(msg)

  def serialize[M: Method](message: Message[M]): MessageBuffer = {
    val builder = new StringBuilder

    builder ++= implicitly[Method[M]].apply(message.method)
    builder ++= "\r\n"

    message.properties foreach { case (key, values) =>
      values foreach { value =>
        builder ++= s"$key: $value\r\n"
      }
    }

    if (message.payload.nonEmpty) {
      builder ++= "\r\n"
      (MessageBuffer encodeString builder.toString) concat message.payload
    }
    else
      MessageBuffer encodeString builder.toString
  }

  def deserialize[M: Method](buffer: MessageBuffer): Try[Message[M]] = Try {
    val (method, properties, payload) = parse(buffer)
    Message(
      implicitly[Method[M]].apply(method) match {
        case Some(method) => method
        case _ => throwInvalidMethod
      },
      properties,
      payload)
  }

  private def throwNoMethod =
    throw new Exception("Invalid message: no method")
  private def throwInvalidMethod =
    throw new Exception("Invalid message: invalid method")
  private def throwInvalidLineBreak =
    throw new Exception("Invalid message: invalid line break")
  private def throwEmptyKey =
    throw new Exception("Invalid message: empty key")
  private def throwEmptyValue =
    throw new Exception("Invalid message: empty value")
  private def throwMissingValue =
    throw new Exception("Invalid message: missing value")

  private def parse(buffer: MessageBuffer) = {
    val stateHeader = 0
    val stateBeforeKey = 1
    val stateKey = 2
    val stateValue = 3
    val stateBeforePayload = 4
    val statePayload = 5

    var state = stateHeader
    var offset = 0
    var mark = 0
    var method = ""
    var key = ""
    var payload = MessageBuffer.empty
    var properties = Map.empty[String, Seq[String]]

    def parseString = buffer.decodeString(mark, offset - mark - 1)

    def makeMethod() = {
      method = parseString.trim
      if (method.isEmpty)
        throwNoMethod
    }

    def makeKey() = {
      key = parseString.trim
      if (key.isEmpty)
        throwEmptyKey
    }

    def makeProperty() = {
      val value = parseString.trim
      properties += key -> (properties.getOrElse(key, Seq.empty) :+ value)
      if (value.isEmpty)
        throwEmptyValue
    }

    def makePayload() =
      payload = buffer.copy(mark, buffer.length - mark)

    while (offset < buffer.size && state != statePayload) {
      val ch = buffer(offset)
      offset += 1

      state match {
        case `stateHeader` =>
          ch match {
            case '\r' =>
              makeMethod()
              state = stateBeforeKey
            case _  =>
          }

        case `stateBeforeKey` =>
          ch match {
            case '\n' => state = stateKey; mark = offset
            case _  => throwInvalidLineBreak
          }

        case `stateKey` =>
          ch match {
            case '\r' =>
              if (parseString.trim.nonEmpty)
                throwMissingValue
              state = stateBeforePayload
            case ':' =>
              makeKey()
              state = stateValue; mark = offset
            case _  =>
          }

        case `stateValue` =>
          ch match {
            case '\r' =>
              makeProperty()
              state = stateBeforeKey
            case _  =>
          }

        case `stateBeforePayload` =>
          ch match {
            case '\n' => state = statePayload; mark = offset
            case _  => throwInvalidLineBreak
          }

        case _ =>
          assert(assertion = false, "invalid state")
      }
    }

    offset += 1

    state match {
      case `stateHeader` => makeMethod()
      case `stateKey` => if (parseString.trim.nonEmpty) throwMissingValue
      case `stateValue` => makeProperty()
      case `statePayload` => makePayload()
      case `stateBeforeKey` | `stateBeforePayload` =>
      case _ => assert(assertion = false, "invalid state")
    }

    (method, properties, payload)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy