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

com.socrata.iteratee.ByteCharEnumeratee.scala Maven / Gradle / Ivy

The newest version!
package com.socrata.iteratee

import scala.io.Codec

import java.nio.{CharBuffer, ByteBuffer}
import java.nio.charset.CoderResult
import java.nio.charset.CharsetDecoder

import com.rojoma.json.util.WrappedCharArray

/** An Enumeratee which reads character data out of a byte stream and passes them down in chunks to a
 * secondary [[com.socrata.iteratee.Iteratee]].
 *
 * @note Because `CharsetDecoder` is stateful, this is not a "pure" iteratee!  Once `process` or `endOfInput` have
 *       been called, this iteratee has been invalidated.  It will raise an `IllegalStateException` if re-use
 *       is attempted.
 */
class ByteCharEnumeratee[T](decoder: CharsetDecoder, leftOver: Array[Byte], outBuf: CharBuffer, iteratee: CharIteratee[T]) extends ByteIteratee[T] {
  /** Constructs the `ByteCharIteratee` with the given codec.  This iteratee's `process`
   * and `endOfInput` methods will throw any exceptions thrown by a decoder produced from
   * that codec.
   */
  def this(codec: Codec, iteratee: CharIteratee[T]) = this(codec.decoder, null, CharBuffer.allocate(4096), iteratee)

  private var used = false

  private def guardValidity() {
    if(used) throw new IllegalStateException("Impure iteratee already used")
    used = true
  }

  private def toWrappedArray(buf: CharBuffer) = {
    buf.flip()
    val ca = WrappedCharArray(buf.array, buf.arrayOffset + buf.position, buf.remaining)
    buf.clear()
    ca
  }

  def process(bytes: Array[Byte]): Either[ByteIteratee[T], T] = {
    guardValidity()

    val inBuf =
      if(leftOver == null) {
        ByteBuffer.wrap(bytes)
      } else {
        val tmp = ByteBuffer.allocate(leftOver.length + bytes.length)
        tmp.put(leftOver).put(bytes).flip()
        tmp
      }

    var currentIteratee = iteratee
    var newLeftOver: Array[Byte] = null
    while(inBuf.hasRemaining) {
      decoder.decode(inBuf, outBuf, false) match {
        case CoderResult.OVERFLOW =>
          currentIteratee.process(toWrappedArray(outBuf)) match {
            case Right(t) => return Right(t)
            case Left(newIt) => currentIteratee = newIt
          }
        case CoderResult.UNDERFLOW if !inBuf.hasRemaining =>
          // ok, done with this chunk
        case CoderResult.UNDERFLOW =>
          newLeftOver = new Array[Byte](inBuf.remaining)
          inBuf.get(newLeftOver)
      }
    }

    currentIteratee.process(toWrappedArray(outBuf)) match {
      case Right(t) => return Right(t)
      case Left(newIt) => currentIteratee = newIt
    }

    Left(new ByteCharEnumeratee(decoder, newLeftOver, outBuf, currentIteratee))
  }

  def endOfInput(): T = {
    guardValidity()

    // CharsetDecoder was designed by a crazy person.
    // * It's allowed to be stateful.  So WHY will it say "nope, you'll have to re-feed me the remaining bytes
    //   when you have more input"?
    // * There is no way, without keeping track of whether you've called decode/3 previously, to know
    //   if it's safe to invoke flush (flushing a brand-new decoder will raise IllegalStateException).

    var it = iteratee
    if(leftOver != null) {
      val inBuf = ByteBuffer.wrap(leftOver)
      decoder.decode(inBuf, outBuf, true) match {
        case CoderResult.OVERFLOW =>
          it.process(toWrappedArray(outBuf)) match {
            case Right(t) => return t
            case Left(newIt) => it = newIt
          }
        case CoderResult.UNDERFLOW =>
          assert(!inBuf.hasRemaining)
      }
    }
    while((try { decoder.flush(outBuf) } catch { case _: IllegalStateException => CoderResult.UNDERFLOW }) == CoderResult.OVERFLOW) {
      it.process(toWrappedArray(outBuf)) match {
        case Right(t) => return t
        case Left(newIt) => it = newIt
      }
    }

    it.process(toWrappedArray(outBuf)) match {
      case Right(t) => return t
      case Left(newIt) => it = newIt
    }

    it.endOfInput()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy