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

xerial.core.io.DataProducer.scala Maven / Gradle / Ivy

The newest version!


//--------------------------------------
//
// DataProducer.scala
// Since: 2012/03/14 11:01
//
//--------------------------------------

package xerial.core.io
import java.io._
import java.nio.CharBuffer
import xerial.core.log.Logger


/**
 * Data producer produces data using a thread, and provides input stream to fetch the data.
 * To use this trait, implement the [[xerial.core.io.DataProducer.produce]] method, in which
 * binary data produced
 *
 * @author leo
 */
trait DataProducer extends InputStream with DataProducerBase[InputStream, OutputStream] {

  protected val pipeIn = new PipedInputStream(8192)
  // 8K
  protected val pipeOut = new PipedOutputStream(pipeIn)

  protected def produceStart: Unit = {
    try
      produce(pipeOut)
    finally {
      pipeOut.flush
      pipeOut.close
    }
  }

  def produce(out: OutputStream): Unit

  def toReader = new InputStreamReader(this)

  override def read(): Int = wrap(pipeIn.read)
  override def read(b: Array[Byte]): Int = wrap(pipeIn.read(b))
  override def read(b: Array[Byte], off: Int, len: Int) = wrap(pipeIn.read(b, off, len))
  override def skip(n: Long) = wrap(pipeIn.skip(n))
  override def available() = wrap(pipeIn.available())
  override def mark(readlimit: Int) = wrap(pipeIn.mark(readlimit))
  override def reset() = wrap(pipeIn.reset)
  override def markSupported() = wrap(pipeIn.markSupported)
}

/**
 * Implementer must invoke startWorker.
 * @tparam PipeIn
 * @tparam PipeOut
 */
trait DataProducerBase[PipeIn <: Closeable, PipeOut <: Closeable] extends Closeable with Logger {

  protected val pipeIn: PipeIn
  protected val pipeOut: PipeOut

  protected val worker = new Thread(new Runnable {
    def run() {
      try {
        produceStart
      }
      catch {
        case e: InterruptedException => {
          // terminated by close
          warn("Interrupted while producing data")
        }
      }
    }
  })

  /**
   * Start data production
   */
  protected def produceStart: Unit

  lazy protected val started: Boolean = {
    worker.setDaemon(true)
    worker.start()
    try {
      // Wait until the first data is produced. That also means initialization of classes extending this trait is finished.
      // The current thread is awaken by (PipedWriter.write -> PipedReader.recieve ->  notifyAll)
      Thread.sleep(0)
    }
    catch {
      case e: InterruptedException =>
        trace("sleep interrupted")
    }
    true
  }

  /**
   * Use this method to wrap any read method
   * @param f
   * @tparam A
   * @return
   */
  protected def wrap[A](f: => A): A = {
    if (started != true)
      sys.error("Failed to start the producer")
    f
  }

  override def close {
    // No need exists to close the reader explicitly since PipeReader.close resets the buffer
    pipeIn.close
    pipeOut.close
    if (started && worker.isAlive) {
      worker.interrupt
    }
  }
}


/**
 * Producer of text data.
 */
trait TextDataProducer extends Reader with DataProducerBase[Reader, Writer] {

  protected val pipeIn = new PipedReader
  protected val pipeOut = new PrintWriter(new PipedWriter(pipeIn))

  protected def produceStart: Unit = {
    try {
      produce(pipeOut)
    }
    finally {
      pipeOut.flush
      pipeOut.close
    }
  }

  def produce(out: PrintWriter): Unit

  class LineIterator extends Iterator[String] {
    val bufferedReader = new BufferedReader(pipeIn)
    var nextLine: String = null

    def hasNext = {
      if (nextLine == null) {
        nextLine = bufferedReader.readLine()
      }
      nextLine != null
    }

    def next(): String = {
      if (hasNext) {
        val line = nextLine
        nextLine = null
        line
      }
      else
        Iterator.empty.next
    }
  }

  /**
   * @deprecated This method has no support for closing the input stream appropriately. Use eachLine or lineIterator instead
   * @return
   */
  def lines: Iterator[String] = wrap(new LineIterator)

  def lineIterator[A](f: Iterator[String] => A): A = {
    val lineIt = wrap(new LineIterator)
    try
      f(lineIt)
    finally
      close
  }

  def eachLine[A](f: String => A): Unit = lineIterator(it => it.foreach(line => f(line)))

  def readAsString: String = {
    try {
      val b = new StringBuilder
      val buf = new Array[Char](8192)

      def loop: Unit = {
        val readLen = read(buf)
        if (readLen != -1) {
          b.appendAll(buf, 0, readLen)
          loop
        }
      }
      loop
      b.result()
    }
    finally
      close
  }

  def toInputStream : InputStream = new ReaderInputStream(this)


  override def read(target: CharBuffer) = wrap(pipeIn.read(target))
  override def read() = wrap(pipeIn.read())
  override def read(cbuf: Array[Char]) = wrap(pipeIn.read(cbuf))
  override def read(cbuf: Array[Char], offset: Int, len: Int) = wrap(pipeIn.read(cbuf, offset, len))
  override def skip(n: Long) = wrap(pipeIn.skip(n))
  override def ready() = wrap(pipeIn.ready)
  override def markSupported() = wrap(pipeIn.markSupported())
  override def mark(readAheadLimit: Int) = wrap(pipeIn.mark(readAheadLimit))
  override def reset() = wrap(pipeIn.reset)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy