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

replpp.shaded.geny.Writable.scala Maven / Gradle / Ivy

The newest version!
package replpp.shaded
package geny
import java.io.{ByteArrayInputStream, InputStream, OutputStream, OutputStreamWriter}
import java.nio.charset.StandardCharsets

import sun.nio.cs.StreamEncoder

/**
 * A [[Writable]] is a source of bytes that can be written to an OutputStream.
 *
 * Essentially a push-based version of `java.io.InputStream`, that allows an
 * implementation to guarantee that cleanup logic runs after the bytes are
 * written.
 *
 * [[Writable]] is also much easier to implement than `java.io.InputStream`: any
 * code that previously wrote output to an `ByteArrayOutputStream` or
 * `StringBuilder` can trivially satisfy the [[Writable]] interface. That makes
 * [[Writable]] very convenient to use for allowing zero-friction zero-overhead
 * streaming data exchange between different libraries.
 *
 * [[Writable]] comes with implicit constructors from `Array[Byte]`, `String`
 * and `InputStream`, and is itself a tiny interface with minimal functionality.
 * Libraries using [[Writable]] are expected to extend it to provide additional
 * methods or additional implicit constructors that make sense in their context.
 */
trait Writable extends Any{
  def writeBytesTo(out: OutputStream): Unit
  def httpContentType: Option[String] = None
  def contentLength: Option[Long] = None
}
object Writable extends LowPriWritable {
  implicit class StringWritable(s: String) extends Writable{
    def writeBytesTo(out: OutputStream): Unit = {

      s.grouped(8192).foreach(ss => out.write(ss.getBytes(StandardCharsets.UTF_8)))
    }
    override def httpContentType = Some("text/plain; charset=utf-8")
    override def contentLength = Some(Internal.encodedLength(s))
  }

  implicit class ByteArrayWritable(a: Array[Byte]) extends Writable{
    def writeBytesTo(out: OutputStream): Unit = out.write(a)
    override def httpContentType = Some("application/octet-stream")
    override def contentLength = Some(a.length)
  }

  implicit class ByteBufferWritable(buffer: java.nio.ByteBuffer) extends Writable {
    def writeBytesTo(out: OutputStream): Unit = {
      // TODO: there is room for optimization here. We could match on the output
      // stream and in case it has an underlying NIO channel, write the buffer
      // directly to it. E.g.
      //
      // out match {
      //   case fs: java.io.FileOutputStream => fs.getChannel().write(buffer)
      //   case _ =>
      // }
      //
      // This optimization however is not available on ScalaJS, and hence
      // requires a restructuring of this source file.
      val bb = buffer.duplicate().order(buffer.order())
      var tmp = new Array[Byte](8192)
      val length = bb.remaining()
      var count = 0
      while (count < length) {
        val l = math.min(tmp.size, length - count)
        bb.get(tmp, 0, l)
        out.write(tmp, 0, l)
        count += l
      }
    }
    override def httpContentType = Some("application/octet-stream")
    override def contentLength = Some(buffer.remaining())
  }

}

trait LowPriWritable{
  implicit def readableWritable[T](t: T)(implicit f: T => Readable): Writable = f(t)
}

/**
 * A [[Readable]] is a source of bytes that can be read from an InputStream
 *
 * A subtype of [[Writable]], every [[Readable]] can be trivially used as a
 * [[Writable]] by transferring the bytes from the InputStream to the OutputStream,
 * but not every [[Writable]] is a [[Readable]].
 *
 * Note that the InputStream is only available inside the `readBytesThrough`, and
 * may be closed and cleaned up (along with any associated resources) once the
 * callback returns.
 */
trait Readable extends Writable{
  def readBytesThrough[T](f: InputStream => T): T
  def writeBytesTo(out: OutputStream): Unit = readBytesThrough(Internal.transfer(_, out))
}
object Readable{
  implicit class StringReadable(s: String) extends Readable{
    def readBytesThrough[T](f: InputStream => T): T = {
      f(new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)))
    }
    override def httpContentType = Some("text/plain; charset=utf-8")
    override def contentLength = Some(Internal.encodedLength(s))
  }

  implicit class ByteArrayReadable(a: Array[Byte]) extends Readable{
    def readBytesThrough[T](f: InputStream => T): T = f(new ByteArrayInputStream(a))
    override def httpContentType = Some("application/octet-stream")
    override def contentLength = Some(a.length)
  }

  implicit class ByteBufferReadable(buffer: java.nio.ByteBuffer) extends Readable{
    def readBytesThrough[T](f: InputStream => T): T = {
      val bb = buffer.duplicate().order(buffer.order())

      val is = new InputStream {
        override def read(): Int = if (!bb.hasRemaining()) {
          -1
        } else {
          bb.get() & 0xff
        }
        override def read(bytes: Array[Byte], off: Int, len: Int) = if (!bb.hasRemaining()) {
          -1
        } else {
          val l = math.min(len, bb.remaining())
          bb.get(bytes, off, l)
          l
        }
      }

      f(is)
    }
    override def httpContentType = Some("application/octet-stream")
    override def contentLength = Some(buffer.remaining())
  }

  implicit class InputStreamReadable(i: InputStream) extends Readable{
    def readBytesThrough[T](f: InputStream => T): T = f(i)
    override def httpContentType: Option[String] = Some("application/octet-stream")
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy