summaryrefslogtreecommitdiff
path: root/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala')
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala160
1 files changed, 160 insertions, 0 deletions
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala b/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala
new file mode 100644
index 0000000..18c1c57
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala
@@ -0,0 +1,160 @@
+package java.io
+
+import scala.annotation.tailrec
+
+import java.nio._
+import java.nio.charset._
+
+class OutputStreamWriter(private[this] var out: OutputStream,
+ private[this] var enc: CharsetEncoder) extends Writer {
+
+ private[this] var closed: Boolean = false
+
+ /** Incoming buffer: pending Chars that have been written to this instance
+ * of OutputStreamWriter, but not yet encoded.
+ * Normally, this should always be at most 1 Char, if it is a high surrogate
+ * which ended up alone at the end of the input of a write().
+ */
+ private[this] var inBuf: String = ""
+
+ /** Outgoing buffer: Bytes that have been decoded (from `inBuf`), but not
+ * yet written to the underlying output stream.
+ * The valid bytes are between 0 and outBuf.position.
+ */
+ private[this] var outBuf: ByteBuffer = ByteBuffer.allocate(4096)
+
+ def this(out: OutputStream, cs: Charset) =
+ this(out,
+ cs.newEncoder
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE))
+
+ def this(out: OutputStream) =
+ this(out, Charset.defaultCharset)
+
+ def this(out: OutputStream, charsetName: String) =
+ this(out, Charset.forName(charsetName))
+
+ def getEncoding(): String =
+ if (closed) null else enc.charset.name
+
+ override def write(c: Int): Unit =
+ write(c.toChar.toString, 0, 1)
+
+ override def write(cbuf: Array[Char], off: Int, len: Int): Unit =
+ writeImpl(CharBuffer.wrap(cbuf, off, len))
+
+ override def write(str: String, off: Int, len: Int): Unit =
+ writeImpl(CharBuffer.wrap(str, off, len))
+
+ private def writeImpl(cbuf: CharBuffer): Unit = {
+ ensureOpen()
+
+ val cbuf1 = if (inBuf != "") {
+ val fullInput = CharBuffer.wrap(inBuf + cbuf.toString)
+ inBuf = ""
+ fullInput
+ } else cbuf
+
+ @inline
+ @tailrec
+ def loopEncode(): Unit = {
+ val result = enc.encode(cbuf1, outBuf, false)
+ if (result.isUnderflow) ()
+ else if (result.isOverflow) {
+ makeRoomInOutBuf()
+ loopEncode()
+ } else {
+ result.throwException()
+ throw new AssertionError("should not get here")
+ }
+ }
+
+ loopEncode()
+ if (cbuf1.hasRemaining)
+ inBuf = cbuf1.toString
+ }
+
+ override def flush(): Unit = {
+ ensureOpen()
+ flushBuffer()
+ out.flush()
+ }
+
+ override def close(): Unit = if (!closed) {
+ // Finish up the input
+ @inline
+ @tailrec
+ def loopEncode(): Unit = {
+ val cbuf = CharBuffer.wrap(inBuf)
+ val result = enc.encode(cbuf, outBuf, true)
+ if (result.isUnderflow) {
+ assert(!cbuf.hasRemaining,
+ "CharsetEncoder.encode() should not have returned UNDERFLOW when "+
+ "both endOfInput and inBuf.hasRemaining are true. It should have "+
+ "returned a MalformedInput error instead.")
+ } else if (result.isOverflow) {
+ makeRoomInOutBuf()
+ loopEncode()
+ } else {
+ result.throwException()
+ throw new AssertionError("should not get here")
+ }
+ }
+
+ @inline
+ @tailrec
+ def loopFlush(): Unit = {
+ if (enc.flush(outBuf).isOverflow) {
+ makeRoomInOutBuf()
+ loopFlush()
+ }
+ }
+
+ loopEncode()
+ loopFlush()
+
+ // Flush before closing
+ flush()
+
+ // Close the underlying stream
+ out.close()
+
+ // Clean up all the resources
+ closed = true
+ out = null
+ enc = null
+ inBuf = null
+ outBuf = null
+ }
+
+ private def ensureOpen(): Unit = {
+ if (closed)
+ throw new IOException("Closed writer.")
+ }
+
+ private def makeRoomInOutBuf(): Unit = {
+ if (outBuf.position != 0) {
+ flushBuffer()
+ } else {
+ // Very unlikely (outBuf.capacity is not enough to encode a single code point)
+ outBuf.flip()
+ val newBuf = ByteBuffer.allocate(outBuf.capacity * 2)
+ newBuf.put(outBuf)
+ outBuf = newBuf
+ }
+ }
+
+ /** Flushes the internal buffer of this writer, but not the underlying
+ * output stream.
+ */
+ private[io] def flushBuffer(): Unit = {
+ ensureOpen()
+
+ // Don't use outBuf.flip() first, in case out.write() throws
+ // Hence, use 0 instead of position, and position instead of limit
+ out.write(outBuf.array, outBuf.arrayOffset, outBuf.position)
+ outBuf.clear()
+ }
+
+}