summaryrefslogtreecommitdiff
path: root/examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala
diff options
context:
space:
mode:
authorHaoyi Li <haoyi@haoyi-mbp.corp.dropbox.com>2014-11-26 00:45:31 -0800
committerHaoyi Li <haoyi@haoyi-mbp.corp.dropbox.com>2014-11-26 00:45:31 -0800
commit24f31e120f9537faede7a174bb09ee35f64e1ce4 (patch)
tree06ffc3ecc7847789008352b7e2b7c040dad48907 /examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala
parentb89ce9cbf79363f8cab09186a5d7ba94bc0af02a (diff)
parent2c4b142503bd2d871e6818b5cab8c38627d9e4a0 (diff)
downloadhands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.tar.gz
hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.tar.bz2
hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.zip
Merge commit '2c4b142503bd2d871e6818b5cab8c38627d9e4a0' as 'examples/scala-js'
Diffstat (limited to 'examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala')
-rw-r--r--examples/scala-js/javalib/src/main/scala/java/io/PrintStream.scala218
1 files changed, 218 insertions, 0 deletions
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)
+ }
+
+}