summaryrefslogtreecommitdiff
path: root/examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala
blob: 0f065238eab02ef87f4d15437c18c78ac69ebea9 (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
package java.io

class BufferedReader(in: Reader, sz: Int) extends Reader {

  def this(in: Reader) = this(in, 4096)

  private[this] var buf = new Array[Char](sz)

  /** Last valid value in the buffer (exclusive) */
  private[this] var end = 0

  /** Next position to read from buffer */
  private[this] var pos = 0

  private[this] var closed = false

  private[this] var validMark = false

  override def close(): Unit = {
    closed = true
  }

  override def mark(readAheadLimit: Int): Unit = {
    ensureOpen()

    val srcBuf = buf
    if (buf.size < readAheadLimit)
      buf = new Array[Char](readAheadLimit)

    // Move data to beginning of buffer
    if (pos != 0 || (buf ne srcBuf))
      System.arraycopy(srcBuf, pos, buf, 0, end - pos)

    // Update internal state
    end -= pos
    pos = 0
    validMark = true
  }

  override def markSupported(): Boolean = true

  override def read(): Int = {
    ensureOpen()

    if (prepareRead()) {
      val res = buf(pos).toInt
      pos += 1
      res
    } else -1
  }

  override def read(cbuf: Array[Char], off: Int, len: Int): Int = {
    ensureOpen()

    if (off < 0 || len < 0 || len > cbuf.length - off)
      throw new IndexOutOfBoundsException

    if (len == 0) 0
    else if (prepareRead()) {
      val count = Math.min(len, end - pos)
      System.arraycopy(this.buf, pos, cbuf, off, count)
      pos += count
      count
    } else -1
  }

  def readLine(): String = {
    ensureOpen()

    var res = ""

    while (prepareRead() && buf(pos) != '\n' && buf(pos) != '\r') {
      res += buf(pos)
      pos += 1
    }

    if (pos >= end) {
      // We have reached the end of the stream (prepareRead() returned false)
      if (res == "") null
      else res
    } else {
      // Consume terminator
      pos += 1

      // Check whether we have a \r\n. This may overrun the buffer
      // and then push a value back which may unnecessarily invalidate
      // the mark. This mimics java behavior
      if (buf(pos-1) == '\r' && prepareRead() && buf(pos) == '\n')
        pos += 1 // consume '\n'

      res
    }
  }

  override def ready(): Boolean = {
    ensureOpen()
    pos < end || in.ready()
  }

  override def reset(): Unit = {
    ensureOpen()

    if (!validMark) throw new IOException("Mark invalid")
    pos = 0
  }

  override def skip(n: Long): Long = {
    if (n < 0) throw new IllegalArgumentException("n negative")
    else if (pos < end) {
      val count = Math.min(n, end - pos).toInt
      pos += count
      count.toLong
    } else {
      validMark = false
      in.skip(n)
    }
  }

  /** Prepare the buffer for reading. Returns false if EOF */
  private def prepareRead(): Boolean =
    pos < end || fillBuffer()

  /** Tries to fill the buffer. Returns false if EOF */
  private def fillBuffer(): Boolean = {
    if (validMark && end < buf.length) {
      // we may not do a full re-read, since we'll damage the mark.
      val read = in.read(buf, end, buf.length - end)
      if (read > 0) // protect from adding -1
        end += read
      read > 0
    } else {
      // Full re-read
      validMark = false
      end = in.read(buf)
      pos = 0
      end > 0
    }
  }

  private def ensureOpen(): Unit = {
    if (closed)
      throw new IOException("Operation on closed stream")
  }

}