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

com.twitter.finagle.memcached.protocol.text.client.Decoder.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle.memcached.protocol.text.client

import org.jboss.netty.channel._
import com.twitter.util.StateMachine
import org.jboss.netty.buffer.ChannelBuffer
import com.twitter.finagle.memcached.util.ChannelBufferUtils._
import com.twitter.finagle.memcached.protocol.ServerError
import com.twitter.finagle.memcached.util.ParserUtils
import com.twitter.finagle.memcached.protocol.text._

object Decoder {
  private val END   = "END": ChannelBuffer
  private val ITEM  = "ITEM": ChannelBuffer
  private val STAT  = "STAT": ChannelBuffer
  private val VALUE = "VALUE": ChannelBuffer
}

class Decoder extends AbstractDecoder with StateMachine {
  import Decoder._

  case class AwaitingResponse()                                                  extends State
  case class AwaitingResponseOrEnd(valuesSoFar: Seq[TokensWithData])             extends State
  case class AwaitingStatsOrEnd(valuesSoFar: Seq[Tokens])                        extends State
  case class AwaitingData(valuesSoFar: Seq[TokensWithData], tokens: Seq[ChannelBuffer], bytesNeeded: Int) extends State

  final protected[memcached] def start() {
    state = AwaitingResponse()
  }

  def decode(ctx: ChannelHandlerContext, channel: Channel, buffer: ChannelBuffer): Decoding = {
    state match {
      case AwaitingResponse() =>
        decodeLine(buffer, needsData(_)) { tokens =>
          if (isEnd(tokens)) {
            ValueLines(Seq[TokensWithData]())
          } else if (isStats(tokens)) {
            awaitStatsOrEnd(Seq(Tokens(tokens)))
            needMoreData
          } else {
            Tokens(tokens)
          }
        }
      case AwaitingStatsOrEnd(linesSoFar) =>
        decodeLine(buffer, needsData(_)) { tokens =>
          if (isEnd(tokens)) {
            StatLines(linesSoFar)
          } else if (isStats(tokens)) {
            awaitStatsOrEnd(linesSoFar ++ Seq(Tokens(tokens)))
            needMoreData
          } else {
            throw new ServerError("Invalid reply from STATS command")
          }
        }
      case AwaitingData(valuesSoFar, tokens, bytesNeeded) =>
        decodeData(bytesNeeded, buffer) { data =>
          awaitResponseOrEnd(
            valuesSoFar ++
            Seq(TokensWithData(tokens, data)))
          needMoreData
        }
      case AwaitingResponseOrEnd(valuesSoFar) =>
        decodeLine(buffer, needsData(_)) { tokens =>
          if (isEnd(tokens)) {
            ValueLines(valuesSoFar)
          } else needMoreData
        }
    }
  }

  final protected[memcached] def awaitData(tokens: Seq[ChannelBuffer], bytesNeeded: Int) = {
    state match {
      case AwaitingResponse() =>
        awaitData(Seq(), tokens, bytesNeeded)
      case AwaitingResponseOrEnd(valuesSoFar) =>
        awaitData(valuesSoFar, tokens, bytesNeeded)
    }
  }

  private[this] def awaitData(valuesSoFar: Seq[TokensWithData], tokens: Seq[ChannelBuffer], bytesNeeded: Int) {
    state = AwaitingData(valuesSoFar, tokens, bytesNeeded)
  }

  private[this] def awaitResponseOrEnd(valuesSoFar: Seq[TokensWithData]) {
    state = AwaitingResponseOrEnd(valuesSoFar)
  }

  private[this] def awaitStatsOrEnd(valuesSoFar: Seq[Tokens]) {
    state = AwaitingStatsOrEnd(valuesSoFar)
  }

  private[this] val needMoreData = null: Decoding

  private[this] def isEnd(tokens: Seq[ChannelBuffer]) =
    (tokens.length == 1 && tokens.head == END)

  private[this] def isStats(tokens: Seq[ChannelBuffer]) =
    (tokens.length > 0 && (tokens.head == STAT || tokens.head == ITEM))

  private[this] def needsData(tokens: Seq[ChannelBuffer]) = {
    val responseName = tokens.head
    if (responseName == VALUE) {
      validateValueResponse(tokens)
      Some(tokens(3).toInt)
    } else None
  }


  private[this] def validateValueResponse(args: Seq[ChannelBuffer]) {
    if (args.length < 4) throw new ServerError("Too few arguments")
    if (args.length > 5) throw new ServerError("Too many arguments")
    if (args.length == 5 && !ParserUtils.isDigits(args(4))) throw new ServerError("CAS must be a number")
    if (!ParserUtils.isDigits(args(3))) throw new ServerError("Bytes must be number")
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy