summaryrefslogtreecommitdiff
path: root/examples/scala-js/javalib/src/main/scala/java/io
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/javalib/src/main/scala/java/io')
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala145
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/ByteArrayInputStream.scala58
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/ByteArrayOutputStream.scala62
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/Closeable.scala6
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/DataInput.scala19
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/FilterInputStream.scala24
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/FilterOutputStream.scala16
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/Flushable.scala5
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/InputStream.scala53
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/InputStreamReader.scala216
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/OutputStream.scala25
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/OutputStreamWriter.scala160
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala218
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/PrintWriter.scala150
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/Reader.scala60
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/Serializable.scala3
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/StringReader.scala69
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/StringWriter.scala44
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/Throwables.scala19
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/Writer.scala45
20 files changed, 1397 insertions, 0 deletions
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala b/examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala
new file mode 100644
index 0000000..0f06523
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/BufferedReader.scala
@@ -0,0 +1,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")
+ }
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayInputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayInputStream.scala
new file mode 100644
index 0000000..697e07b
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayInputStream.scala
@@ -0,0 +1,58 @@
+package java.io
+
+class ByteArrayInputStream(
+ protected val buf: Array[Byte],
+ offset: Int, length: Int) extends InputStream {
+
+ protected val count: Int = offset + length
+ protected var mark: Int = offset
+ protected var pos: Int = offset
+
+ def this(buf: Array[Byte]) = this(buf, 0, buf.length)
+
+ override def read(): Int = {
+ if (pos >= count)
+ -1
+ else {
+ val res = buf(pos) & 0xFF // convert to unsigned int
+ pos += 1
+ res
+ }
+ }
+
+ override def read(b: Array[Byte], off: Int, reqLen: Int): Int = {
+ if (off < 0 || reqLen < 0 || reqLen > b.length - off)
+ throw new IndexOutOfBoundsException
+
+ val len = Math.min(reqLen, count - pos)
+
+ if (reqLen == 0)
+ 0 // 0 requested, 0 returned
+ else if (len == 0)
+ -1 // nothing to read at all
+ else {
+ System.arraycopy(buf, pos, b, off, len)
+ pos += len
+ len
+ }
+ }
+
+ override def skip(n: Long): Long = {
+ val k = Math.max(0, Math.min(n, count - pos))
+ pos += k.toInt
+ k.toLong
+ }
+
+ override def available(): Int = count - pos
+
+ override def markSupported(): Boolean = true
+
+ override def mark(readlimit: Int): Unit =
+ mark = pos
+
+ override def reset(): Unit =
+ pos = mark
+
+ override def close(): Unit = ()
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayOutputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayOutputStream.scala
new file mode 100644
index 0000000..916002d
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/ByteArrayOutputStream.scala
@@ -0,0 +1,62 @@
+package java.io
+
+import scala.scalajs.js
+
+import scala.annotation.tailrec
+
+class ByteArrayOutputStream(initBufSize: Int) extends OutputStream {
+
+ protected var buf: Array[Byte] = new Array(initBufSize)
+ protected var count: Int = 0
+
+ def this() = this(32)
+
+ override def write(b: Int): Unit = {
+ if (count >= buf.length)
+ growBuf(1)
+
+ buf(count) = b.toByte
+ count += 1
+ }
+
+ override def write(b: Array[Byte], off: Int, len: Int): Unit = {
+ if (off < 0 || len < 0 || len > b.length - off)
+ throw new IndexOutOfBoundsException()
+
+ if (count + len > buf.length)
+ growBuf(len)
+
+ System.arraycopy(b, off, buf, count, len)
+ count += len
+ }
+
+ def writeTo(out: OutputStream): Unit =
+ out.write(buf, 0, count)
+
+ def reset(): Unit =
+ count = 0
+
+ def toByteArray(): Array[Byte] = {
+ val res = new Array[Byte](count)
+ System.arraycopy(buf, 0, res, 0, count)
+ res
+ }
+
+ def size(): Int = count
+
+ override def toString(): String =
+ new String(buf, 0, count)
+
+ def toString(charsetName: String): String =
+ new String(buf, 0, count, charsetName)
+
+ override def close(): Unit = ()
+
+ private def growBuf(minIncrement: Int): Unit = {
+ val newSize = Math.max(count + minIncrement, buf.length * 2)
+ val newBuf = new Array[Byte](newSize)
+ System.arraycopy(buf, 0, newBuf, 0, count)
+ buf = newBuf
+ }
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Closeable.scala b/examples/scala-js/javalib/src/main/scala/java/io/Closeable.scala
new file mode 100644
index 0000000..e572390
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/Closeable.scala
@@ -0,0 +1,6 @@
+package java.io
+
+/** Note that Closeable doesn't extend AutoCloseable for Java6 compat */
+trait Closeable {
+ def close(): Unit
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/DataInput.scala b/examples/scala-js/javalib/src/main/scala/java/io/DataInput.scala
new file mode 100644
index 0000000..37913b4
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/DataInput.scala
@@ -0,0 +1,19 @@
+package java.io
+
+trait DataInput {
+ def readBoolean(): Boolean
+ def readByte(): Byte
+ def readChar(): Char
+ def readDouble(): Double
+ def readFloat(): Float
+ def readFully(b: Array[Byte]): Unit
+ def readFully(b: Array[Byte], off: Int, len: Int): Unit
+ def readInt(): Int
+ def readLine(): String
+ def readLong(): Long
+ def readShort(): Short
+ def readUnsignedByte(): Int
+ def readUnsignedShort(): Int
+ def readUTF(): String
+ def skipBytes(n: Int): Int
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/FilterInputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/FilterInputStream.scala
new file mode 100644
index 0000000..a85b9f6
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/FilterInputStream.scala
@@ -0,0 +1,24 @@
+package java.io
+
+class FilterInputStream protected (
+ protected val in: InputStream) extends InputStream {
+
+ override def read(): Int =
+ in.read()
+
+ override def read(b: Array[Byte]): Int =
+ read(b, 0, b.length) // this is spec! must not do in.read(b)
+
+ override def read(b: Array[Byte], off: Int, len: Int): Int =
+ in.read(b, off, len)
+
+ override def skip(n: Long): Long = in.skip(n)
+
+ override def available(): Int = in.available()
+
+ override def close(): Unit = in.close()
+
+ override def mark(readlimit: Int): Unit = in.mark(readlimit)
+ override def markSupported(): Boolean = in.markSupported()
+ override def reset(): Unit = in.reset()
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/FilterOutputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/FilterOutputStream.scala
new file mode 100644
index 0000000..299b7b6
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/FilterOutputStream.scala
@@ -0,0 +1,16 @@
+package java.io
+
+class FilterOutputStream(protected val out: OutputStream) extends OutputStream {
+ def write(b: Int): Unit =
+ out.write(b)
+
+ override def write(b: Array[Byte]): Unit =
+ write(b, 0, b.length) // this is spec! it must not call out.write(b)
+
+ override def write(b: Array[Byte], off: Int, len: Int): Unit =
+ super.write(b, off, len) // calls this.write(Int) repeatedly
+
+ override def flush(): Unit = out.flush()
+
+ override def close(): Unit = out.close()
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Flushable.scala b/examples/scala-js/javalib/src/main/scala/java/io/Flushable.scala
new file mode 100644
index 0000000..2879ad2
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/Flushable.scala
@@ -0,0 +1,5 @@
+package java.io
+
+trait Flushable {
+ def flush(): Unit
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/InputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/InputStream.scala
new file mode 100644
index 0000000..412d84b
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/InputStream.scala
@@ -0,0 +1,53 @@
+package java.io
+
+abstract class InputStream extends Closeable {
+ def read(): Int
+
+ def read(b: Array[Byte]): Int = read(b, 0, b.length)
+
+ def read(b: Array[Byte], off: Int, len: Int): Int = {
+ if (off < 0 || len < 0 || len > b.length - off)
+ throw new IndexOutOfBoundsException
+
+ if (len == 0) 0
+ else {
+ var bytesWritten = 0
+ var next = 0
+
+ while (bytesWritten < len && next != -1) {
+ next =
+ if (bytesWritten == 0) read()
+ else {
+ try read()
+ catch { case _: IOException => -1 }
+ }
+ if (next != -1) {
+ b(off + bytesWritten) = next.toByte
+ bytesWritten += 1
+ }
+ }
+
+ if (bytesWritten <= 0) -1
+ else bytesWritten
+ }
+ }
+
+ def skip(n: Long): Long = {
+ var skipped = 0
+ while (skipped < n && read() != -1)
+ skipped += 1
+ skipped
+ }
+
+ def available(): Int = 0
+
+ def close(): Unit = ()
+
+ def mark(readlimit: Int): Unit = ()
+
+ def reset(): Unit =
+ throw new IOException("Reset not supported")
+
+ def markSupported(): Boolean = false
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/InputStreamReader.scala b/examples/scala-js/javalib/src/main/scala/java/io/InputStreamReader.scala
new file mode 100644
index 0000000..1ef957c
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/InputStreamReader.scala
@@ -0,0 +1,216 @@
+package java.io
+
+import scala.annotation.tailrec
+
+import java.nio._
+import java.nio.charset._
+
+class InputStreamReader(private[this] var in: InputStream,
+ private[this] var decoder: CharsetDecoder) extends Reader {
+
+ private[this] var closed: Boolean = false
+
+ /** Buffer in which to read bytes from the underlying input stream.
+ *
+ * Class invariant: contains bytes already read from `in` but not yet
+ * decoded.
+ */
+ private[this] var inBuf: ByteBuffer = ByteBuffer.allocate(4096)
+ inBuf.limit(0)
+
+ /** Tells whether the end of the underlying input stream has been reached.
+ * Class invariant: if true, then `in.read()` has returned -1.
+ */
+ private[this] var endOfInput: Boolean = false
+
+ /** Buffer in which to decode bytes into chars.
+ * Usually, it is not used, because we try to decode directly to the
+ * destination array. So as long as we do not really need one, we share
+ * an empty buffer.
+ *
+ * Class invariant: contains chars already decoded but not yet *read* by
+ * the user of this instance.
+ */
+ private[this] var outBuf: CharBuffer = InputStreamReader.CommonEmptyCharBuffer
+
+ def this(in: InputStream, charset: Charset) =
+ this(in,
+ charset.newDecoder
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE))
+
+ def this(in: InputStream) =
+ this(in, Charset.defaultCharset)
+
+ def this(in: InputStream, charsetName: String) =
+ this(in, Charset.forName(charsetName))
+
+ def close(): Unit = {
+ closed = true
+ in = null
+ decoder = null
+ inBuf = null
+ outBuf = null
+ }
+
+ def getEncoding(): String =
+ if (closed) null else decoder.charset.name
+
+ override def read(): Int = {
+ ensureOpen()
+
+ if (outBuf.hasRemaining) outBuf.get()
+ else super.read()
+ }
+
+ 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 (outBuf.hasRemaining) {
+ // Reuse chars decoded last time
+ val available = Math.min(outBuf.remaining, len)
+ outBuf.get(cbuf, off, available)
+ available
+ } else {
+ // Try and decode directly into the destination array
+ val directOut = CharBuffer.wrap(cbuf, off, len)
+ val result = readImpl(directOut)
+ if (result != InputStreamReader.Overflow) {
+ result
+ } else {
+ /* There's not enough space in the destination array to receive even
+ * a tiny bit of output from the decoder. We need to decode to the
+ * outBuf instead.
+ * This happens typically when the next code point to decode is a
+ * supplementary character, and the given `len` is 1.
+ */
+ readMoreThroughOutBuf(cbuf, off, len)
+ }
+ }
+ }
+
+ // In a separate method because this is (hopefully) not a common case
+ private def readMoreThroughOutBuf(cbuf: Array[Char], off: Int, len: Int): Int = {
+ // Return outBuf to its full capacity
+ outBuf.limit(outBuf.capacity)
+ outBuf.position(0)
+
+ @tailrec // but not inline, this is not a common path
+ def loopWithOutBuf(desiredOutBufSize: Int): Int = {
+ if (outBuf.capacity < desiredOutBufSize)
+ outBuf = CharBuffer.allocate(desiredOutBufSize)
+ val charsRead = readImpl(outBuf)
+ if (charsRead == InputStreamReader.Overflow)
+ loopWithOutBuf(desiredOutBufSize*2)
+ else
+ charsRead
+ }
+
+ val charsRead = loopWithOutBuf(2*len)
+ assert(charsRead != 0) // can be -1, though
+ outBuf.flip()
+
+ if (charsRead == -1) -1
+ else {
+ val available = Math.min(charsRead, len)
+ outBuf.get(cbuf, off, available)
+ available
+ }
+ }
+
+ @tailrec
+ private def readImpl(out: CharBuffer): Int = {
+ val initPos = out.position
+ val result = decoder.decode(inBuf, out, endOfInput)
+
+ if (out.position != initPos) {
+ /* Good, we made progress, so we can return.
+ * Note that the `result` does not matter. Whether it's an underflow,
+ * an overflow, or even an error, if we read *something*, we can return
+ * that.
+ * The next invocation of read() will cause a new invocation of decode(),
+ * which will necessarily return the same result (but without advancing
+ * at all), which will cause one of the following cases to be handled.
+ */
+ out.position - initPos
+ } else if (result.isUnderflow) {
+ if (endOfInput) {
+ assert(!inBuf.hasRemaining,
+ "CharsetDecoder.decode() should not have returned UNDERFLOW when "+
+ "both endOfInput and inBuf.hasRemaining are true. It should have "+
+ "returned a MalformedInput error instead.")
+ // Flush
+ if (decoder.flush(out).isOverflow)
+ InputStreamReader.Overflow
+ else {
+ // Done
+ if (out.position == initPos) -1
+ else out.position - initPos
+ }
+ } else {
+ // We need to read more from the underlying input stream
+ if (inBuf.limit == inBuf.capacity) {
+ inBuf.compact()
+ if (!inBuf.hasRemaining) {
+ throw new AssertionError(
+ "Scala.js implementation restriction: " +
+ inBuf.capacity + " bytes do not seem to be enough for " +
+ getEncoding + " to decode a single code point. " +
+ "Please report this as a bug.")
+ }
+ inBuf.limit(inBuf.position)
+ inBuf.position(0)
+ }
+
+ /* Note that this stores the new data after the limit of the buffer.
+ * Further, note that we may read more bytes than strictly necessary,
+ * according to the specification of InputStreamReader.
+ */
+ val bytesRead =
+ in.read(inBuf.array, inBuf.limit, inBuf.capacity - inBuf.limit)
+
+ if (bytesRead == -1)
+ endOfInput = true
+ else
+ inBuf.limit(inBuf.limit + bytesRead)
+
+ readImpl(out)
+ }
+ } else if (result.isOverflow) {
+ InputStreamReader.Overflow
+ } else {
+ result.throwException()
+ throw new AssertionError("should not get here")
+ }
+ }
+
+ /* In theory, `in.available() > 0` is incorrect. We should return true only
+ * if there are enough bytes available to read at least one code point.
+ * However, this is how the JDK behaves, and even the JavaDoc suggests this
+ * is the expected behavior.
+ */
+ override def ready(): Boolean =
+ outBuf.hasRemaining || in.available() > 0
+
+ private def ensureOpen(): Unit = {
+ if (closed)
+ throw new IOException("Stream closed")
+ }
+
+}
+
+object InputStreamReader {
+ private final val Overflow = -2
+
+ /** Empty CharBuffer shared by all InputStreamReaders as long as they do
+ * not really need one.
+ * Since we do not use `mark()`, it is fine to share them, because `mark()`
+ * is the only piece of mutable state for an empty buffer. Everything else
+ * is effectively immutable (e.g., position and limit must always be 0).
+ */
+ private val CommonEmptyCharBuffer = CharBuffer.allocate(0)
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/OutputStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/OutputStream.scala
new file mode 100644
index 0000000..729e69b
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/OutputStream.scala
@@ -0,0 +1,25 @@
+package java.io
+
+abstract class OutputStream extends Object with Closeable with Flushable {
+ def write(b: Int): Unit
+
+ def write(b: Array[Byte]): Unit =
+ write(b, 0, b.length)
+
+ def write(b: Array[Byte], off: Int, len: Int): Unit = {
+ if (off < 0 || len < 0 || len > b.length - off)
+ throw new IndexOutOfBoundsException()
+
+ var n = off
+ val stop = off + len
+ while (n < stop) {
+ write(b(n))
+ n += 1
+ }
+ }
+
+ def flush(): Unit = ()
+
+ def close(): Unit = ()
+
+}
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()
+ }
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala b/examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala
new file mode 100644
index 0000000..68fa041
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala
@@ -0,0 +1,218 @@
+package java.io
+
+import java.nio.charset.Charset
+import java.util.Formatter
+
+class PrintStream private (_out: OutputStream, autoFlush: Boolean,
+ charset: Charset)
+ extends FilterOutputStream(_out) with Appendable with Closeable {
+
+ /* The way we handle charsets here is a bit tricky, because we want to
+ * minimize the area of reachability for normal programs.
+ *
+ * First, if nobody uses the constructor taking an explicit encoding, we
+ * don't want to reach Charset.forName(), which pulls in all of the
+ * implemented charsets.
+ *
+ * Second, most programs will reach PrintStream only because of
+ * java.lang.System.{out,err}, which are subclasses of PrintStream that do
+ * not actually need to encode anything: they override all of PrintStream's
+ * methods to bypass the encoding altogether, and hence don't even need
+ * the default charset.
+ *
+ * This is why we have:
+ * * A private constructor taking the Charset directly, instead of its name.
+ * * Which is allowed to be `null`, which stands for the default charset.
+ * * The default charset is only loaded lazily in the initializer of the
+ * encoder field.
+ */
+
+ def this(out: OutputStream) =
+ this(out, false, null: Charset)
+
+ def this(out: OutputStream, autoFlush: Boolean) =
+ this(out, autoFlush, null: Charset)
+
+ def this(out: OutputStream, autoFlush: Boolean, encoding: String) =
+ this(out, autoFlush, Charset.forName(encoding))
+
+ /* The following constructors, although implemented, will not link, since
+ * File, FileOutputStream and BufferedOutputStream are not implemented.
+ * They're here just in case a third-party library on the classpath
+ * implements those.
+ */
+ def this(file: File) =
+ this(new BufferedOutputStream(new FileOutputStream(file)))
+ def this(file: File, csn: String) =
+ this(new BufferedOutputStream(new FileOutputStream(file)), false, csn)
+ def this(fileName: String) =
+ this(new File(fileName))
+ def this(fileName: String, csn: String) =
+ this(new File(fileName), csn)
+
+ private lazy val encoder = {
+ val c =
+ if (charset == null) Charset.defaultCharset
+ else charset
+ /* We pass `this` as the output stream for the encoding writer so that
+ * we can apply auto-flushing. Note that this will flush() more often
+ * than required by the spec. It appears to be consistent with how the
+ * JDK behaves.
+ */
+ new OutputStreamWriter(this, c)
+ }
+
+ private var closing: Boolean = false
+ private var closed: Boolean = false
+ private var errorFlag: Boolean = false
+
+ override def flush(): Unit =
+ ensureOpenAndTrapIOExceptions(out.flush())
+
+ override def close(): Unit = trapIOExceptions {
+ if (!closing) {
+ closing = true
+ encoder.close()
+ flush()
+ closed = true
+ out.close()
+ }
+ }
+
+ def checkError(): Boolean = {
+ if (closed) {
+ /* Just check the error flag.
+ * Common sense would tell us to look at the underlying writer's
+ * checkError() result too (like we do in the not closed case below).
+ * But the JDK does not behave like that. So we don't either.
+ */
+ errorFlag
+ } else {
+ flush()
+ /* If the underlying writer is also a PrintStream, we also check its
+ * checkError() result. This is not clearly specified by the JavaDoc,
+ * but, experimentally, the JDK seems to behave that way.
+ */
+ errorFlag || (out match {
+ case out: PrintStream => out.checkError()
+ case _ => false
+ })
+ }
+ }
+
+ protected[io] def setError(): Unit = errorFlag = true
+ protected[io] def clearError(): Unit = errorFlag = false
+
+ /* Note that calling directly the write() methods will happily bypass the
+ * potential lone high surrogate that is buffered in the underlying
+ * OutputStreamWriter. This means that the following sequence of operations:
+ *
+ * ps.print('\ud83d') // high surrogate of PILE OF POO
+ * ps.write('a')
+ * ps.print('\udca9') // low surrogate of PILE OF POO
+ *
+ * will result in the following bytes being emitted to the underlying stream:
+ *
+ * a\ud83d\udca9
+ *
+ * i.e., first the 'a', then the PILE OF POO.
+ *
+ * This is consistent with the behavior of the JDK.
+ */
+
+ override def write(b: Int): Unit = {
+ ensureOpenAndTrapIOExceptions {
+ out.write(b)
+ if (autoFlush && b == '\n')
+ flush()
+ }
+ }
+
+ override def write(buf: Array[Byte], off: Int, len: Int): Unit = {
+ ensureOpenAndTrapIOExceptions {
+ out.write(buf, off, len)
+ if (autoFlush)
+ flush()
+ }
+ }
+
+ def print(b: Boolean): Unit = printString(String.valueOf(b))
+ def print(c: Char): Unit = printString(String.valueOf(c))
+ def print(i: Int): Unit = printString(String.valueOf(i))
+ def print(l: Long): Unit = printString(String.valueOf(l))
+ def print(f: Float): Unit = printString(String.valueOf(f))
+ def print(d: Double): Unit = printString(String.valueOf(d))
+ def print(s: String): Unit = printString(if (s == null) "null" else s)
+ def print(obj: AnyRef): Unit = printString(String.valueOf(obj))
+
+ private def printString(s: String): Unit = ensureOpenAndTrapIOExceptions {
+ encoder.write(s)
+ encoder.flushBuffer()
+ }
+
+ def print(s: Array[Char]): Unit = ensureOpenAndTrapIOExceptions {
+ encoder.write(s)
+ encoder.flushBuffer()
+ }
+
+ def println(): Unit = ensureOpenAndTrapIOExceptions {
+ encoder.write('\n') // In Scala.js the line separator is always LF
+ encoder.flushBuffer()
+ if (autoFlush)
+ flush()
+ }
+
+ def println(b: Boolean): Unit = { print(b); println() }
+ def println(c: Char): Unit = { print(c); println() }
+ def println(i: Int): Unit = { print(i); println() }
+ def println(l: Long): Unit = { print(l); println() }
+ def println(f: Float): Unit = { print(f); println() }
+ def println(d: Double): Unit = { print(d); println() }
+ def println(s: Array[Char]): Unit = { print(s); println() }
+ def println(s: String): Unit = { print(s); println() }
+ def println(obj: AnyRef): Unit = { print(obj); println() }
+
+ def printf(fmt: String, args: Array[Object]): PrintStream =
+ format(fmt, args)
+
+ // Not implemented:
+ //def printf(l: java.util.Locale, fmt: String, args: Array[Object]): PrintStream = ???
+
+ def format(fmt: String, args: Array[Object]): PrintStream = {
+ new Formatter(this).format(fmt, args)
+ this
+ }
+
+ // Not implemented:
+ //def format(l: java.util.Locale, fmt: String, args: Array[Object]): PrintStream = ???
+
+ def append(csq: CharSequence): PrintStream = {
+ print(if (csq == null) "null" else csq.toString)
+ this
+ }
+
+ def append(csq: CharSequence, start: Int, end: Int): PrintStream = {
+ val csq1 = if (csq == null) "null" else csq
+ print(csq1.subSequence(start, end).toString)
+ this
+ }
+
+ def append(c: Char): PrintStream = {
+ print(c)
+ this
+ }
+
+ @inline private[this] def trapIOExceptions(body: => Unit): Unit = {
+ try {
+ body
+ } catch {
+ case _: IOException => setError()
+ }
+ }
+
+ @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = {
+ if (closed) setError()
+ else trapIOExceptions(body)
+ }
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/PrintWriter.scala b/examples/scala-js/javalib/src/main/scala/java/io/PrintWriter.scala
new file mode 100644
index 0000000..4e693e0
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/PrintWriter.scala
@@ -0,0 +1,150 @@
+package java.io
+
+import java.util.Formatter
+
+class PrintWriter(protected[io] var out: Writer,
+ autoFlush: Boolean) extends Writer {
+
+ def this(out: Writer) = this(out, false)
+
+ def this(out: OutputStream, autoFlush: Boolean) =
+ this(new OutputStreamWriter(out), autoFlush)
+ def this(out: OutputStream) =
+ this(out, false)
+
+ /* The following constructors, although implemented, will not link, since
+ * File, FileOutputStream and BufferedOutputStream are not implemented.
+ * They're here just in case a third-party library on the classpath
+ * implements those.
+ */
+ def this(file: File) =
+ this(new BufferedOutputStream(new FileOutputStream(file)))
+ def this(file: File, csn: String) =
+ this(new OutputStreamWriter(new BufferedOutputStream(
+ new FileOutputStream(file)), csn))
+ def this(fileName: String) = this(new File(fileName))
+ def this(fileName: String, csn: String) = this(new File(fileName), csn)
+
+ private var closed: Boolean = false
+ private var errorFlag: Boolean = false
+
+ def flush(): Unit =
+ ensureOpenAndTrapIOExceptions(out.flush())
+
+ def close(): Unit = trapIOExceptions {
+ if (!closed) {
+ flush()
+ closed = true
+ out.close()
+ }
+ }
+
+ def checkError(): Boolean = {
+ if (closed) {
+ /* Just check the error flag.
+ * Common sense would tell us to look at the underlying writer's
+ * checkError() result too (like we do in the not closed case below).
+ * But the JDK does not behave like that. So we don't either.
+ */
+ errorFlag
+ } else {
+ flush()
+ /* If the underlying writer is also a PrintWriter, we also check its
+ * checkError() result. This is not clearly specified by the JavaDoc,
+ * but, experimentally, the JDK seems to behave that way.
+ */
+ errorFlag || (out match {
+ case out: PrintWriter => out.checkError()
+ case _ => false
+ })
+ }
+ }
+
+ protected[io] def setError(): Unit = errorFlag = true
+ protected[io] def clearError(): Unit = errorFlag = false
+
+ override def write(c: Int): Unit =
+ ensureOpenAndTrapIOExceptions(out.write(c))
+
+ override def write(buf: Array[Char], off: Int, len: Int): Unit =
+ ensureOpenAndTrapIOExceptions(out.write(buf, off, len))
+
+ override def write(buf: Array[Char]): Unit =
+ ensureOpenAndTrapIOExceptions(out.write(buf))
+
+ override def write(s: String, off: Int, len: Int): Unit =
+ ensureOpenAndTrapIOExceptions(out.write(s, off, len))
+
+ override def write(s: String): Unit =
+ ensureOpenAndTrapIOExceptions(out.write(s))
+
+ def print(b: Boolean): Unit = write(String.valueOf(b))
+ def print(c: Char): Unit = write(c)
+ def print(i: Int): Unit = write(String.valueOf(i))
+ def print(l: Long): Unit = write(String.valueOf(l))
+ def print(f: Float): Unit = write(String.valueOf(f))
+ def print(d: Double): Unit = write(String.valueOf(d))
+ def print(s: Array[Char]): Unit = write(s)
+ def print(s: String): Unit = write(if (s == null) "null" else s)
+ def print(obj: AnyRef): Unit = write(String.valueOf(obj))
+
+ def println(): Unit = {
+ write('\n') // In Scala.js the line separator is always LF
+ if (autoFlush)
+ flush()
+ }
+
+ def println(b: Boolean): Unit = { print(b); println() }
+ def println(c: Char): Unit = { print(c); println() }
+ def println(i: Int): Unit = { print(i); println() }
+ def println(l: Long): Unit = { print(l); println() }
+ def println(f: Float): Unit = { print(f); println() }
+ def println(d: Double): Unit = { print(d); println() }
+ def println(s: Array[Char]): Unit = { print(s); println() }
+ def println(s: String): Unit = { print(s); println() }
+ def println(obj: AnyRef): Unit = { print(obj); println() }
+
+ def printf(fmt: String, args: Array[Object]): PrintWriter =
+ format(fmt, args)
+
+ // Not implemented:
+ //def printf(l: java.util.Locale, fmt: String, args: Array[Object]): PrintWriter = ???
+
+ def format(fmt: String, args: Array[Object]): PrintWriter = {
+ new Formatter(this).format(fmt, args)
+ if (autoFlush)
+ flush()
+ this
+ }
+
+ // Not implemented:
+ //def format(l: java.util.Locale, fmt: String, args: Array[Object]): PrintWriter = ???
+
+ override def append(csq: CharSequence): PrintWriter = {
+ super.append(csq)
+ this
+ }
+
+ override def append(csq: CharSequence, start: Int, end: Int): PrintWriter = {
+ super.append(csq, start, end)
+ this
+ }
+
+ override def append(c: Char): PrintWriter = {
+ super.append(c)
+ this
+ }
+
+ @inline private[this] def trapIOExceptions(body: => Unit): Unit = {
+ try {
+ body
+ } catch {
+ case _: IOException => setError()
+ }
+ }
+
+ @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = {
+ if (closed) setError()
+ else trapIOExceptions(body)
+ }
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Reader.scala b/examples/scala-js/javalib/src/main/scala/java/io/Reader.scala
new file mode 100644
index 0000000..97be140
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/Reader.scala
@@ -0,0 +1,60 @@
+package java.io
+
+import java.nio.CharBuffer
+
+abstract class Reader private[this] (_lock: Option[Object])
+ extends Readable with Closeable {
+
+ protected val lock = _lock.getOrElse(this)
+
+ protected def this(lock: Object) = this(Some(lock))
+ protected def this() = this(None)
+
+ def read(target: CharBuffer): Int = {
+ if (!target.hasRemaining) 0
+ else if (target.hasArray) {
+ val charsRead = read(target.array,
+ target.position + target.arrayOffset, target.remaining)
+ if (charsRead != -1)
+ target.position(target.position + charsRead)
+ charsRead
+ } else {
+ val buf = new Array[Char](target.remaining)
+ val charsRead = read(buf)
+ if (charsRead != -1)
+ target.put(buf, 0, charsRead)
+ charsRead
+ }
+ }
+
+ def read(): Int = {
+ val buf = new Array[Char](1)
+ if (read(buf) == -1) -1
+ else buf(0).toInt
+ }
+
+ def read(cbuf: Array[Char]): Int =
+ read(cbuf, 0, cbuf.length)
+
+ def read(cbuf: Array[Char], off: Int, len: Int): Int
+
+ def skip(n: Long): Long = {
+ if (n < 0)
+ throw new IllegalArgumentException("Cannot skip negative amount")
+ else if (read() == -1) 0
+ else 1
+ }
+
+ def ready(): Boolean = false
+
+ def markSupported(): Boolean = false
+
+ def mark(readAheadLimit: Int): Unit =
+ throw new IOException("Mark not supported")
+
+ def reset(): Unit =
+ throw new IOException("Reset not supported")
+
+ def close(): Unit
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Serializable.scala b/examples/scala-js/javalib/src/main/scala/java/io/Serializable.scala
new file mode 100644
index 0000000..01dd228
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/Serializable.scala
@@ -0,0 +1,3 @@
+package java.io
+
+trait Serializable
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/StringReader.scala b/examples/scala-js/javalib/src/main/scala/java/io/StringReader.scala
new file mode 100644
index 0000000..2ca8f90
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/StringReader.scala
@@ -0,0 +1,69 @@
+package java.io
+
+class StringReader(s: String) extends Reader {
+
+ private[this] var closed = false
+ private[this] var pos = 0
+ private[this] var mark = 0
+
+ override def close(): Unit = {
+ closed = true
+ }
+
+ override def mark(readAheadLimit: Int): Unit = {
+ ensureOpen()
+
+ mark = pos
+ }
+
+ override def markSupported(): Boolean = true
+
+ override def read(): Int = {
+ ensureOpen()
+
+ if (pos < s.length) {
+ val res = s.charAt(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 {
+ val count = Math.min(len, s.length - pos)
+ var i = 0
+ while (i < count) {
+ cbuf(off + i) = s.charAt(pos + i)
+ i += 1
+ }
+ pos += count
+ count
+ }
+ }
+
+ override def ready(): Boolean = pos < s.length
+
+ override def reset(): Unit = {
+ ensureOpen()
+ pos = mark
+ }
+
+ override def skip(ns: Long): Long = {
+ // Apparently, StringReader.skip allows negative skips
+ val count = Math.max(Math.min(ns, s.length - pos).toInt, -pos)
+ pos += count
+ count.toLong
+ }
+
+ private def ensureOpen(): Unit = {
+ if (closed)
+ throw new IOException("Operation on closed stream")
+ }
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/StringWriter.scala b/examples/scala-js/javalib/src/main/scala/java/io/StringWriter.scala
new file mode 100644
index 0000000..13eca00
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/StringWriter.scala
@@ -0,0 +1,44 @@
+package java.io
+
+class StringWriter extends Writer {
+
+ private[this] val buf = new StringBuffer
+
+ def this(initialSize: Int) = this()
+
+ override def write(c: Int): Unit =
+ buf.append(c.toChar)
+
+ def write(cbuf: Array[Char], off: Int, len: Int): Unit =
+ buf.append(cbuf, off, len)
+
+ override def write(str: String): Unit =
+ buf.append(str)
+
+ override def write(str: String, off: Int, len: Int): Unit =
+ buf.append(str, off, off + len) // Third param is 'end', not 'len'
+
+ override def append(csq: CharSequence): StringWriter = {
+ buf.append(csq)
+ this
+ }
+
+ override def append(csq: CharSequence, start: Int, end: Int): StringWriter = {
+ buf.append(csq, start, end)
+ this
+ }
+
+ override def append(c: Char): StringWriter = {
+ buf.append(c)
+ this
+ }
+
+ override def toString(): String = buf.toString
+
+ def getBuffer(): StringBuffer = buf
+
+ def flush(): Unit = ()
+
+ def close(): Unit = ()
+
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Throwables.scala b/examples/scala-js/javalib/src/main/scala/java/io/Throwables.scala
new file mode 100644
index 0000000..c312c4c
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/Throwables.scala
@@ -0,0 +1,19 @@
+package java.io
+
+class IOException(s: String, e: Throwable) extends Exception(s, e) {
+ def this(e: Throwable) = this(null, e)
+ def this(s: String) = this(s, null)
+ def this() = this(null, null)
+}
+
+class EOFException(s: String) extends IOException(s) {
+ def this() = this(null)
+}
+
+class UTFDataFormatException(s: String) extends IOException(s) {
+ def this() = this(null)
+}
+
+class UnsupportedEncodingException(s: String) extends IOException(s) {
+ def this() = this(null)
+}
diff --git a/examples/scala-js/javalib/src/main/scala/java/io/Writer.scala b/examples/scala-js/javalib/src/main/scala/java/io/Writer.scala
new file mode 100644
index 0000000..d63b477
--- /dev/null
+++ b/examples/scala-js/javalib/src/main/scala/java/io/Writer.scala
@@ -0,0 +1,45 @@
+package java.io
+
+abstract class Writer private[this] (_lock: Option[Object]) extends
+ Appendable with Closeable with Flushable {
+
+ protected val lock = _lock.getOrElse(this)
+
+ protected def this(lock: Object) = this(Some(lock))
+ protected def this() = this(None)
+
+ def write(c: Int): Unit =
+ write(Array(c.toChar))
+
+ def write(cbuf: Array[Char]): Unit =
+ write(cbuf, 0, cbuf.length)
+
+ def write(cbuf: Array[Char], off: Int, len: Int): Unit
+
+ def write(str: String): Unit =
+ write(str.toCharArray)
+
+ def write(str: String, off: Int, len: Int): Unit =
+ write(str.toCharArray, off, len)
+
+ def append(csq: CharSequence): Writer = {
+ write(if (csq == null) "null" else csq.toString)
+ this
+ }
+
+ def append(csq: CharSequence, start: Int, end: Int): Writer = {
+ val csq1 = if (csq == null) "null" else csq
+ write(csq1.subSequence(start, end).toString)
+ this
+ }
+
+ def append(c: Char): Writer = {
+ write(c.toInt)
+ this
+ }
+
+ def flush(): Unit
+
+ def close(): Unit
+
+}