summaryrefslogtreecommitdiff
path: root/examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala
blob: 18c1c5716a6b732fccb7a02f17949a44e02f7d33 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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()
  }

}