diff options
author | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
---|---|---|
committer | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
commit | 24f31e120f9537faede7a174bb09ee35f64e1ce4 (patch) | |
tree | 06ffc3ecc7847789008352b7e2b7c040dad48907 /examples/scala-js/javalib | |
parent | b89ce9cbf79363f8cab09186a5d7ba94bc0af02a (diff) | |
parent | 2c4b142503bd2d871e6818b5cab8c38627d9e4a0 (diff) | |
download | hands-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')
63 files changed, 5806 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 + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/net/URI.scala b/examples/scala-js/javalib/src/main/scala/java/net/URI.scala new file mode 100644 index 0000000..c969f55 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/net/URI.scala @@ -0,0 +1,706 @@ +package java.net + +import scala.scalajs.js.RegExp +import scala.scalajs.js +import scala.scalajs.js.{decodeURIComponent => decode} + +import scala.annotation.tailrec + +final class URI(origStr: String) extends Serializable with Comparable[URI] { + + import URI.Fields._ + + /** The fields matched in the regular expression. + * + * This is a local val for the primary constructor. It is a val, + * since we'll set it to null after initializing all fields. + */ + private[this] var _fld = Option(URI.uriRe.exec(origStr)).getOrElse { + throw new URISyntaxException(origStr, "Malformed URI") + } + + private val _isAbsolute = fld(AbsScheme).isDefined + private val _isOpaque = fld(AbsOpaquePart).isDefined + + @inline private def fld(idx: Int): js.UndefOr[String] = _fld(idx) + + @inline private def fld(absIdx: Int, relIdx: Int): js.UndefOr[String] = + if (_isAbsolute) _fld(absIdx) else _fld(relIdx) + + private val _scheme = fld(AbsScheme) + + private val _schemeSpecificPart = { + if (!_isAbsolute) fld(RelSchemeSpecificPart) + else if (_isOpaque) fld(AbsOpaquePart) + else fld(AbsHierPart) + }.get + + private val _authority = fld(AbsAuthority, RelAuthority) + private val _userInfo = fld(AbsUserInfo, RelUserInfo) + private val _host = fld(AbsHost, RelHost) + private val _port = fld(AbsPort, RelPort).fold(-1)(_.toInt) + + private val _path = { + if (_isAbsolute) { + if (_authority.isDefined) fld(AbsNetPath) + else fld(AbsAbsPath) + } else { + if (_authority.isDefined) fld(RelNetPath) + else fld(RelAbsPath) orElse fld(RelRelPath) + } + } + + private val _query = fld(AbsQuery, RelQuery) + private val _fragment = fld(Fragment) + + // End of default ctor. Unset helper field + _fld = null + + def this(scheme: String, ssp: String, fragment: String) = + this(URI.uriStr(scheme, ssp, fragment)) + + def this(scheme: String, userInfo: String, host: String, port: Int, + path: String, query: String, fragment: String) = { + this(URI.uriStr(scheme, userInfo, host, port, path, query, fragment)) + parseServerAuthority() + } + + def this(scheme: String, host: String, path: String, fragment: String) = + this(scheme, null, host, -1, path, null, fragment) + + def this(scheme: String, authority: String, path: String, query: String, + fragment: String) = { + this(URI.uriStr(scheme, authority, path, query, fragment)) + // JavaDoc says to invoke parseServerAuthority() here, but in practice + // it isn't invoked. This makes sense, since you want to be able + // to create URIs with registry-based authorities. + // parseServerAuthority() + } + + /** Compare this URI to another URI while supplying a comparator + * + * This helper is required to account for the semantic differences + * between [[compareTo]] and [[equals]]. ([[equals]] does treat + * URI escapes specially: they are never case-sensitive). + */ + @inline + private def internalCompare(that: URI)(cmp: (String, String) => Int): Int = { + @inline def cmpOpt(x: js.UndefOr[String], y: js.UndefOr[String]): Int = { + if (x == y) 0 + // Undefined components are considered less than defined components + else x.fold(-1)(s1 => y.fold(1)(s2 => cmp(s1, s2))) + } + + if (this._scheme != that._scheme) + this._scheme.fold(-1)(s1 => that._scheme.fold(1)(s1.compareToIgnoreCase)) + else if (this._isOpaque != that._isOpaque) + // A hierarchical URI is less than an opaque URI + if (this._isOpaque) 1 else -1 + else if (_isOpaque) { + val ssp = cmp(this._schemeSpecificPart, that._schemeSpecificPart) + if (ssp != 0) ssp + else cmpOpt(this._fragment, that._fragment) + } else if (this._authority != that._authority) { + if (this._host.isDefined && that._host.isDefined) { + val ui = cmpOpt(this._userInfo, that._userInfo) + if (ui != 0) ui + else { + val hst = this._host.get.compareToIgnoreCase(that._host.get) + if (hst != 0) hst + else if (this._port == that._port) 0 + else if (this._port == -1) -1 + else if (that._port == -1) 1 + else this._port - that._port + } + } else + cmpOpt(this._authority, that._authority) + } else if (this._path != that._path) + cmpOpt(this._path, that._path) + else if (this._query != that._query) + cmpOpt(this._query, that._query) + else + cmpOpt(this._fragment, that._fragment) + } + + def compareTo(that: URI): Int = internalCompare(that)(_.compareTo(_)) + + override def equals(that: Any): Boolean = that match { + case that: URI => internalCompare(that)(URI.escapeAwareCompare) == 0 + case _ => false + } + + def getAuthority(): String = _authority.map(decode).orNull + def getFragment(): String = _fragment.map(decode).orNull + def getHost(): String = _host.orNull + def getPath(): String = _path.map(decode).orNull + def getPort(): Int = _port + def getQuery(): String = _query.map(decode).orNull + def getRawAuthority(): String = _authority.orNull + def getRawFragment(): String = _fragment.orNull + def getRawPath(): String = _path.orNull + def getRawQuery(): String = _query.orNull + def getRawSchemeSpecificPart(): String = _schemeSpecificPart + def getRawUserInfo(): String = _userInfo.orNull + def getScheme(): String = _scheme.orNull + def getSchemeSpecificPart(): String = decode(_schemeSpecificPart) + def getUserInfo(): String = _userInfo.map(decode).orNull + + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + import URI.normalizeEscapes + + var acc = URI.uriSeed + acc = mix(acc, _scheme.##) // scheme may not contain escapes + acc = mix(acc, normalizeEscapes(_schemeSpecificPart).##) + acc = mixLast(acc, _fragment.map(normalizeEscapes).##) + + finalizeHash(acc, 3) + } + + def isAbsolute(): Boolean = _isAbsolute + def isOpaque(): Boolean = _isOpaque + + def normalize(): URI = if (_isOpaque || _path.isEmpty) this else { + val origPath = _path.get + + // Step 1: Remove all "." segments + // Step 2: Remove ".." segments preceeded by non ".." segment until no + // longer applicable + + /** Checks whether a successive ".." may drop the head of a + * reversed segment list. + */ + def okToDropFrom(resRev: List[String]) = + resRev.nonEmpty && resRev.head != ".." && resRev.head != "" + + @tailrec + def loop(in: List[String], resRev: List[String]): List[String] = in match { + case "." :: Nil => + // convert "." segments at end to an empty segment + // (consider: /a/b/. => /a/b/, not /a/b) + loop(Nil, "" :: resRev) + case ".." :: Nil if okToDropFrom(resRev) => + // prevent a ".." segment at end to change a "dir" into a "file" + // (consider: /a/b/.. => /a/, not /a) + loop(Nil, "" :: resRev.tail) + case "." :: xs => + // remove "." segments + loop(xs, resRev) + case "" :: xs if xs.nonEmpty => + // remove empty segments not at end of path + loop(xs, resRev) + case ".." :: xs if okToDropFrom(resRev) => + // Remove preceeding non-".." segment + loop(xs, resRev.tail) + case x :: xs => + loop(xs, x :: resRev) + case Nil => + resRev.reverse + } + + // Split into segments. -1 since we want empty trailing ones + val segments0 = origPath.split("/", -1).toList + val isAbsPath = segments0.nonEmpty && segments0.head == "" + // Don't inject first empty segment into normalization loop, so we + // won't need to special case it. + val segments1 = if (isAbsPath) segments0.tail else segments0 + val segments2 = loop(segments1, Nil) + + // Step 3: If path is relative and first segment contains ":", prepend "." + // segment (according to JavaDoc). If it is absolute, add empty + // segment again to have leading "/". + val segments3 = { + if (isAbsPath) + "" :: segments2 + else if (segments2.nonEmpty && segments2.head.contains(':')) + "." :: segments2 + else segments2 + } + + val newPath = segments3.mkString("/") + + // Only create new instance if anything changed + if (newPath == origPath) + this + else + new URI(getScheme(), getRawAuthority(), newPath, getQuery(), getFragment()) + } + + def parseServerAuthority(): URI = { + if (_authority.nonEmpty && _host.isEmpty) + throw new URISyntaxException(origStr, "No Host in URI") + else this + } + + def relativize(uri: URI): URI = { + def authoritiesEqual = this._authority.fold(uri._authority.isEmpty) { a1 => + uri._authority.fold(false)(a2 => URI.escapeAwareCompare(a1, a2) == 0) + } + + if (this.isOpaque || uri.isOpaque || + this._scheme != uri._scheme || !authoritiesEqual) uri + else { + val thisN = this.normalize() + val uriN = uri.normalize() + + // Strangely, Java doesn't handle escapes here. So we don't + if (uriN.getRawPath().startsWith(thisN.getRawPath())) { + val newPath = uriN.getRawPath().stripPrefix(thisN.getRawPath()) + + new URI(scheme = null, authority = null, + // never produce an abs path if we relativized + path = newPath.stripPrefix("/"), + query = uri.getQuery(), fragment = uri.getFragment()) + } else uri + } + } + + def resolve(str: String): URI = resolve(URI.create(str)) + + def resolve(uri: URI): URI = { + if (uri.isAbsolute() || this.isOpaque()) uri + else if (uri._scheme.isEmpty && uri._authority.isEmpty && + uri._path.get == "" && uri._query.isEmpty) + // This is a special case for URIs like: "#foo". This allows to + // just change the fragment in the current document. + new URI( + this.getScheme(), + this.getRawAuthority(), + this.getRawPath(), + this.getRawQuery(), + uri.getRawFragment()) + else if (uri._authority.isDefined) + new URI( + this.getScheme(), + uri.getRawAuthority(), + uri.getRawPath(), + uri.getRawQuery(), + uri.getRawFragment()) + else if (uri._path.get.startsWith("/")) + new URI( + this.getScheme(), + this.getRawAuthority(), + uri.getRawPath(), + uri.getRawQuery(), + uri.getRawFragment()) + else { + val basePath = this._path.get + val relPath = uri._path.get + val endIdx = basePath.lastIndexOf('/') + val path = + if (endIdx == -1) relPath + else basePath.substring(0, endIdx+1) + relPath + new URI( + this.getScheme(), + this.getAuthority(), + path, + uri.getRawQuery(), + uri.getRawFragment()).normalize() + } + } + + def toASCIIString(): String = origStr // We allow only ASCII in URIs. + override def toString(): String = origStr + + // Not implemented: + // def toURL(): URL + +} + +object URI { + + def create(str: String): URI = { + try new URI(str) + catch { + case e: URISyntaxException => throw new IllegalArgumentException(e) + } + } + + // IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit + private final val ipv4address = "[0-9]{1,3}(?:\\.[0-9]{1,3}){3}" + + private final val ipv6address = { + // http://stackoverflow.com/a/17871737/1149944 + val block = "[0-9a-f]{1,4}" + val lelem = "(?:"+block+":)" + val relem = "(?::"+block+")" + val ipv4 = ipv4address + + "(?:" + + lelem+"{7}"+block+"|"+ // 1:2:3:4:5:6:7:8 + lelem+"{1,7}:|"+ // 1:: 1:2:3:4:5:6:7:: + lelem+"{1,6}"+relem+"|"+ // 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 + lelem+"{1,5}"+relem+"{1,2}|"+ // 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 + lelem+"{1,4}"+relem+"{1,3}|"+ // 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 + lelem+"{1,3}"+relem+"{1,4}|"+ // 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 + lelem+"{1,2}"+relem+"{1,5}|"+ // 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 + lelem +relem+"{1,6}|"+ // 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 + ":(?:"+relem+"{1,7}|:)|" + // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: + lelem+"{6}"+ipv4+"|"+ // 1:2:3:4:5:6:10.0.0.1 + lelem+"{1,5}:"+ipv4+"|"+ // 1::10.0.0.1 1:2:3:4:5::10.0.0.1 + lelem+"{1,4}"+relem+":"+ipv4+"|"+ // 1::6:10.0.0.1 1:2:3:4::6:10.0.0.1 + lelem+"{1,3}"+relem+"{1,2}:"+ipv4+"|"+ // 1::5:6:10.0.0.1 1:2:3::5:6:10.0.0.1 1:2:3::6:10.0.0.1 + lelem+"{1,2}"+relem+"{1,3}:"+ipv4+"|"+ // 1::4:5:6:10.0.0.1 1:2::4:5:6:10.0.0.1 1:2::6:10.0.0.1 + lelem +relem+"{1,4}:"+ipv4+"|"+ // 1::3:4:5:6:10.0.0.1 1::3:4:5:6:10.0.0.1 1::6:10.0.0.1 + "::"+lelem+"{1,5}"+ipv4+ // ::2:3:4:5:10.0.0.1 ::5:10.0.0.1 ::10.0.0.1 + ")(?:%[0-9a-z]+)?" + + // This was part of the original regex, but is too specific to + // IPv6 details. + // fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}| # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) + // ::(ffff(:0{1,4}){0,1}:){0,1} + // ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + // (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses) + // ([0-9a-fA-F]{1,4}:){1,4}: + // ((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3} + // (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address) + } + + private val ipv6Re = new RegExp("^"+ipv6address+"$", "i") + + // URI syntax parser. Based on RFC2396, RFC2732 and adaptations according to + // JavaDoc. + // - http://www.ietf.org/rfc/rfc2396.txt (see Appendix A for complete syntax) + // - http://www.ietf.org/rfc/rfc2732.txt + + private val uriRe = { + // We don't use any interpolators here to allow for constant folding + + /////////////////// + //// Helpers //// + /////////////////// + + // Inlined definitions + // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | + // "$" | "," | "[" | "]" ; last two added by RFC2732 + // unreserved = alphanum | mark + // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | + // "(" | ")" + + // escaped = "%" hex hex + val escaped = "%[a-f0-9]{2}" + + // uric = reserved | unreserved | escaped + val uric = "(?:[;/?:@&=+$,\\[\\]a-z0-9-_.!~*'()]|"+escaped+")" + + // pchar = unreserved | escaped | + // ":" | "@" | "&" | "=" | "+" | "$" | "," + val pchar = "(?:[a-z0-9-_.!~*'():@&=+$,]|"+escaped+")" + + /////////////////// + //// Server //// + /////////////////// + + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + val domainlabel = "(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])" + + // toplabel = alpha | alpha *( alphanum | "-" ) alphanum + val toplabel = "(?:[a-z]|[a-z][a-z0-9-]*[a-z0-9])" + + // hostname = *( domainlabel "." ) toplabel [ "." ] + val hostname = "(?:"+domainlabel+"\\.)*"+toplabel+"\\.?" + + // IPv6reference = "[" IPv6address "]" + val ipv6reference = "\\[(?:"+ipv6address+")\\]" + + // host = hostname | IPv4address | IPv6reference + // ; IPv6reference added by RFC2732 + val host = "("+hostname+"|"+ipv4address+"|"+ipv6reference+")" /*CAPT*/ + + // Inlined definition + // port = *digit + + // hostport = host [ ":" port ] + val hostport = host+"(?::([0-9]*))?" /*CAPT*/ + + // userinfo = *( unreserved | escaped | + // ";" | ":" | "&" | "=" | "+" | "$" | "," ) + val userinfo = "(?:[a-z0-9-_.!~*'();:&=+$,]|"+escaped+")*" + + // server = [ [ userinfo "@" ] hostport ] + val server = "(?:(?:("+userinfo+")@)?"+hostport+")?" /*CAPT*/ + + /////////////////// + //// Authority //// + /////////////////// + + // reg_name = 1*( unreserved | escaped | "$" | "," | + // ";" | ":" | "@" | "&" | "=" | "+" ) + val reg_name = "(?:[a-z0-9-_.!~*'()$,;:@&=+]|"+escaped+")+" + + // authority = server | reg_name + val authority = server+"|"+reg_name + + /////////////////// + //// Paths //// + /////////////////// + + // Inlined definitions + // param = *pchar + + // segment = *pchar *( ";" param ) + val segment = pchar+"*(?:;"+pchar+"*)*" + + // path_segments = segment *( "/" segment ) + val path_segments = segment+"(?:/"+segment+")*" + + // abs_path = "/" path_segments + val abs_path = "/"+path_segments + + // net_path = "//" authority [ abs_path ] + val net_path = "//("+authority+")("+abs_path+")?" /*2CAPT*/ + + // Inlined definition + // Deviation from RCF2396 according to JavaDoc: Allow empty rel_segment + // and hence empty rel_path + // rel_segment = 1*( unreserved | escaped | + // ";" | "@" | "&" | "=" | "+" | "$" | "," ) + + // rel_path = rel_segment [ abs_path ] + val rel_path = "(?:[a-z0-9-_.!~*'();@&=+$,]|"+escaped+")*(?:"+abs_path+")?" + + /////////////////// + /// Query/Frag /// + /////////////////// + + // query = *uric + val query = "("+uric+"*)" /*CAPT*/ + // fragment = *uric + val fragment = "("+uric+"*)" /*CAPT*/ + + /////////////////// + /// Parts /// + /////////////////// + + // hier_part = ( net_path | abs_path ) [ "?" query ] + val hier_part = "(?:"+net_path+"|("+abs_path+"))(?:\\?"+query+")?" /*CAPT*/ + + // Inlined definition + // uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | + // "&" | "=" | "+" | "$" | "," + + // opaque_part = uric_no_slash *uric + val opaque_part = "(?:[a-z0-9-_.!~*'();?:@&=+$,]|"+escaped+")"+uric+"*" + + /////////////////// + /// URIs /// + /////////////////// + + // scheme = alpha *( alpha | digit | "+" | "-" | "." ) + val scheme = "([a-z][a-z0-9+-.]*)" /*CAPT*/ + + // absoluteURI = scheme ":" ( hier_part | opaque_part ) + val absoluteURI = scheme+":(?:("+hier_part+")|("+opaque_part+"))" /*2CAPT*/ + + // relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] + val relativeURI = /*2CAPT*/ + "(?:"+net_path+"|("+abs_path+")|("+rel_path+"))(?:\\?"+query+")?" + + // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] + val uriRef = "^(?:"+absoluteURI+"|"+relativeURI+")(?:#"+fragment+")?$" + + new RegExp(uriRef, "i") + } + + private object Fields { + final val AbsScheme = 1 + final val AbsHierPart = AbsScheme+1 + final val AbsAuthority = AbsHierPart+1 + final val AbsUserInfo = AbsAuthority+1 + final val AbsHost = AbsUserInfo+1 + final val AbsPort = AbsHost+1 + final val AbsNetPath = AbsPort+1 // abs_path part only + final val AbsAbsPath = AbsNetPath+1 + final val AbsQuery = AbsAbsPath+1 + final val AbsOpaquePart = AbsQuery+1 + final val RelSchemeSpecificPart = 0 // It's the whole string + final val RelAuthority = AbsOpaquePart+1 + final val RelUserInfo = RelAuthority+1 + final val RelHost = RelUserInfo+1 + final val RelPort = RelHost+1 + final val RelNetPath = RelPort+1 // abs_path part only + final val RelAbsPath = RelNetPath+1 + final val RelRelPath = RelAbsPath+1 + final val RelQuery = RelRelPath+1 + final val Fragment = RelQuery+1 + } + + // Helpers for constructors + + private def uriStr(scheme: String, ssp: String, fragment: String): String = { + var resStr = "" + + if (scheme != null) + resStr += scheme + ":" + + if (ssp != null) + resStr += quoteIllegal(ssp) + + if (fragment != null) + resStr += "#" + quoteIllegal(fragment) + + resStr + } + + private def uriStr(scheme: String, userInfo: String, host: String, port: Int, + path: String, query: String, fragment: String): String = { + var resStr = "" + + if (scheme != null) + resStr += scheme + ":" + + if (userInfo != null || host != null || port != -1) + resStr += "//" + + if (userInfo != null) + resStr += quoteUserInfo(userInfo) + "@" + + if (host != null) { + if (URI.ipv6Re.test(host)) + resStr += "[" + host + "]" + else + resStr += host + } + + if (port != -1) + resStr += ":" + port + + if (path != null) + resStr += quotePath(path) + + if (query != null) + resStr += "?" + quoteIllegal(query) + + if (fragment != null) + resStr += "#" + quoteIllegal(fragment) + + resStr + } + + private def uriStr(scheme: String, authority: String, path: String, + query: String, fragment: String) = { + var resStr = "" + + if (scheme != null) + resStr += scheme + ":" + + if (authority != null) + resStr += "//" + quoteAuthority(authority) + + if (path != null) + resStr += quotePath(path) + + if (query != null) + resStr += "?" + quoteIllegal(query) + + if (fragment != null) + resStr += "#" + quoteIllegal(fragment) + + resStr + } + + // Quote helpers + + private val quoteChar: js.Function1[String, String] = { (str: String) => + require(str.length == 1) + + val c = str.head.toInt + + if (c > 127) + throw new URISyntaxException(null, "Only ASCII allowed in URIs") + else + f"%%$c%02x" + } + + /** matches any character not in unreserved, punct, escaped or other */ + private val userInfoQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, punct, escaped or other */ + private def quoteUserInfo(str: String) = + (str: js.prim.String).replace(userInfoQuoteRe, quoteChar) + + /** matches any character not in unreserved, punct, escaped, other or equal + * to '/' or '@' + */ + private val pathQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s@/]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, punct, escaped, other or equal + * to '/' or '@' + */ + private def quotePath(str: String) = + (str: js.prim.String).replace(pathQuoteRe, quoteChar) + + /** matches any character not in unreserved, punct, escaped, other or equal + * to '@', '[' or ']' + * The last two are different to how JavaDoc specifies, but hopefully yield + * the same behavior. (We shouldn't escape [], since they may occur + * in IPv6 addresses, but technically speaking they are in reserved + * due to RFC2732). + */ + private val authorityQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=%\\s@\\[\\]]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, punct, escaped, other or equal + * to '@' + */ + private def quoteAuthority(str: String) = + (str: js.prim.String).replace(authorityQuoteRe, quoteChar) + + /** matches any character not in unreserved, reserved, escaped or other */ + private val illegalQuoteRe = + new RegExp("[^a-z0-9-_.!~*'(),;:$&+=?/\\[\\]%\\s]|%(?![0-9a-f]{2})", "ig") + + /** Quote any character not in unreserved, reserved, escaped or other */ + private def quoteIllegal(str: String) = + (str: js.prim.String).replace(illegalQuoteRe, quoteChar) + + /** Case-sensitive comparison that is case-insensitive inside URI + * escapes. Will compare `a%A0` and `a%a0` as equal, but `a%A0` and + * `A%A0` as different. + */ + private def escapeAwareCompare(x: String, y: String): Int = { + @tailrec + def loop(i: Int): Int = { + if (i >= x.length || i >= y.length) + x.length - y.length + else { + val diff = x.charAt(i) - y.charAt(i) + if (diff != 0) diff + else if (x.charAt(i) == '%') { + // we need to do a CI compare for the next two characters + assert(x.length > i + 2, "Invalid escape in URI") + assert(y.length > i + 2, "Invalid escape in URI") + val cmp = + x.substring(i+1, i+3).compareToIgnoreCase(y.substring(i+1, i+3)) + if (cmp != 0) cmp + else loop(i+3) + } else loop(i+1) + } + } + + loop(0) + } + + /** Upper-cases all URI escape sequences in `str`. Used for hashing */ + private def normalizeEscapes(str: String): String = { + var i = 0 + var res = "" + while (i < str.length) { + if (str.charAt(i) == '%') { + assert(str.length > i + 2, "Invalid escape in URI") + res += str.substring(i, i+3).toUpperCase() + i += 3 + } else { + res += str.substring(i, i+1) + i += 1 + } + } + + res + } + + private final val uriSeed = 53722356 + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/net/URISyntaxException.scala b/examples/scala-js/javalib/src/main/scala/java/net/URISyntaxException.scala new file mode 100644 index 0000000..85e0879 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/net/URISyntaxException.scala @@ -0,0 +1,15 @@ +package java.net + +class URISyntaxException( + private val input: String, + private val reason: String, + private val index: Int) extends Exception( + s"$reason in $input at $index") { + + def this(input: String, reason: String) = this(input, reason, -1) + + def getIndex(): Int = index + def getInput(): String = input + def getReason(): String = reason + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/Buffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/Buffer.scala new file mode 100644 index 0000000..be7ab7f --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/Buffer.scala @@ -0,0 +1,83 @@ +package java.nio + +abstract class Buffer private[nio] (val _capacity: Int) { + private var _limit: Int = capacity + private var _position: Int = 0 + private[nio] var _mark: Int = -1 + + final def capacity(): Int = _capacity + + final def position(): Int = _position + + final def position(newPosition: Int): Buffer = { + if (newPosition < 0 || newPosition > limit()) + throw new IllegalArgumentException + _position = newPosition + if (_mark > newPosition) + _mark = -1 + this + } + + final def limit(): Int = _limit + + final def limit(newLimit: Int): Buffer = { + if (newLimit < 0 || newLimit > capacity()) + throw new IllegalArgumentException + _limit = newLimit + if (_position > newLimit) { + _position = newLimit + if (_mark > newLimit) + _mark = -1 + } + this + } + + final def mark(): Buffer = { + _mark = _position + this + } + + final def reset(): Buffer = { + if (_mark == -1) + throw new InvalidMarkException + _position = _mark + this + } + + final def clear(): Buffer = { + _mark = -1 + _position = 0 + _limit = capacity + this + } + + final def flip(): Buffer = { + _mark = -1 + _limit = _position + _position = 0 + this + } + + final def rewind(): Buffer = { + _mark = -1 + _position = 0 + this + } + + @inline final def remaining(): Int = limit - position + + @inline final def hasRemaining(): Boolean = position != limit + + def isReadOnly(): Boolean + + def hasArray(): Boolean + + def array(): Object + + def arrayOffset(): Int + + def isDirect(): Boolean + + override def toString(): String = + s"${getClass.getName}[pos=$position lim=$limit cap=$capacity]" +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/BufferOverflowException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/BufferOverflowException.scala new file mode 100644 index 0000000..03f359e --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/BufferOverflowException.scala @@ -0,0 +1,3 @@ +package java.nio + +class BufferOverflowException extends RuntimeException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/BufferUnderflowException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/BufferUnderflowException.scala new file mode 100644 index 0000000..e286975 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/BufferUnderflowException.scala @@ -0,0 +1,3 @@ +package java.nio + +class BufferUnderflowException extends RuntimeException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/ByteBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/ByteBuffer.scala new file mode 100644 index 0000000..b743b39 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/ByteBuffer.scala @@ -0,0 +1,227 @@ +package java.nio + +object ByteBuffer { + private final val HashSeed = -547316498 // "java.nio.ByteBuffer".## + + def allocate(capacity: Int): ByteBuffer = + wrap(new Array[Byte](capacity)) + + def allocateDirect(capacity: Int): ByteBuffer = + allocate(capacity) + + def wrap(array: Array[Byte], offset: Int, length: Int): ByteBuffer = + HeapByteBuffer.wrap(array, 0, array.length, offset, length, false) + + def wrap(array: Array[Byte]): ByteBuffer = + wrap(array, 0, array.length) +} + +abstract class ByteBuffer private[nio] ( + _capacity: Int, private[nio] val _array: Array[Byte], + private[nio] val _arrayOffset: Int) + extends Buffer(_capacity) with Comparable[ByteBuffer] { + + def this(_capacity: Int) = this(_capacity, null, -1) + + private var _order: ByteOrder = ByteOrder.BIG_ENDIAN + + def slice(): ByteBuffer + + def duplicate(): ByteBuffer + + def asReadOnlyBuffer(): ByteBuffer + + def get(): Byte + + def put(b: Byte): ByteBuffer + + def get(index: Int): Byte + + def put(index: Int, b: Byte): ByteBuffer + + def get(dst: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + if (remaining < length) + throw new BufferUnderflowException + + var i = offset + while (i != end) { + dst(i) = get() + i += 1 + } + + this + } + + def get(dst: Array[Byte]): ByteBuffer = + get(dst, 0, dst.length) + + def put(src: ByteBuffer): ByteBuffer = { + if (src eq this) + throw new IllegalArgumentException + if (isReadOnly) + throw new ReadOnlyBufferException + if (src.remaining > remaining) + throw new BufferOverflowException + + var n = src.remaining + if (src._array != null) { // even if read-only + val pos = src.position + put(src._array, src._arrayOffset + pos, n) + src.position(pos + n) + } else { + while (n != 0) { + put(src.get()) + n -= 1 + } + } + + this + } + + def put(src: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + if (remaining < length) + throw new BufferOverflowException + + var i = offset + while (i != end) { + put(src(i)) + i += 1 + } + + this + } + + final def put(src: Array[Byte]): ByteBuffer = + put(src, 0, src.length) + + @inline final def hasArray(): Boolean = _array != null && !isReadOnly + + @inline final def array(): Array[Byte] = { + val a = _array + if (a == null) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + a + } + + @inline final def arrayOffset(): Int = { + val o = _arrayOffset + if (o == -1) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + o + } + + def compact(): ByteBuffer + + // Not implemented: + //def isDirect(): Boolean + + // toString(): String inherited from Buffer + + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + val start = position + val end = limit + var h = ByteBuffer.HashSeed + var i = start + while (i != end) { + h = mix(h, get().##) + i += 1 + } + position(start) + finalizeHash(h, end-start) + } + + override def equals(that: Any): Boolean = that match { + case that: ByteBuffer => compareTo(that) == 0 + case _ => false + } + + def compareTo(that: ByteBuffer): Int = { + if (this eq that) { + 0 + } else { + val thisStart = this.position + val thisRemaining = this.remaining + val thatStart = that.position + val thatRemaining = that.remaining + val shortestLength = Math.min(thisRemaining, thatRemaining) + + var i = 0 + while (i != shortestLength) { + val cmp = this.get().compareTo(that.get()) + if (cmp != 0) { + this.position(thisStart) + that.position(thatStart) + return cmp + } + i += 1 + } + + this.position(thisStart) + that.position(thatStart) + thisRemaining.compareTo(thatRemaining) + } + } + + final def order(): ByteOrder = _order + + final def order(bo: ByteOrder): ByteBuffer = { + if (bo == null) + throw new NullPointerException + _order = bo + this + } + + /* Not implemented: + + def getChar(): Char + def putChar(value: Char): ByteBuffer + def getChar(index: Int): Char + def putChar(index: Int, value: Char): ByteBuffer + def asCharBuffer(): CharBuffer + + def getShort(): Short + def putShort(value: Short): ByteBuffer + def getShort(index: Int): Short + def putShort(index: Int, value: Short): ByteBuffer + def asShortBuffer(): ShortBuffer + + def getInt(): Int + def putInt(value: Int): ByteBuffer + def getInt(index: Int): Int + def putInt(index: Int, value: Int): ByteBuffer + def asIntBuffer(): IntBuffer + + def getLong(): Long + def putLong(value: Long): ByteBuffer + def getLong(index: Int): Long + def putLong(index: Int, value: Long): ByteBuffer + def asLongBuffer(): LongBuffer + + def getFloat(): Float + def putFloat(value: Float): ByteBuffer + def getFloat(index: Int): Float + def putFloat(index: Int, value: Float): ByteBuffer + def asFloatBuffer(): FloatBuffer + + def getDouble(): Double + def putDouble(value: Double): ByteBuffer + def getDouble(index: Int): Double + def putDouble(index: Int, value: Double): ByteBuffer + def asDoubleBuffer(): DoubleBuffer + + */ +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/ByteOrder.scala b/examples/scala-js/javalib/src/main/scala/java/nio/ByteOrder.scala new file mode 100644 index 0000000..20bac6a --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/ByteOrder.scala @@ -0,0 +1,15 @@ +package java.nio + +final class ByteOrder private (name: String) { + override def toString(): String = name +} + +object ByteOrder { + val BIG_ENDIAN: ByteOrder = new ByteOrder("BIG_ENDIAN") + val LITTLE_ENDIAN: ByteOrder = new ByteOrder("LITTLE_ENDIAN") + + def nativeOrder(): ByteOrder = { + if (scala.scalajs.runtime.Bits.areTypedArraysBigEndian) BIG_ENDIAN + else LITTLE_ENDIAN + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/CharBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/CharBuffer.scala new file mode 100644 index 0000000..5e74953 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/CharBuffer.scala @@ -0,0 +1,228 @@ +package java.nio + +object CharBuffer { + private final val HashSeed = -182887236 // "java.nio.CharBuffer".## + + def allocate(capacity: Int): CharBuffer = + wrap(new Array[Char](capacity)) + + def wrap(array: Array[Char], offset: Int, length: Int): CharBuffer = + HeapCharBuffer.wrap(array, 0, array.length, offset, length, false) + + def wrap(array: Array[Char]): CharBuffer = + wrap(array, 0, array.length) + + def wrap(csq: CharSequence, start: Int, end: Int): CharBuffer = + StringCharBuffer.wrap(csq, 0, csq.length, start, end) + + def wrap(csq: CharSequence): CharBuffer = + wrap(csq, 0, csq.length) +} + +abstract class CharBuffer private[nio] ( + _capacity: Int, private[nio] val _array: Array[Char], + private[nio] val _arrayOffset: Int) + extends Buffer(_capacity) with Comparable[CharBuffer] + with CharSequence with Appendable with Readable { + + def this(_capacity: Int) = this(_capacity, null, -1) + + def read(target: CharBuffer): Int = { + // Attention: this method must not change this buffer's position + val n = remaining + if (n == 0) -1 + else if (_array != null) { // even if read-only + target.put(_array, _arrayOffset, n) + n + } else { + val savedPos = position + target.put(this) + position(savedPos) + n + } + } + + def slice(): CharBuffer + + def duplicate(): CharBuffer + + def asReadOnlyBuffer(): CharBuffer + + def get(): Char + + def put(c: Char): CharBuffer + + def get(index: Int): Char + + def put(index: Int, c: Char): CharBuffer + + def get(dst: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + if (remaining < length) + throw new BufferUnderflowException + + var i = offset + while (i != end) { + dst(i) = get() + i += 1 + } + + this + } + + def get(dst: Array[Char]): CharBuffer = + get(dst, 0, dst.length) + + def put(src: CharBuffer): CharBuffer = { + if (src eq this) + throw new IllegalArgumentException + if (isReadOnly) + throw new ReadOnlyBufferException + if (src.remaining > remaining) + throw new BufferOverflowException + + var n = src.remaining + if (src._array != null) { // even if read-only + val pos = src.position + put(src._array, src._arrayOffset + pos, n) + src.position(pos + n) + } else { + while (n != 0) { + put(src.get()) + n -= 1 + } + } + + this + } + + def put(src: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + if (remaining < length) + throw new BufferOverflowException + + var i = offset + while (i != end) { + put(src(i)) + i += 1 + } + + this + } + + final def put(src: Array[Char]): CharBuffer = + put(src, 0, src.length) + + def put(src: String, start: Int, end: Int): CharBuffer = + put(CharBuffer.wrap(src, start, end)) + + final def put(src: String): CharBuffer = + put(src, 0, src.length) + + @inline final def hasArray(): Boolean = _array != null && !isReadOnly + + @inline final def array(): Array[Char] = { + val a = _array + if (a == null) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + a + } + + @inline final def arrayOffset(): Int = { + val o = _arrayOffset + if (o == -1) + throw new UnsupportedOperationException + if (isReadOnly) + throw new ReadOnlyBufferException + o + } + + def compact(): CharBuffer + + // Not implemented: + //def isDirect(): Boolean + + override def hashCode(): Int = { + import scala.util.hashing.MurmurHash3._ + val start = position + val end = limit + var h = CharBuffer.HashSeed + var i = start + while (i != end) { + h = mix(h, get().##) + i += 1 + } + position(start) + finalizeHash(h, end-start) + } + + override def equals(that: Any): Boolean = that match { + case that: CharBuffer => compareTo(that) == 0 + case _ => false + } + + def compareTo(that: CharBuffer): Int = { + if (this eq that) { + 0 + } else { + val thisStart = this.position + val thisRemaining = this.remaining + val thatStart = that.position + val thatRemaining = that.remaining + val shortestLength = Math.min(thisRemaining, thatRemaining) + + var i = 0 + while (i != shortestLength) { + val cmp = this.get().compareTo(that.get()) + if (cmp != 0) { + this.position(thisStart) + that.position(thatStart) + return cmp + } + i += 1 + } + + this.position(thisStart) + that.position(thatStart) + thisRemaining.compareTo(thatRemaining) + } + } + + override def toString(): String = { + if (_array != null) { // even if read-only + new String(_array, position + _arrayOffset, remaining) + } else { + val chars = new Array[Char](remaining) + val savedPos = position + get(chars) + position(savedPos) + new String(chars) + } + } + + final def length(): Int = remaining + + final def charAt(index: Int): Char = get(position + index) + + def subSequence(start: Int, end: Int): CharSequence + + def append(csq: CharSequence): CharBuffer = + put(csq.toString()) + + def append(csq: CharSequence, start: Int, end: Int): CharBuffer = + put(csq.subSequence(start, end).toString()) + + def append(c: Char): CharBuffer = + put(c) + + def order(): ByteOrder +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/HeapByteBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/HeapByteBuffer.scala new file mode 100644 index 0000000..ed3fd29 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/HeapByteBuffer.scala @@ -0,0 +1,129 @@ +package java.nio + +private[nio] final class HeapByteBuffer private ( + _capacity: Int, _array0: Array[Byte], _arrayOffset0: Int, + _initialPosition: Int, _initialLimit: Int, _readOnly: Boolean) + extends ByteBuffer(_capacity, _array0, _arrayOffset0) { + + position(_initialPosition) + limit(_initialLimit) + + def isReadOnly(): Boolean = _readOnly + + def isDirect(): Boolean = false + + def slice(): ByteBuffer = { + val cap = remaining + new HeapByteBuffer(cap, _array, _arrayOffset+position, 0, cap, isReadOnly) + } + + def duplicate(): ByteBuffer = { + val result = new HeapByteBuffer(capacity, _array, _arrayOffset, + position, limit, isReadOnly) + result._mark = this._mark + result + } + + def asReadOnlyBuffer(): ByteBuffer = { + val result = new HeapByteBuffer(capacity, _array, _arrayOffset, + position, limit, true) + result._mark = this._mark + result + } + + def get(): Byte = { + if (!hasRemaining) + throw new BufferUnderflowException + val p = position + position(p + 1) + _array(_arrayOffset + p) + } + + def put(b: Byte): ByteBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (!hasRemaining) + throw new BufferOverflowException + val p = position + _array(_arrayOffset + p) = b + position(p + 1) + this + } + + def get(index: Int): Byte = { + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) + } + + def put(index: Int, b: Byte): ByteBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) = b + this + } + + override def get(dst: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferUnderflowException + + System.arraycopy(_array, startPos + _arrayOffset, dst, offset, length) + position(endPos) + + this + } + + override def put(src: Array[Byte], offset: Int, length: Int): ByteBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferOverflowException + + System.arraycopy(src, offset, _array, startPos + _arrayOffset, length) + position(endPos) + + this + } + + def compact(): ByteBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + + val offset = _arrayOffset + val len = remaining + System.arraycopy(_array, offset + position, _array, offset, len) + _mark = -1 + limit(capacity) + position(len) + this + } +} + +private[nio] object HeapByteBuffer { + private[nio] def wrap(array: Array[Byte], arrayOffset: Int, capacity: Int, + initialPosition: Int, initialLength: Int, + isReadOnly: Boolean): ByteBuffer = { + if (arrayOffset < 0 || capacity < 0 || arrayOffset+capacity > array.length) + throw new IndexOutOfBoundsException + val initialLimit = initialPosition + initialLength + if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) + throw new IndexOutOfBoundsException + new HeapByteBuffer(capacity, array, arrayOffset, + initialPosition, initialLimit, isReadOnly) + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/HeapCharBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/HeapCharBuffer.scala new file mode 100644 index 0000000..546c55d --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/HeapCharBuffer.scala @@ -0,0 +1,138 @@ +package java.nio + +private[nio] final class HeapCharBuffer private ( + _capacity: Int, _array0: Array[Char], _arrayOffset0: Int, + _initialPosition: Int, _initialLimit: Int, _readOnly: Boolean) + extends CharBuffer(_capacity, _array0, _arrayOffset0) { + + position(_initialPosition) + limit(_initialLimit) + + def isReadOnly(): Boolean = _readOnly + + def isDirect(): Boolean = false + + def slice(): CharBuffer = { + val cap = remaining + new HeapCharBuffer(cap, _array, _arrayOffset + position, 0, cap, isReadOnly) + } + + def duplicate(): CharBuffer = { + val result = new HeapCharBuffer(capacity, _array, _arrayOffset, + position, limit, isReadOnly) + result._mark = this._mark + result + } + + def asReadOnlyBuffer(): CharBuffer = { + val result = new HeapCharBuffer(capacity, _array, _arrayOffset, + position, limit, true) + result._mark = this._mark + result + } + + def subSequence(start: Int, end: Int): CharBuffer = { + if (start < 0 || end < start || end > remaining) + throw new IndexOutOfBoundsException + new HeapCharBuffer(capacity, _array, _arrayOffset, + position + start, position + end, isReadOnly) + } + + def get(): Char = { + if (!hasRemaining) + throw new BufferUnderflowException + val p = position + position(p + 1) + _array(_arrayOffset + p) + } + + def put(c: Char): CharBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (!hasRemaining) + throw new BufferOverflowException + val p = position + _array(_arrayOffset + p) = c + position(p + 1) + this + } + + def get(index: Int): Char = { + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) + } + + def put(index: Int, b: Char): CharBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _array(_arrayOffset + index) = b + this + } + + override def get(dst: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferUnderflowException + + System.arraycopy(_array, startPos + _arrayOffset, dst, offset, length) + position(endPos) + + this + } + + override def put(src: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + if (offset < 0 || length < 0 || end > src.length) + throw new IndexOutOfBoundsException + if (isReadOnly) + throw new ReadOnlyBufferException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferOverflowException + + System.arraycopy(src, offset, _array, startPos + _arrayOffset, length) + position(endPos) + + this + } + + def compact(): CharBuffer = { + if (isReadOnly) + throw new ReadOnlyBufferException + + val offset = _arrayOffset + val len = remaining + System.arraycopy(_array, offset + position, _array, offset, len) + _mark = -1 + limit(capacity) + position(len) + this + } + + def order(): ByteOrder = ByteOrder.nativeOrder() +} + +private[nio] object HeapCharBuffer { + private[nio] def wrap(array: Array[Char], arrayOffset: Int, capacity: Int, + initialPosition: Int, initialLength: Int, + isReadOnly: Boolean): CharBuffer = { + if (arrayOffset < 0 || capacity < 0 || arrayOffset+capacity > array.length) + throw new IndexOutOfBoundsException + val initialLimit = initialPosition + initialLength + if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) + throw new IndexOutOfBoundsException + new HeapCharBuffer(capacity, array, arrayOffset, + initialPosition, initialLimit, isReadOnly) + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/InvalidMarkException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/InvalidMarkException.scala new file mode 100644 index 0000000..c2d3714 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/InvalidMarkException.scala @@ -0,0 +1,3 @@ +package java.nio + +class InvalidMarkException extends IllegalStateException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala new file mode 100644 index 0000000..ee0868b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala @@ -0,0 +1,3 @@ +package java.nio + +class ReadOnlyBufferException extends UnsupportedOperationException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/StringCharBuffer.scala b/examples/scala-js/javalib/src/main/scala/java/nio/StringCharBuffer.scala new file mode 100644 index 0000000..25bc594 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/StringCharBuffer.scala @@ -0,0 +1,102 @@ +package java.nio + +private[nio] final class StringCharBuffer private ( + _capacity: Int, private[this] var _csq: CharSequence, + private[this] var _csqOffset: Int, + _initialPosition: Int, _initialLimit: Int) + extends CharBuffer(_capacity) { + + position(_initialPosition) + limit(_initialLimit) + + def isReadOnly(): Boolean = true + + def isDirect(): Boolean = false + + def slice(): CharBuffer = { + val cap = remaining + new StringCharBuffer(cap, _csq, _csqOffset + position, 0, cap) + } + + def duplicate(): CharBuffer = { + val result = new StringCharBuffer(capacity, _csq, _csqOffset, + position, limit) + result._mark = this._mark + result + } + + def asReadOnlyBuffer(): CharBuffer = duplicate() + + def subSequence(start: Int, end: Int): CharBuffer = { + if (start < 0 || end < start || end > remaining) + throw new IndexOutOfBoundsException + new StringCharBuffer(capacity, _csq, _csqOffset, + position + start, position + end) + } + + def get(): Char = { + if (!hasRemaining) + throw new BufferUnderflowException + val p = position + position(p + 1) + _csq.charAt(_csqOffset + p) + } + + def put(c: Char): CharBuffer = + throw new ReadOnlyBufferException + + def get(index: Int): Char = { + if (index < 0 || index >= limit) + throw new IndexOutOfBoundsException + _csq.charAt(_csqOffset + index) + } + + def put(index: Int, b: Char): CharBuffer = + throw new ReadOnlyBufferException + + override def get(dst: Array[Char], offset: Int, length: Int): CharBuffer = { + val end = offset + length + + if (offset < 0 || length < 0 || end > dst.length) + throw new IndexOutOfBoundsException + + val startPos = position + val endPos = startPos + length + if (endPos > limit) + throw new BufferUnderflowException + + var i = offset + var j = startPos + _csqOffset + while (i != end) { + dst(i) = _csq.charAt(j) + i += 1 + j += 1 + } + position(endPos) + + this + } + + def compact(): CharBuffer = + throw new ReadOnlyBufferException + + override def toString(): String = { + val offset = _csqOffset + _csq.subSequence(position + offset, limit + offset).toString() + } + + def order(): ByteOrder = ByteOrder.nativeOrder() +} + +private[nio] object StringCharBuffer { + private[nio] def wrap(csq: CharSequence, csqOffset: Int, capacity: Int, + initialPosition: Int, initialLength: Int): CharBuffer = { + if (csqOffset < 0 || capacity < 0 || csqOffset+capacity > csq.length) + throw new IndexOutOfBoundsException + val initialLimit = initialPosition + initialLength + if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) + throw new IndexOutOfBoundsException + new StringCharBuffer(capacity, csq, csqOffset, + initialPosition, initialLimit) + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala new file mode 100644 index 0000000..8017348 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala @@ -0,0 +1,3 @@ +package java.nio.charset + +class CharacterCodingException extends java.io.IOException diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/Charset.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/Charset.scala new file mode 100644 index 0000000..6d1af47 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/Charset.scala @@ -0,0 +1,103 @@ +package java.nio.charset + +import java.nio.{ByteBuffer, CharBuffer} + +import scala.scalajs.js + +abstract class Charset protected (canonicalName: String, + aliases: Array[String]) extends AnyRef with Comparable[Charset] { + final def name(): String = canonicalName + + override final def equals(that: Any): Boolean = that match { + case that: Charset => this.name == that.name + case _ => false + } + + override final def toString(): String = name() + + override final def hashCode(): Int = name.## + + override final def compareTo(that: Charset): Int = + name.compareToIgnoreCase(that.name) + + def contains(cs: Charset): Boolean + + def newDecoder(): CharsetDecoder + def newEncoder(): CharsetEncoder + + def canEncode(): Boolean = true + + private lazy val cachedDecoder = { + this.newDecoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + } + + private lazy val cachedEncoder = { + this.newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE) + } + + final def decode(bb: ByteBuffer): CharBuffer = + cachedDecoder.decode(bb) + + final def encode(cb: CharBuffer): ByteBuffer = + cachedEncoder.encode(cb) + + final def encode(str: String): ByteBuffer = + encode(CharBuffer.wrap(str)) + + def displayName(): String = name +} + +object Charset { + import StandardCharsets._ + + def defaultCharset(): Charset = + UTF_8 + + def forName(charsetName: String): Charset = + CharsetMap.getOrElse(charsetName.toLowerCase, + throw new UnsupportedCharsetException(charsetName)) + + def isSupported(charsetName: String): Boolean = + CharsetMap.contains(charsetName.toLowerCase) + + private lazy val CharsetMap = { + val m = js.Dictionary.empty[Charset] + + // All these lists where obtained by experimentation on the JDK + + for (s <- Seq("iso-8859-1", "iso8859-1", "iso_8859_1", "iso8859_1", + "iso_8859-1", "8859_1", "iso_8859-1:1987", + "latin1", "csisolatin1", "l1", + "ibm-819", "ibm819", "cp819", "819", + "iso-ir-100")) + m(s) = ISO_8859_1 + + for (s <- Seq("us-ascii", "ascii7", "ascii", "csascii", + "default", + "cp367", "ibm367", + "iso646-us", "646", "iso_646.irv:1983", "iso_646.irv:1991", + "ansi_x3.4-1986", "ansi_x3.4-1968", + "iso-ir-6")) + m(s) = US_ASCII + + for (s <- Seq("utf-8", "utf_8", "utf8", "unicode-1-1-utf-8")) + m(s) = UTF_8 + + for (s <- Seq("utf-16be", "utf_16be", "x-utf-16be", + "iso-10646-ucs-2", "unicodebigunmarked")) + m(s) = UTF_16BE + + for (s <- Seq("utf-16le", "utf_16le", "x-utf-16le", + "unicodelittleunmarked")) + m(s) = UTF_16LE + + for (s <- Seq("utf-16", "utf_16", "unicode", "unicodebig")) + m(s) = UTF_16 + + m + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala new file mode 100644 index 0000000..a3532ba --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala @@ -0,0 +1,217 @@ +package java.nio.charset + +import scala.annotation.{switch, tailrec} + +import java.nio._ + +abstract class CharsetDecoder protected (cs: Charset, + _averageCharsPerByte: Float, _maxCharsPerByte: Float) { + + import CharsetDecoder._ + + // Config + + private[this] var _replacement: String = "\uFFFD" + private[this] var _malformedInputAction: CodingErrorAction = + CodingErrorAction.REPORT + private[this] var _unmappableCharacterAction: CodingErrorAction = + CodingErrorAction.REPORT + + // Status + + private[this] var status: Int = INIT + + // Methods + + final def charset(): Charset = cs + + final def replacement(): String = _replacement + + final def replaceWith(newReplacement: String): CharsetDecoder = { + if (newReplacement == null || newReplacement == "") + throw new IllegalArgumentException("Invalid replacement: "+newReplacement) + if (newReplacement.length > maxCharsPerByte) + throw new IllegalArgumentException( + "Replacement string cannot be longer than maxCharsPerByte") + _replacement = newReplacement + implReplaceWith(newReplacement) + this + } + + protected def implReplaceWith(newReplacement: String): Unit = () + + def malformedInputAction(): CodingErrorAction = _malformedInputAction + + final def onMalformedInput(newAction: CodingErrorAction): CharsetDecoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _malformedInputAction = newAction + implOnMalformedInput(newAction) + this + } + + protected def implOnMalformedInput(newAction: CodingErrorAction): Unit = () + + def unmappableCharacterAction(): CodingErrorAction = _unmappableCharacterAction + + final def onUnmappableCharacter(newAction: CodingErrorAction): CharsetDecoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _unmappableCharacterAction = newAction + implOnUnmappableCharacter(newAction) + this + } + + protected def implOnUnmappableCharacter(newAction: CodingErrorAction): Unit = () + + final def averageCharsPerByte(): Float = _averageCharsPerByte + final def maxCharsPerByte(): Float = _maxCharsPerByte + + final def decode(in: ByteBuffer, out: CharBuffer, + endOfInput: Boolean): CoderResult = { + + if (status == FLUSHED || (!endOfInput && status == END)) + throw new IllegalStateException + + status = if (endOfInput) END else ONGOING + + @inline + @tailrec + def loop(): CoderResult = { + val result1 = try { + decodeLoop(in, out) + } catch { + case ex: BufferOverflowException => + throw new CoderMalfunctionError(ex) + case ex: BufferUnderflowException => + throw new CoderMalfunctionError(ex) + } + + val result2 = if (result1.isUnderflow) { + val remaining = in.remaining + if (endOfInput && remaining > 0) + CoderResult.malformedForLength(remaining) + else + result1 + } else { + result1 + } + + if (result2.isUnderflow || result2.isOverflow) { + result2 + } else { + val action = + if (result2.isUnmappable) unmappableCharacterAction + else malformedInputAction + + action match { + case CodingErrorAction.REPLACE => + if (out.remaining < replacement.length) { + CoderResult.OVERFLOW + } else { + out.put(replacement) + in.position(in.position + result2.length) + loop() + } + case CodingErrorAction.REPORT => + result2 + case CodingErrorAction.IGNORE => + in.position(in.position + result2.length) + loop() + } + } + } + + loop() + } + + final def flush(out: CharBuffer): CoderResult = { + (status: @switch) match { + case END => + val result = implFlush(out) + if (result.isUnderflow) + status = FLUSHED + result + case FLUSHED => + CoderResult.UNDERFLOW + case _ => + throw new IllegalStateException + } + } + + protected def implFlush(out: CharBuffer): CoderResult = + CoderResult.UNDERFLOW + + final def reset(): CharsetDecoder = { + status = INIT + implReset() + this + } + + protected def implReset(): Unit = () + + protected def decodeLoop(in: ByteBuffer, out: CharBuffer): CoderResult + + final def decode(in: ByteBuffer): CharBuffer = { + def grow(out: CharBuffer): CharBuffer = { + if (out.capacity == 0) { + CharBuffer.allocate(1) + } else { + val result = CharBuffer.allocate(out.capacity*2) + out.flip() + result.put(out) + result + } + } + + @inline + @tailrec + def loopDecode(out: CharBuffer): CharBuffer = { + val result = decode(in, out, endOfInput = true) + if (result.isUnderflow) { + assert(!in.hasRemaining) + out + } else if (result.isOverflow) { + loopDecode(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + @inline + @tailrec + def loopFlush(out: CharBuffer): CharBuffer = { + val result = flush(out) + if (result.isUnderflow) { + out + } else if (result.isOverflow) { + loopFlush(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + reset() + val initLength = (in.remaining.toDouble * averageCharsPerByte).toInt + val out = loopFlush(loopDecode(CharBuffer.allocate(initLength))) + out.flip() + out + } + + def isAutoDetecting(): Boolean = false + + def isCharsetDetected(): Boolean = + throw new UnsupportedOperationException + + def detectedCharset(): Charset = + throw new UnsupportedOperationException +} + +object CharsetDecoder { + private final val INIT = 1 + private final val ONGOING = 2 + private final val END = 3 + private final val FLUSHED = 4 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala new file mode 100644 index 0000000..37d2296 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala @@ -0,0 +1,235 @@ +package java.nio.charset + +import scala.annotation.{switch, tailrec} + +import java.nio._ + +abstract class CharsetEncoder protected (cs: Charset, + _averageBytesPerChar: Float, _maxBytesPerChar: Float, + private[this] var _replacement: Array[Byte]) { + + import CharsetEncoder._ + + protected def this(cs: Charset, _averageBytesPerChar: Float, + _maxBytesPerChar: Float) = + this(cs, _averageBytesPerChar, _averageBytesPerChar, Array('?'.toByte)) + + // Config + + private[this] var _malformedInputAction: CodingErrorAction = + CodingErrorAction.REPORT + private[this] var _unmappableCharacterAction: CodingErrorAction = + CodingErrorAction.REPORT + + // Status + + private[this] var status: Int = INIT + + // Methods + + final def charset(): Charset = cs + + final def replacement(): Array[Byte] = _replacement + + final def replaceWith(newReplacement: Array[Byte]): CharsetEncoder = { + if (newReplacement == null || newReplacement.length == 0 || + newReplacement.length > maxBytesPerChar || + !isLegalReplacement(newReplacement)) + throw new IllegalArgumentException + + _replacement = newReplacement + implReplaceWith(newReplacement) + this + } + + protected def implReplaceWith(newReplacement: Array[Byte]): Unit = () + + def isLegalReplacement(repl: Array[Byte]): Boolean = { + val decoder = charset.newDecoder + val replBuf = ByteBuffer.wrap(repl) + + @inline + @tailrec + def loop(outBufSize: Int): Boolean = { + val result = decoder.decode(replBuf, CharBuffer.allocate(outBufSize), true) + if (result.isOverflow) { + loop(outBufSize * 2) + } else { + !replBuf.hasRemaining + } + } + + loop(2) + } + + def malformedInputAction(): CodingErrorAction = _malformedInputAction + + final def onMalformedInput(newAction: CodingErrorAction): CharsetEncoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _malformedInputAction = newAction + implOnMalformedInput(newAction) + this + } + + protected def implOnMalformedInput(newAction: CodingErrorAction): Unit = () + + def unmappableCharacterAction(): CodingErrorAction = _unmappableCharacterAction + + final def onUnmappableCharacter(newAction: CodingErrorAction): CharsetEncoder = { + if (newAction == null) + throw new IllegalArgumentException("null CodingErrorAction") + _unmappableCharacterAction = newAction + implOnUnmappableCharacter(newAction) + this + } + + protected def implOnUnmappableCharacter(newAction: CodingErrorAction): Unit = () + + final def averageBytesPerChar(): Float = _averageBytesPerChar + final def maxBytesPerChar(): Float = _maxBytesPerChar + + final def encode(in: CharBuffer, out: ByteBuffer, + endOfInput: Boolean): CoderResult = { + + if (status == FLUSHED || (!endOfInput && status == END)) + throw new IllegalStateException + + status = if (endOfInput) END else ONGOING + + @inline + @tailrec + def loop(): CoderResult = { + val result1 = try { + encodeLoop(in, out) + } catch { + case ex: BufferOverflowException => + throw new CoderMalfunctionError(ex) + case ex: BufferUnderflowException => + throw new CoderMalfunctionError(ex) + } + + val result2 = if (result1.isUnderflow) { + val remaining = in.remaining + if (endOfInput && remaining > 0) + CoderResult.malformedForLength(remaining) + else + result1 + } else { + result1 + } + + if (result2.isUnderflow || result2.isOverflow) { + result2 + } else { + val action = + if (result2.isUnmappable) unmappableCharacterAction + else malformedInputAction + + action match { + case CodingErrorAction.REPLACE => + if (out.remaining < replacement.length) { + CoderResult.OVERFLOW + } else { + out.put(replacement) + in.position(in.position + result2.length) + loop() + } + case CodingErrorAction.REPORT => + result2 + case CodingErrorAction.IGNORE => + in.position(in.position + result2.length) + loop() + } + } + } + + loop() + } + + final def flush(out: ByteBuffer): CoderResult = { + (status: @switch) match { + case END => + val result = implFlush(out) + if (result.isUnderflow) + status = FLUSHED + result + case FLUSHED => + CoderResult.UNDERFLOW + case _ => + throw new IllegalStateException + } + } + + protected def implFlush(out: ByteBuffer): CoderResult = + CoderResult.UNDERFLOW + + final def reset(): CharsetEncoder = { + status = INIT + implReset() + this + } + + protected def implReset(): Unit = () + + protected def encodeLoop(arg1: CharBuffer, arg2: ByteBuffer): CoderResult + + final def encode(in: CharBuffer): ByteBuffer = { + def grow(out: ByteBuffer): ByteBuffer = { + if (out.capacity == 0) { + ByteBuffer.allocate(1) + } else { + val result = ByteBuffer.allocate(out.capacity*2) + out.flip() + result.put(out) + result + } + } + + if (in.remaining == 0) { + ByteBuffer.allocate(0) + } else { + @inline + @tailrec + def loopEncode(out: ByteBuffer): ByteBuffer = { + val result = encode(in, out, endOfInput = true) + if (result.isUnderflow) { + assert(!in.hasRemaining) + out + } else if (result.isOverflow) { + loopEncode(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + @inline + @tailrec + def loopFlush(out: ByteBuffer): ByteBuffer = { + val result = flush(out) + if (result.isUnderflow) { + out + } else if (result.isOverflow) { + loopFlush(grow(out)) + } else { + result.throwException() + throw new AssertionError("should not get here") + } + } + + reset() + val initLength = (in.remaining * averageBytesPerChar).toInt + val out = loopFlush(loopEncode(ByteBuffer.allocate(initLength))) + out.flip() + out + } + } +} + +object CharsetEncoder { + private final val INIT = 0 + private final val ONGOING = 1 + private final val END = 2 + private final val FLUSHED = 3 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala new file mode 100644 index 0000000..33174f3 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala @@ -0,0 +1,3 @@ +package java.nio.charset + +class CoderMalfunctionError(cause: Exception) extends Error(cause) diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderResult.scala new file mode 100644 index 0000000..fdc63cc --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -0,0 +1,78 @@ +package java.nio.charset + +import scala.annotation.switch + +import scala.collection.mutable + +import java.nio._ + +class CoderResult private (kind: Int, _length: Int) { + import CoderResult._ + + @inline def isUnderflow(): Boolean = kind == Underflow + @inline def isOverflow(): Boolean = kind == Overflow + @inline def isMalformed(): Boolean = kind == Malformed + @inline def isUnmappable(): Boolean = kind == Unmappable + + @inline def isError(): Boolean = isMalformed || isUnmappable + + @inline def length(): Int = { + val l = _length + if (l < 0) + throw new UnsupportedOperationException + l + } + + def throwException(): Unit = (kind: @switch) match { + case Overflow => throw new BufferOverflowException + case Underflow => throw new BufferUnderflowException + case Malformed => throw new MalformedInputException(_length) + case Unmappable => throw new UnmappableCharacterException(_length) + } +} + +object CoderResult { + private final val Underflow = 0 + private final val Overflow = 1 + private final val Malformed = 2 + private final val Unmappable = 3 + + val OVERFLOW: CoderResult = new CoderResult(Overflow, -1) + val UNDERFLOW: CoderResult = new CoderResult(Underflow, -1) + + private val Malformed1 = new CoderResult(Malformed, 1) + private val Malformed2 = new CoderResult(Malformed, 2) + private val Malformed3 = new CoderResult(Malformed, 3) + private val Malformed4 = new CoderResult(Malformed, 4) + + private val uniqueMalformed = mutable.Map.empty[Int, CoderResult] + + private val Unmappable1 = new CoderResult(Unmappable, 1) + private val Unmappable2 = new CoderResult(Unmappable, 2) + private val Unmappable3 = new CoderResult(Unmappable, 3) + private val Unmappable4 = new CoderResult(Unmappable, 4) + + private val uniqueUnmappable = mutable.Map.empty[Int, CoderResult] + + @inline def malformedForLength(length: Int): CoderResult = (length: @switch) match { + case 1 => Malformed1 + case 2 => Malformed2 + case 3 => Malformed3 + case 4 => Malformed4 + case _ => malformedForLengthImpl(length) + } + + private def malformedForLengthImpl(length: Int): CoderResult = + uniqueMalformed.getOrElseUpdate(length, new CoderResult(Malformed, length)) + + @inline def unmappableForLength(length: Int): CoderResult = (length: @switch) match { + case 1 => Unmappable1 + case 2 => Unmappable2 + case 3 => Unmappable3 + case 4 => Unmappable4 + case _ => unmappableForLengthImpl(length) + } + + private def unmappableForLengthImpl(length: Int): CoderResult = + uniqueUnmappable.getOrElseUpdate(length, new CoderResult(Unmappable, length)) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala new file mode 100644 index 0000000..63b48bb --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala @@ -0,0 +1,11 @@ +package java.nio.charset + +class CodingErrorAction private (name: String) { + override def toString(): String = name +} + +object CodingErrorAction { + val IGNORE = new CodingErrorAction("IGNORE") + val REPLACE = new CodingErrorAction("REPLACE") + val REPORT = new CodingErrorAction("REPORT") +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala new file mode 100644 index 0000000..4c91c1b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala @@ -0,0 +1,9 @@ +package java.nio.charset + +class MalformedInputException( + inputLength: Int) extends CharacterCodingException { + def getInputLength(): Int = inputLength + + override def getMessage(): String = + "Input length = " + inputLength +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala new file mode 100644 index 0000000..38f3f98 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala @@ -0,0 +1,14 @@ +package java.nio.charset + +final class StandardCharsets private {} + +object StandardCharsets { + import scala.scalajs.niocharset.{StandardCharsets => SC} + + def ISO_8859_1: Charset = SC.ISO_8859_1 + def US_ASCII: Charset = SC.US_ASCII + def UTF_8: Charset = SC.UTF_8 + def UTF_16BE: Charset = SC.UTF_16BE + def UTF_16LE: Charset = SC.UTF_16LE + def UTF_16: Charset = SC.UTF_16 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala new file mode 100644 index 0000000..5748f70 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala @@ -0,0 +1,9 @@ +package java.nio.charset + +class UnmappableCharacterException( + inputLength: Int) extends CharacterCodingException { + def getInputLength(): Int = inputLength + + override def getMessage(): String = + "Input length = " + inputLength +} diff --git a/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala new file mode 100644 index 0000000..97a7a4e --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala @@ -0,0 +1,6 @@ +package java.nio.charset + +class UnsupportedCharsetException( + charsetName: String) extends IllegalArgumentException(charsetName) { + def getCharsetName(): String = charsetName +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Arrays.scala b/examples/scala-js/javalib/src/main/scala/java/util/Arrays.scala new file mode 100644 index 0000000..ed9afd1 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Arrays.scala @@ -0,0 +1,401 @@ +package java.util + +import scala.annotation.tailrec + +object Arrays { + def sort[T <: Object](array: Array[Object], comparator: Comparator[T]): Unit = { + scala.util.Sorting.stableSort[Object](array, + (a: Object, b: Object) => + comparator.compare(a.asInstanceOf[T], b.asInstanceOf[T]) < 0) + } + + def fill(a: Array[Boolean], value: Boolean): Unit = + fillImpl(a, value) + + def fill(a: Array[Boolean], fromIndex: Int, toIndex: Int, value: Boolean): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Byte], value: Byte): Unit = + fillImpl(a, value) + + def fill(a: Array[Byte], fromIndex: Int, toIndex: Int, value: Byte): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Char], value: Char): Unit = + fillImpl(a, value) + + def fill(a: Array[Char], fromIndex: Int, toIndex: Int, value: Char): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Short], value: Short): Unit = + fillImpl(a, value) + + def fill(a: Array[Short], fromIndex: Int, toIndex: Int, value: Short): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Int], value: Int): Unit = + fillImpl(a, value) + + def fill(a: Array[Int], fromIndex: Int, toIndex: Int, value: Int): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Long], value: Long): Unit = + fillImpl(a, value) + + def fill(a: Array[Long], fromIndex: Int, toIndex: Int, value: Long): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Float], value: Float): Unit = + fillImpl(a, value) + + def fill(a: Array[Float], fromIndex: Int, toIndex: Int, value: Float): Unit = + fillImpl(a, fromIndex, toIndex, value) + + def fill(a: Array[Double], value: Double): Unit = + fillImpl(a, value) + + def fill(a: Array[Double], fromIndex: Int, toIndex: Int, value: Double): Unit = + fillImpl(a, fromIndex, toIndex, value) + + private def fillImpl[@specialized T](a: Array[T], value: T): Unit = { + var i = 0 + while (i != a.length) { + a(i) = value + i += 1 + } + } + + private def fillImpl[@specialized T](a: Array[T], + fromIndex: Int, toIndex: Int, value: T): Unit = { + if (fromIndex > toIndex) + throw new IllegalArgumentException + if (fromIndex < 0 || toIndex > a.length) + throw new ArrayIndexOutOfBoundsException + + var i = fromIndex + while (i != toIndex) { + a(i) = value + i += 1 + } + } + + def fill(a: Array[AnyRef], value: AnyRef): Unit = { + var i = 0 + while (i < a.length) { + a(i) = value + i += 1 + } + } + + def fill(a: Array[AnyRef], + fromIndex: Int, toIndex: Int, value: AnyRef): Unit = { + if (fromIndex > toIndex) + throw new IllegalArgumentException + if (fromIndex < 0 || toIndex > a.length) + throw new ArrayIndexOutOfBoundsException + + var i = fromIndex + while (i < toIndex) { + a(i) = value + i += 1 + } + } + + @inline private def checkIndexForBinarySearch( + length: Int, start: Int, end: Int): Unit = { + if (start > end) + throw new IllegalArgumentException("fromIndex(" + start + ") > toIndex(" + end + ")") + if (start < 0) + throw new ArrayIndexOutOfBoundsException("Array index out of range: " + start) + if (end > length) + throw new ArrayIndexOutOfBoundsException("Array index out of range: " + end) + } + + def binarySearch(a: Array[Char], key: Char): Int = + binarySearchImpl[Char](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Char], + startIndex: Int, endIndex: Int, key: Char): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Char](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Short], key: Short): Int = + binarySearchImpl[Short](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Short], + startIndex: Int, endIndex: Int, key: Short): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Short](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Int], key: Int): Int = + binarySearchImpl[Int](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Int], + startIndex: Int, endIndex: Int, key: Int): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Int](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Long], key: Long): Int = + binarySearchImpl[Long](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Long], + startIndex: Int, endIndex: Int, key: Long): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Long](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Float], key: Float): Int = + binarySearchImpl[Float](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Float], + startIndex: Int, endIndex: Int, key: Float): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Float](a, startIndex, endIndex, key, _ < _) + } + + def binarySearch(a: Array[Double], key: Double): Int = + binarySearchImpl[Double](a, 0, a.length, key, _ < _) + + def binarySearch(a: Array[Double], + startIndex: Int, endIndex: Int, key: Double): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImpl[Double](a, startIndex, endIndex, key, _ < _) + } + + @inline + @tailrec + private def binarySearchImpl[@specialized T](a: Array[T], + startIndex: Int, endIndex: Int, key: T, lt: (T, T) => Boolean): Int = { + if (startIndex == endIndex) { + // Not found + -startIndex - 1 + } else { + // Indices are unsigned 31-bit integer, so this does not overflow + val mid = (startIndex + endIndex) >>> 1 + val elem = a(mid) + if (lt(key, elem)) { + binarySearchImpl(a, startIndex, mid, key, lt) + } else if (key == elem) { + // Found + mid + } else { + binarySearchImpl(a, mid + 1, endIndex, key, lt) + } + } + } + + def binarySearch(a: Array[AnyRef], key: AnyRef): Int = + binarySearchImplRef(a, 0, a.length, key) + + def binarySearch(a: Array[AnyRef], + startIndex: Int, endIndex: Int, key: AnyRef): Int = { + checkIndexForBinarySearch(a.length, startIndex, endIndex) + binarySearchImplRef(a, startIndex, endIndex, key) + } + + @inline + @tailrec + def binarySearchImplRef(a: Array[AnyRef], + startIndex: Int, endIndex: Int, key: AnyRef): Int = { + if (startIndex == endIndex) { + // Not found + -startIndex - 1 + } else { + // Indices are unsigned 31-bit integer, so this does not overflow + val mid = (startIndex + endIndex) >>> 1 + val cmp = key.asInstanceOf[Comparable[AnyRef]].compareTo(a(mid)) + if (cmp < 0) { + binarySearchImplRef(a, startIndex, mid, key) + } else if (cmp == 0) { + // Found + mid + } else { + binarySearchImplRef(a, mid + 1, endIndex, key) + } + } + } + + def copyOf(original: Array[Boolean], newLength: Int): Array[Boolean] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Boolean], start: Int, end: Int): Array[Boolean] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Char], newLength: Int): Array[Char] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Char], start: Int, end: Int): Array[Char] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Byte], newLength: Int): Array[Byte] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Byte], start: Int, end: Int): Array[Byte] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Short], newLength: Int): Array[Short] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Short], start: Int, end: Int): Array[Short] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Int], newLength: Int): Array[Int] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Int], start: Int, end: Int): Array[Int] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Long], newLength: Int): Array[Long] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Long], start: Int, end: Int): Array[Long] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Float], newLength: Int): Array[Float] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Float], start: Int, end: Int): Array[Float] = + copyOfRangeImpl(original, start, end, new Array(_)) + + def copyOf(original: Array[Double], newLength: Int): Array[Double] = + copyOfImpl(original, newLength, new Array(_)) + + def copyOfRange(original: Array[Double], start: Int, end: Int): Array[Double] = + copyOfRangeImpl(original, start, end, new Array(_)) + + @inline private def copyOfImpl[@specialized T](original: Array[T], + newLength: Int, newArray: Int => Array[T]): Array[T] = { + checkArrayLength(newLength) + val copyLength = Math.min(newLength, original.length) + val ret = newArray(newLength) + System.arraycopy(original, 0, ret, 0, copyLength) + ret + } + + @inline private def copyOfRangeImpl[@specialized T](original: Array[T], + start: Int, end: Int, newArray: Int => Array[T]): Array[T] = { + checkIndicesForCopyOfRange(original.length, start, end) + val retLength = end - start + val copyLength = Math.min(retLength, original.length - start) + val ret = newArray(retLength) + System.arraycopy(original, start, ret, 0, copyLength) + ret + } + + def copyOf(original: Array[AnyRef], newLength: Int): Array[AnyRef] = { + checkArrayLength(newLength) + val copyLength = Math.min(newLength, original.length) + val ret = java.lang.reflect.Array.newInstance( + original.getClass.getComponentType, newLength).asInstanceOf[Array[AnyRef]] + System.arraycopy(original, 0, ret, 0, copyLength) + ret + } + + def copyOfRange(original: Array[AnyRef], start: Int, end: Int): Array[AnyRef] = { + checkIndicesForCopyOfRange(original.length, start, end) + val retLength = end - start + val copyLength = Math.min(retLength, original.length - start) + val ret = java.lang.reflect.Array.newInstance( + original.getClass.getComponentType, retLength).asInstanceOf[Array[AnyRef]] + System.arraycopy(original, start, ret, 0, copyLength) + ret + } + + @inline private def checkArrayLength(len: Int): Unit = { + if (len < 0) + throw new NegativeArraySizeException + } + + @inline private def checkIndicesForCopyOfRange( + len: Int, start: Int, end: Int): Unit = { + if (start > end) + throw new IllegalArgumentException(start + " > " + end) + if (start < 0 || start > len) + throw new ArrayIndexOutOfBoundsException + } + + def hashCode(a: Array[Boolean]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Boolean(x).hashCode) + } + + def hashCode(a: Array[Char]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Character(x).hashCode) + } + + def hashCode(a: Array[Byte]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Byte(x).hashCode) + } + + def hashCode(a: Array[Short]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Short(x).hashCode) + } + + def hashCode(a: Array[Int]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Integer(x).hashCode) + } + + def hashCode(a: Array[Long]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Long(x).hashCode) + } + + def hashCode(a: Array[Float]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Float(x).hashCode) + } + + def hashCode(a: Array[Double]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + new java.lang.Double(x).hashCode) + } + + def hashCode(a: Array[AnyRef]): Int = { + if (a == null) 0 + else a.foldLeft(1)((acc, x) => 31*acc + (if (x == null) 0 else x.hashCode)) + } + + def equals(a: Array[Boolean], b: Array[Boolean]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Char], b: Array[Char]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Byte], b: Array[Byte]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Short], b: Array[Short]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Int], b: Array[Int]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Long], b: Array[Long]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Float], b: Array[Float]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[Double], b: Array[Double]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + + def equals(a: Array[AnyRef], b: Array[AnyRef]): Boolean = + (a eq b) || (a != null && b != null && a.length == b.length && + (0 until a.size).forall(i => a(i) == b(i))) + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Comparator.scala b/examples/scala-js/javalib/src/main/scala/java/util/Comparator.scala new file mode 100644 index 0000000..d63c48e --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Comparator.scala @@ -0,0 +1,6 @@ +package java.util + +trait Comparator[A] { + def compare(o1: A, o2: A): Int + def equals(obj: Any): Boolean +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Date.scala b/examples/scala-js/javalib/src/main/scala/java/util/Date.scala new file mode 100644 index 0000000..8f4e85f --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Date.scala @@ -0,0 +1,147 @@ +/** + * 2014 Matt Seddon + * This code is donated in full to the scala-js project. + */ + +package java.util + +import scalajs.js + +class Date private (private val date: js.Date) extends Object + with Serializable with Cloneable with Comparable[Date] { + + import Date._ + + def this() = this(new js.Date()) + + @Deprecated + def this(year: Int, month: Int, date: Int, hrs: Int, min: Int, sec: Int) = { + this(new js.Date()) + this.date.setFullYear(1900 + year, month, date) + this.date.setHours(hrs, min, sec, 0) + } + + @Deprecated + def this(year: Int, month: Int, date: Int, hrs: Int, min: Int) = + this(year, month, date, hrs, min, 0) + + @Deprecated + def this(year: Int, month: Int, date: Int) = + this(year, month, date, 0, 0, 0) + + def this(date: Long) = this(new js.Date(date)) + + @Deprecated + def this(date: String) = this(new js.Date(date)) + + def after(when: Date): Boolean = date.getTime() > when.date.getTime() + + def before(when: Date): Boolean = date.getTime() < when.date.getTime() + + override def clone(): Object = new Date(new js.Date(date.getTime())) + + override def compareTo(anotherDate: Date): Int = + date.getTime().compareTo(anotherDate.date.getTime()) + + override def equals(obj: Any): Boolean = obj match { + case d: Date => d.date.getTime() == date.getTime() + case _ => false + } + + override def hashCode(): Int = date.getTime().hashCode() + + @Deprecated + def getDate(): Int = date.getDate() + + @Deprecated + def getDay(): Int = date.getDay() + + @Deprecated + def getHours(): Int = date.getHours() + + @Deprecated + def getMinutes(): Int = date.getMinutes() + + @Deprecated + def getMonth(): Int = date.getMonth() + + @Deprecated + def getSeconds(): Int = date.getSeconds() + + def getTime(): Long = date.getTime().toLong + + @Deprecated + def getTimeZoneOffset(): Int = date.getTimezoneOffset() + + @Deprecated + def getYear(): Int = date.getFullYear() - 1900 + + @Deprecated + def setDate(date: Int): Unit = this.date.setDate(date) + + @Deprecated + def setHours(hours: Int): Unit = date.setHours(hours) + + @Deprecated + def setMinutes(minutes: Int): Unit = date.setMinutes(minutes) + + @Deprecated + def setMonth(month: Int): Unit = date.setMonth(month) + + @Deprecated + def setSeconds(seconds: Int): Unit = date.setSeconds(seconds) + + def setTime(time: Long): Unit = date.setTime(time) + + @Deprecated + def setYear(year: Int): Unit = date.setFullYear(1900 + year) + + @Deprecated + def toGMTString(): String = { + date.getUTCDate() + " " + Months(date.getUTCMonth()) + " " + + date.getUTCFullYear() + " " + pad0(date.getUTCHours()) + ":" + + pad0(date.getUTCMinutes()) + ":" + + pad0(date.getUTCSeconds()) +" GMT" + } + + @Deprecated + def toLocaleString(): String = { + date.getDate() + "-" + Months(date.getMonth()) + "-" + + date.getFullYear() + "-" + pad0(date.getHours()) + ":" + + pad0(date.getMinutes()) + ":" + pad0(date.getSeconds()) + } + + override def toString(): String = { + val offset = -date.getTimezoneOffset() + val sign = if(offset < 0) "-" else "+" + val hours = pad0(Math.abs(offset) / 60) + val mins = pad0(Math.abs(offset) % 60) + Days(date.getDay()) + " "+ Months(date.getMonth()) + " " + + pad0(date.getHours()) + ":" + pad0(date.getMinutes()) + ":" + + pad0(date.getSeconds()) + " GMT" + sign + hours + mins + " " + + date.getFullYear() + } +} + +object Date { + private val Days = Array( + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") + + private val Months = Array( + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") + + private def pad0(i: Int): String = { + val str = "" + i + if (str.length < 2) "0" + str else str + } + + @Deprecated + def UTC(year: Int, month: Int, date: Int, + hrs: Int, min: Int, sec: Int): Long = + js.Date.UTC(year + 1900, month, date, hrs, min, sec).toLong + + @Deprecated + def parse(string: String): Long = + new Date(new js.Date(string)).getTime.toLong +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Formattable.scala b/examples/scala-js/javalib/src/main/scala/java/util/Formattable.scala new file mode 100644 index 0000000..e651fbb --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Formattable.scala @@ -0,0 +1,5 @@ +package java.util + +trait Formattable { + def formatTo(formatter: Formatter, flags: Int, width: Int, precision: Int): Unit +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/FormattableFlags.scala b/examples/scala-js/javalib/src/main/scala/java/util/FormattableFlags.scala new file mode 100644 index 0000000..02f5bce --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/FormattableFlags.scala @@ -0,0 +1,7 @@ +package java.util + +object FormattableFlags { + final val ALTERNATE = 4 + final val LEFT_JUSTIFY = 1 + final val UPPERCASE = 2 +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala b/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala new file mode 100644 index 0000000..5e0ab22 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala @@ -0,0 +1,273 @@ +package java.util + +import scala.annotation.switch +import scala.scalajs.js + +import java.io._ +import java.lang._ + +final class Formatter(private val dest: Appendable) extends Closeable with Flushable { + import Formatter._ + + var closed = false + + def this() = this(new StringBuilder()) + + def close(): Unit = { + if (!closed) { + dest match { + case cl: Closeable => cl.close() + case _ => + } + } + closed = true + } + + def flush(): Unit = ifNotClosed { + dest match { + case fl: Flushable => fl.flush() + case _ => + } + } + + // Begin implem of format() + + def format(format_in: String, args: Array[AnyRef]): Formatter = ifNotClosed { + import js.Any.fromDouble // to have .toFixed and .toExponential on Doubles + + var fmt: String = format_in + var lastImplicitIndex: Int = 0 + var lastIndex: Int = 0 // required for < flag + + while (!fmt.isEmpty) { + fmt match { + case RegularChunk(matchResult) => + fmt = fmt.substring(matchResult(0).get.length) + dest.append(matchResult(0).get) + + case DoublePercent(_) => + fmt = fmt.substring(2) + dest.append('%') + + case EOLChunk(_) => + fmt = fmt.substring(2) + dest.append('\n') + + case FormattedChunk(matchResult) => + fmt = fmt.substring(matchResult(0).get.length) + + val flags = matchResult(2).get + def hasFlag(flag: String) = flags.indexOf(flag) >= 0 + + val indexStr = matchResult(1).getOrElse("") + val index = if (!indexStr.isEmpty) { + Integer.parseInt(indexStr) + } else if (hasFlag("<")) { + lastIndex + } else { + lastImplicitIndex += 1 + lastImplicitIndex + } + lastIndex = index + if (index <= 0 || index > args.length) + throw new MissingFormatArgumentException(matchResult(5).get) + val arg = args(index-1) + + val widthStr = matchResult(3).getOrElse("") + val hasWidth = !widthStr.isEmpty + val width = + if (hasWidth) Integer.parseInt(widthStr) + else 0 + + val precisionStr = matchResult(4).getOrElse("") + val hasPrecision = !precisionStr.isEmpty + val precision = + if (hasPrecision) Integer.parseInt(precisionStr) + else 0 + + val conversion = matchResult(5).get.charAt(0) + + def intArg: Int = (arg: Any) match { + case arg: Int => arg + case arg: Char => arg.toInt + } + def numberArg: scala.Double = (arg: Any) match { + case arg: Number => arg.doubleValue() + case arg: Char => arg.toDouble + } + + def padCaptureSign(argStr: String, prefix: String) = { + val firstChar = argStr.charAt(0) + if (firstChar == '+' || firstChar == '-') + pad(argStr.substring(1), firstChar+prefix) + else + pad(argStr, prefix) + } + + def strRepeat(s: String, times: Int) = { + var result: String = "" + var i = times + while (i > 0) { + result += s + i -= 1 + } + result + } + + def with_+(s: String, preventZero: scala.Boolean = false) = { + if (s.charAt(0) != '-') { + if (hasFlag("+")) + pad(s, "+", preventZero) + else if (hasFlag(" ")) + pad(s, " ", preventZero) + else + pad(s, "", preventZero) + } else { + if (hasFlag("(")) + pad(s.substring(1) + ")", "(", preventZero) + else + pad(s.substring(1), "-", preventZero) + } + } + + def pad(argStr: String, prefix: String = "", + preventZero: Boolean = false) = { + val prePadLen = argStr.length + prefix.length + + val padStr = { + if (width <= prePadLen) { + prefix + argStr + } else { + val padRight = hasFlag("-") + val padZero = hasFlag("0") && !preventZero + val padLength = width - prePadLen + val padChar: String = if (padZero) "0" else " " + val padding = strRepeat(padChar, padLength) + + if (padZero && padRight) + throw new java.util.IllegalFormatFlagsException(flags) + else if (padRight) prefix + argStr + padding + else if (padZero) prefix + padding + argStr + else padding + prefix + argStr + } + } + + val casedStr = + if (conversion.isUpper) padStr.toUpperCase() + else padStr + dest.append(casedStr) + } + + (conversion: @switch) match { + case 'b' | 'B' => pad { arg match { + case null => "false" + case b: Boolean => String.valueOf(b) + case _ => "true" + } } + case 'h' | 'H' => pad { + if (arg eq null) "null" + else Integer.toHexString(arg.hashCode) + } + case 's' | 'S' => arg match { + case null if !hasFlag("#") => pad("null") + case formattable: Formattable => + val flags = ( + (if (hasFlag("-")) FormattableFlags.LEFT_JUSTIFY else 0) | + (if (hasFlag("#")) FormattableFlags.ALTERNATE else 0) | + (if (conversion.isUpper) FormattableFlags.UPPERCASE else 0) + ) + + formattable.formatTo(this, flags, + if (hasWidth) width.toInt else -1, + if (hasPrecision) precision.toInt else -1) + None // no further processing + case t: AnyRef if !hasFlag("#") => pad(t.toString) + case _ => + throw new FormatFlagsConversionMismatchException("#", 's') + } + case 'c' | 'C' => + pad(js.String.fromCharCode(intArg)) + case 'd' => + with_+(numberArg.toString()) + case 'o' => + val str = (arg: Any) match { + case arg: scala.Int => Integer.toOctalString(arg) + case arg: scala.Long => Long.toOctalString(arg) + } + padCaptureSign(str, if (hasFlag("#")) "0" else "") + case 'x' | 'X' => + val str = (arg: Any) match { + case arg: scala.Int => Integer.toHexString(arg) + case arg: scala.Long => Long.toHexString(arg) + } + padCaptureSign(str, if (hasFlag("#")) "0x" else "") + case 'e' | 'E' => + sciNotation(if (hasPrecision) precision else 6) + case 'g' | 'G' => + val m = Math.abs(numberArg) + // precision handling according to JavaDoc + // precision here means number of significant digits + // not digits after decimal point + val p = + if (!hasPrecision) 6 + else if (precision == 0) 1 + else precision + // between 1e-4 and 10e(p): display as fixed + if (m >= 1e-4 && m < Math.pow(10, p)) { + val sig = Math.ceil(Math.log10(m)) + with_+(numberArg.toFixed(Math.max(p - sig, 0))) + } else sciNotation(p - 1) + case 'f' => + with_+({ + // JavaDoc: 6 is default precision + numberArg.toFixed(if (hasPrecision) precision else 6) + }, numberArg.isNaN || numberArg.isInfinite) + } + + def sciNotation(precision: Int) = { + val exp = numberArg.toExponential(precision) + with_+({ + // check if we need additional 0 padding in exponent + // JavaDoc: at least 2 digits + if ("e" == exp.charAt(exp.length - 3)) { + exp.substring(0, exp.length - 1) + "0" + + exp.charAt(exp.length - 1) + } else exp + }, numberArg.isNaN || numberArg.isInfinite) + } + } + } + + this + } + + def ioException(): IOException = null + def locale(): Locale = ifNotClosed { null } + def out(): Appendable = ifNotClosed { dest } + + override def toString(): String = out().toString() + + @inline private def ifNotClosed[T](body: => T): T = + if (closed) throwClosedException() + else body + + private def throwClosedException(): Nothing = + throw new FormatterClosedException() + +} + +object Formatter { + + private class RegExpExtractor(val regexp: js.RegExp) { + def unapply(str: String): Option[js.RegExp.ExecResult] = { + Option(regexp.exec(str)) + } + } + + private val RegularChunk = new RegExpExtractor(new js.RegExp("""^[^\x25]+""")) + private val DoublePercent = new RegExpExtractor(new js.RegExp("""^\x25{2}""")) + private val EOLChunk = new RegExpExtractor(new js.RegExp("""^\x25n""")) + private val FormattedChunk = new RegExpExtractor(new js.RegExp( + """^\x25(?:([1-9]\d*)\$)?([-#+ 0,\(<]*)(\d*)(?:\.(\d+))?([A-Za-z])""")) + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Random.scala b/examples/scala-js/javalib/src/main/scala/java/util/Random.scala new file mode 100644 index 0000000..80428fb --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Random.scala @@ -0,0 +1,119 @@ +package java.util + +import scala.annotation.tailrec + +import scala.scalajs.js + +class Random(seed_in: Long) extends AnyRef with java.io.Serializable { + + private var seed: Long = _ + + // see nextGaussian() + private var nextNextGaussian: Double = _ + private var haveNextNextGaussian: Boolean = false + + setSeed(seed_in) + + def this() = this(Random.randomSeed()) + + def setSeed(seed_in: Long): Unit = { + seed = (seed_in ^ 0x5DEECE66DL) & ((1L << 48) - 1) + haveNextNextGaussian = false + } + + protected def next(bits: Int): Int = { + seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1) + (seed >>> (48 - bits)).toInt + } + + def nextDouble(): Double = + ((next(26).toLong << 27) + next(27)) / (1L << 53).toDouble + + def nextBoolean(): Boolean = next(1) != 0 + + def nextInt(): Int = next(32) + + def nextInt(n: Int): Int = { + if (n <= 0) + throw new IllegalArgumentException("n must be positive"); + + if ((n & -n) == n) // i.e., n is a power of 2 + ((n * next(31).toLong) >> 31).toInt + else { + @tailrec + def loop(): Int = { + val bits = next(31) + val value = bits % n + if (bits - value + (n-1) < 0) loop() + else value + } + + loop() + } + } + + def nextLong(): Long = (next(32).toLong << 32) + next(32) + + def nextFloat(): Float = next(24) / (1 << 24).toFloat + + def nextBytes(bytes: Array[Byte]): Unit = { + var i = 0 + while (i < bytes.length) { + var rnd = nextInt() + var n = Math.min(bytes.length - i, 4) + while (n > 0) { + bytes(i) = rnd.toByte + rnd >>= 8 + n -= 1 + i += 1 + } + } + } + + def nextGaussian(): Double = { + // See http://www.protonfish.com/jslib/boxmuller.shtml + + /* The Box-Muller algorithm produces two random numbers at once. We save + * the second one in `nextNextGaussian` to be used by the next call to + * nextGaussian(). + */ + + if (haveNextNextGaussian) { + haveNextNextGaussian = false + return nextNextGaussian + } + + var x, y, rds: Double = 0 + + /* Get two random numbers from -1 to 1. + * If the radius is zero or greater than 1, throw them out and pick two new + * ones. + * Rejection sampling throws away about 20% of the pairs. + */ + do { + x = nextDouble()*2-1 + y = nextDouble()*2-1 + rds = x*x + y*y + } while (rds == 0 || rds > 1) + + val c = Math.sqrt(-2 * Math.log(rds) / rds) + + // Save y*c for next time + nextNextGaussian = y*c + haveNextNextGaussian = true + + // And return x*c + x*c + } +} + +object Random { + + /** Generate a random long from JS RNG to seed a new Random */ + private def randomSeed(): Long = + (randomInt().toLong << 32) | (randomInt().toLong & 0xffffffffL) + + private def randomInt(): Int = + (Math.floor(js.Math.random() * 4294967296.0) - 2147483648.0).toInt + +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Throwables.scala b/examples/scala-js/javalib/src/main/scala/java/util/Throwables.scala new file mode 100644 index 0000000..c4bb3d6 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Throwables.scala @@ -0,0 +1,166 @@ +package java.util + +class ServiceConfigurationError(s: String, e: Throwable) extends Error(s, e) { + def this(s: String) = this(s, null) +} + +class ConcurrentModificationException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class DuplicateFormatFlagsException private() extends IllegalFormatException { + private var flags: String = null + def this(f: String) { + this() + if (f == null) + throw new NullPointerException() + flags = f + } + def getFlags(): String = flags + override def getMessage(): String = s"Flags = '$flags'" +} + +class EmptyStackException extends RuntimeException + +class FormatFlagsConversionMismatchException private(private val c: Char) extends IllegalFormatException { + private var f: String = null + def this(f: String, c: Char) { + this(c) + if (f == null) + throw new NullPointerException() + this.f = f + } + def getFlags(): String = f + def getConversion(): Char = c + override def getMessage(): String = "Conversion = " + c + ", Flags = " + f +} + +class FormatterClosedException extends IllegalStateException + +class IllegalFormatCodePointException(private val c: Int) extends IllegalFormatException { + def getCodePoint(): Int = c + override def getMessage(): String = s"Code point = $c" +} + +class IllegalFormatConversionException private(private val c: Char) extends IllegalFormatException { + private var arg: Class[_] = null + def this(c: Char, arg: Class[_]) { + this(c) + if (arg == null) + throw new NullPointerException() + this.arg = arg + } + def getConversion(): Char = c + def getArgumentClass(): Class[_] = arg + override def getMessage(): String = s"$c != ${arg.getName()}" +} + +class IllegalFormatException private[util] () extends IllegalArgumentException + +class IllegalFormatFlagsException private() extends IllegalFormatException { + private var flags: String = null + def this(f: String) { + this() + if (f == null) + throw new NullPointerException() + this.flags = f + } + def getFlags(): String = flags + override def getMessage(): String = "Flags = '" + flags + "'" +} + +class IllegalFormatPrecisionException(private val p: Int) extends IllegalFormatException { + def getPrecision(): Int = p + override def getMessage(): String = Integer.toString(p) +} + +class IllegalFormatWidthException(private val w: Int) extends IllegalFormatException { + def getWidth(): Int = w + override def getMessage(): String = Integer.toString(w) +} + +class IllformedLocaleException(s: String, errorIndex: Int) + extends RuntimeException(s + (if(errorIndex < 0) "" else " [at index " + errorIndex + "]")) { + def this() = this(null, -1) + def this(s: String) = this(s, -1) + def getErrorIndex(): Int = errorIndex +} + +class InputMismatchException(s: String) extends NoSuchElementException(s) { + def this() = this(null) +} + +class InvalidPropertiesFormatException(s: String) extends java.io.IOException(s) { + def this(e: Throwable) { + this(if(e == null) null.asInstanceOf[String] else e.toString()) + this.initCause(e) + } + // private def writeObject(out: java.io.ObjectOutputStream) = + // throw new java.io.NotSerializableException("Not serializable.") + // private def readObject(in: java.io.ObjectInputStream) = + // throw new java.io.NotSerializableException("Not serializable.") +} + +class MissingFormatArgumentException private() extends IllegalFormatException { + private var s: String = null + def this(s: String) { + this() + if (s == null) + throw new NullPointerException() + this.s = s + } + def getFormatSpecifier(): String = s + override def getMessage(): String = "Format specifier '" + s + "'" +} + +class MissingFormatWidthException private() extends IllegalFormatException { + private var s: String = null + def this(s: String) { + this() + if (s == null) + throw new NullPointerException() + this.s = s + } + def getFormatSpecifier(): String = s + override def getMessage(): String = s +} + +class MissingResourceException private[util]( + s: String, private var className: String, private var key: String, e: Throwable) + extends RuntimeException(s, e) { + def this(s: String, className: String, key: String) = this(s, className, key, null) + def getClassName(): String = className + def getKey(): String = key +} + +class NoSuchElementException(s: String) extends RuntimeException(s) { + def this() = this(null) +} + +class TooManyListenersException(s: String) extends Exception(s) { + def this() = this(null) +} + +class UnknownFormatConversionException private () extends IllegalFormatException { + private var s: String = null + def this(s: String) { + this() + if (s == null) + throw new NullPointerException() + this.s = s + } + def getConversion(): String = s + override def getMessage(): String = s"Conversion = '$s'" +} + +class UnknownFormatFlagsException private() extends IllegalFormatException { + private var flags: String = null + def this(f: String) { + this() + if (f == null) + throw new NullPointerException() + this.flags = f + } + def getFlags(): String = flags + override def getMessage(): String = "Flags = " + flags +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/UUID.scala b/examples/scala-js/javalib/src/main/scala/java/util/UUID.scala new file mode 100644 index 0000000..9e6c1d4 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/UUID.scala @@ -0,0 +1,163 @@ +package java.util + +import java.lang.{Long => JLong} + +import scala.scalajs.js + +final class UUID private ( + private val i1: Int, private val i2: Int, + private val i3: Int, private val i4: Int, + private[this] var l1: JLong, private[this] var l2: JLong) + extends AnyRef with java.io.Serializable with Comparable[UUID] { + + import UUID._ + + /* Most significant long: + * + * 0xFFFFFFFF00000000 time_low + * 0x00000000FFFF0000 time_mid + * 0x000000000000F000 version + * 0x0000000000000FFF time_hi + * + * Least significant long: + * + * 0xC000000000000000 variant + * 0x3FFF000000000000 clock_seq + * 0x0000FFFFFFFFFFFF node + */ + + def this(mostSigBits: Long, leastSigBits: Long) = { + this((mostSigBits >>> 32).toInt, mostSigBits.toInt, + (leastSigBits >>> 32).toInt, leastSigBits.toInt, + mostSigBits, leastSigBits) + } + + def getLeastSignificantBits(): Long = { + if (l2 eq null) + l2 = (i3.toLong << 32) | (i4.toLong & 0xffffffffL) + l2.longValue + } + + def getMostSignificantBits(): Long = { + if (l1 eq null) + l1 = (i1.toLong << 32) | (i2.toLong & 0xffffffffL) + l1.longValue + } + + def version(): Int = + (i2 & 0xf000) >> 12 + + def variant(): Int = { + if ((i3 & 0x80000000) == 0) { + // MSB0 not set: NCS backwards compatibility variant + 0 + } else if ((i3 & 0x40000000) != 0) { + // MSB1 set: either MS reserved or future reserved + (i3 & 0xe0000000) >>> 29 + } else { + // MSB1 not set: RFC 4122 variant + 2 + } + } + + def timestamp(): Long = { + if (version() != TimeBased) + throw new UnsupportedOperationException("Not a time-based UUID") + (((i2 >>> 16) | ((i2 & 0x0fff) << 16)).toLong << 32) | (i1.toLong & 0xffffffffL) + } + + def clockSequence(): Int = { + if (version() != TimeBased) + throw new UnsupportedOperationException("Not a time-based UUID") + (i3 & 0x3fff0000) >> 16 + } + + def node(): Long = { + if (version() != TimeBased) + throw new UnsupportedOperationException("Not a time-based UUID") + ((i3 & 0xffff).toLong << 32) | (i4.toLong & 0xffffffffL) + } + + override def toString(): String = { + @inline def paddedHex8(i: Int): String = { + val s = Integer.toHexString(i) + "00000000".substring(s.length) + s + } + + @inline def paddedHex4(i: Int): String = { + val s = Integer.toHexString(i) + "0000".substring(s.length) + s + } + + paddedHex8(i1) + "-" + paddedHex4(i2 >>> 16) + "-" + paddedHex4(i2 & 0xffff) + "-" + + paddedHex4(i3 >>> 16) + "-" + paddedHex4(i3 & 0xffff) + paddedHex8(i4) + } + + override def hashCode(): Int = + i1 ^ i2 ^ i3 ^ i4 + + override def equals(that: Any): Boolean = that match { + case that: UUID => + i1 == that.i1 && i2 == that.i2 && i3 == that.i3 && i4 == that.i4 + case _ => + false + } + + def compareTo(that: UUID): Int = { + if (this.i1 != that.i1) { + if (this.i1 > that.i1) 1 else -1 + } else if (this.i2 != that.i2) { + if (this.i2 > that.i2) 1 else -1 + } else if (this.i3 != that.i3) { + if (this.i3 > that.i3) 1 else -1 + } else if (this.i4 != that.i4) { + if (this.i4 > that.i4) 1 else -1 + } else { + 0 + } + } +} + +object UUID { + private final val TimeBased = 1 + private final val DCESecurity = 2 + private final val NameBased = 3 + private final val Random = 4 + + private lazy val rng = new Random() // TODO Use java.security.SecureRandom + + def randomUUID(): UUID = { + val i1 = rng.nextInt() + val i2 = (rng.nextInt() & ~0x0000f000) | 0x00004000 + val i3 = (rng.nextInt() & ~0xc0000000) | 0x80000000 + val i4 = rng.nextInt() + new UUID(i1, i2, i3, i4, null, null) + } + + // Not implemented (requires messing with MD5 or SHA-1): + //def nameUUIDFromBytes(name: Array[Byte]): UUID = ??? + + def fromString(name: String): UUID = { + import Integer.parseInt + + def fail(): Nothing = + throw new IllegalArgumentException("Illegal UUID string: "+name) + + @inline def parseHex8(his: String, los: String): Int = + (parseInt(his, 16) << 16) | parseInt(los, 16) + + if (name.length != 36 || name.charAt(8) != '-' || + name.charAt(13) != '-' || name.charAt(18) != '-' || name.charAt(23) != '-') + fail() + + try { + val i1 = parseHex8(name.substring(0, 4), name.substring(4, 8)) + val i2 = parseHex8(name.substring(9, 13), name.substring(14, 18)) + val i3 = parseHex8(name.substring(19, 23), name.substring(24, 28)) + val i4 = parseHex8(name.substring(28, 32), name.substring(32, 36)) + new UUID(i1, i2, i3, i4, null, null) + } catch { + case _: NumberFormatException => fail() + } + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala new file mode 100644 index 0000000..6d04889 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala @@ -0,0 +1,9 @@ +package java.util.concurrent + +class ExecutionException(message: String, cause: Throwable) + extends Exception(message, cause) { + + protected def this() = this(null, null) + protected def this(message: String) = this(message, null) + def this(cause: Throwable) = this(null, cause) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/Executor.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/Executor.scala new file mode 100644 index 0000000..d030551 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/Executor.scala @@ -0,0 +1,5 @@ +package java.util.concurrent + +trait Executor { + def execute(command: Runnable): Unit +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala new file mode 100644 index 0000000..a77dbfc --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala @@ -0,0 +1,133 @@ +package java.util.concurrent + +abstract class TimeUnit private (_index: Int, + _name: String) extends java.io.Serializable { + + def convert(a: Long, u: TimeUnit): Long + + def toNanos(a: Long): Long + def toMicros(a: Long): Long + def toMillis(a: Long): Long + def toSeconds(a: Long): Long + def toMinutes(a: Long): Long + def toHours(a: Long): Long + def toDays(a: Long): Long + + // not used + //private[concurrent] def excessNanos(a: Long, b: Long): Int + + def name(): String = _name + def ordinal(): Int = _index + + // methods that cannot be implemented + //def timedWait(arg1: AnyRef, arg2: Long): Unit + //def timedJoin(arg1: Thread, arg2: Long): Unit + //def sleep(arg1: Long): Unit + + override def toString() = name() +} + +object TimeUnit { + final val NANOSECONDS: TimeUnit = new TimeUnit(0, "NANOSECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toNanos(a) + def toNanos(a: Long): Long = a + def toMicros(a: Long): Long = a / (C1/C0) + def toMillis(a: Long): Long = a / (C2/C0) + def toSeconds(a: Long): Long = a / (C3/C0) + def toMinutes(a: Long): Long = a / (C4/C0) + def toHours(a: Long): Long = a / (C5/C0) + def toDays(a: Long): Long = a / (C6/C0) + } + + final val MICROSECONDS: TimeUnit = new TimeUnit(1, "MICROSECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toMicros(a) + def toNanos(a: Long): Long = x(a, C1/C0, MAX/(C1/C0)) + def toMicros(a: Long): Long = a + def toMillis(a: Long): Long = a / (C2/C1) + def toSeconds(a: Long): Long = a / (C3/C1) + def toMinutes(a: Long): Long = a / (C4/C1) + def toHours(a: Long): Long = a / (C5/C1) + def toDays(a: Long): Long = a / (C6/C1) + } + + final val MILLISECONDS: TimeUnit = new TimeUnit(2, "MILLISECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toMillis(a) + def toNanos(a: Long): Long = x(a, C2/C0, MAX/(C2/C0)) + def toMicros(a: Long): Long = x(a, C2/C1, MAX/(C2/C1)) + def toMillis(a: Long): Long = a + def toSeconds(a: Long): Long = a / (C3/C2) + def toMinutes(a: Long): Long = a / (C4/C2) + def toHours(a: Long): Long = a / (C5/C2) + def toDays(a: Long): Long = a / (C6/C2) + } + + final val SECONDS: TimeUnit = new TimeUnit(3, "SECONDS") { + def convert(a: Long, u: TimeUnit): Long = u.toSeconds(a) + def toNanos(a: Long): Long = x(a, C3/C0, MAX/(C3/C0)) + def toMicros(a: Long): Long = x(a, C3/C1, MAX/(C3/C1)) + def toMillis(a: Long): Long = x(a, C3/C2, MAX/(C3/C2)) + def toSeconds(a: Long): Long = a + def toMinutes(a: Long): Long = a / (C4/C3) + def toHours(a: Long): Long = a / (C5/C3) + def toDays(a: Long): Long = a / (C6/C3) + } + + final val MINUTES: TimeUnit = new TimeUnit(4, "MINUTES") { + def convert(a: Long, u: TimeUnit): Long = u.toMinutes(a) + def toNanos(a: Long): Long = x(a, C4/C0, MAX/(C4/C0)) + def toMicros(a: Long): Long = x(a, C4/C1, MAX/(C4/C1)) + def toMillis(a: Long): Long = x(a, C4/C2, MAX/(C4/C2)) + def toSeconds(a: Long): Long = x(a, C4/C3, MAX/(C4/C3)) + def toMinutes(a: Long): Long = a + def toHours(a: Long): Long = a / (C5/C4) + def toDays(a: Long): Long = a / (C6/C4) + } + + final val HOURS: TimeUnit = new TimeUnit(5, "HOURS") { + def convert(a: Long, u: TimeUnit): Long = u.toHours(a) + def toNanos(a: Long): Long = x(a, C5/C0, MAX/(C5/C0)) + def toMicros(a: Long): Long = x(a, C5/C1, MAX/(C5/C1)) + def toMillis(a: Long): Long = x(a, C5/C2, MAX/(C5/C2)) + def toSeconds(a: Long): Long = x(a, C5/C3, MAX/(C5/C3)) + def toMinutes(a: Long): Long = x(a, C5/C4, MAX/(C5/C4)) + def toHours(a: Long): Long = a + def toDays(a: Long): Long = a / (C6/C5) + } + + final val DAYS: TimeUnit = new TimeUnit(6, "DAYS") { + def convert(a: Long, u: TimeUnit): Long = u.toDays(a) + def toNanos(a: Long): Long = x(a, C6/C0, MAX/(C6/C0)) + def toMicros(a: Long): Long = x(a, C6/C1, MAX/(C6/C1)) + def toMillis(a: Long): Long = x(a, C6/C2, MAX/(C6/C2)) + def toSeconds(a: Long): Long = x(a, C6/C3, MAX/(C6/C3)) + def toMinutes(a: Long): Long = x(a, C6/C4, MAX/(C6/C4)) + def toHours(a: Long): Long = x(a, C6/C5, MAX/(C6/C5)) + def toDays(a: Long): Long = a + } + + private[this] val _values: Array[TimeUnit] = + Array(NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS) + + // deliberately without type ascription to make them compile-time constants + private final val C0 = 1L + private final val C1 = C0 * 1000L + private final val C2 = C1 * 1000L + private final val C3 = C2 * 1000L + private final val C4 = C3 * 60L + private final val C5 = C4 * 60L + private final val C6 = C5 * 24L + private final val MAX = Long.MaxValue + + def values(): Array[TimeUnit] = _values.clone() + + def valueOf(name: String): TimeUnit = { + _values.find(_.name == name).getOrElse( + throw new IllegalArgumentException("No enum const TimeUnit." + name)) + } + + private def x(a: Long, b: Long, max: Long): Long = { + if (a > max) MAX + else if (a < -max) -MAX + else a * b + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala new file mode 100644 index 0000000..5675c31 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala @@ -0,0 +1,33 @@ +package java.util.concurrent.atomic + +class AtomicBoolean(private[this] var value: Boolean) extends Serializable { + def this() = this(false) + + final def get(): Boolean = value + + final def compareAndSet(expect: Boolean, update: Boolean): Boolean = { + if (expect != value) false else { + value = update + true + } + } + + // For some reason, this method is not final + def weakCompareAndSet(expect: Boolean, update: Boolean): Boolean = + compareAndSet(expect, update) + + final def set(newValue: Boolean): Unit = + value = newValue + + final def lazySet(newValue: Boolean): Unit = + set(newValue) + + final def getAndSet(newValue: Boolean): Boolean = { + val old = value + value = newValue + old + } + + override def toString(): String = + value.toString() +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala new file mode 100644 index 0000000..1f24b7b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala @@ -0,0 +1,63 @@ +package java.util.concurrent.atomic + +class AtomicInteger(private[this] var value: Int) + extends Number with Serializable { + + def this() = this(0) + + final def get(): Int = value + + final def set(newValue: Int): Unit = + value = newValue + + final def lazySet(newValue: Int): Unit = + set(newValue) + + final def getAndSet(newValue: Int): Int = { + val old = value + value = newValue + old + } + + final def compareAndSet(expect: Int, update: Int): Boolean = { + if (expect != value) false else { + value = update + true + } + } + + final def weakCompareAndSet(expect: Int, update: Int): Boolean = + compareAndSet(expect, update) + + final def getAndIncrement(): Int = + getAndAdd(1) + + final def getAndDecrement(): Int = + getAndAdd(-1) + + @inline final def getAndAdd(delta: Int): Int = { + val old = value + value = old + delta + old + } + + final def incrementAndGet(): Int = + addAndGet(1) + + final def decrementAndGet(): Int = + addAndGet(-1) + + @inline final def addAndGet(delta: Int): Int = { + val newValue = value + delta + value = newValue + newValue + } + + override def toString(): String = + value.toString() + + def intValue(): Int = value + def longValue(): Long = value.toLong + def floatValue(): Float = value.toFloat + def doubleValue(): Double = value.toDouble +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala new file mode 100644 index 0000000..5bfecf2 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala @@ -0,0 +1,61 @@ +package java.util.concurrent.atomic + +class AtomicLong(private[this] var value: Long) extends Number with Serializable { + def this() = this(0L) + + final def get(): Long = value + + final def set(newValue: Long): Unit = + value = newValue + + final def lazySet(newValue: Long): Unit = + set(newValue) + + final def getAndSet(newValue: Long): Long = { + val old = value + value = newValue + old + } + + final def compareAndSet(expect: Long, update: Long): Boolean = { + if (expect != value) false else { + value = update + true + } + } + + final def weakCompareAndSet(expect: Long, update: Long): Boolean = + compareAndSet(expect, update) + + final def getAndIncrement(): Long = + getAndAdd(1L) + + final def getAndDecrement(): Long = + getAndAdd(-1L) + + @inline final def getAndAdd(delta: Long): Long = { + val old = value + value = old + delta + old + } + + final def incrementAndGet(): Long = + addAndGet(1L) + + final def decrementAndGet(): Long = + addAndGet(-1L) + + @inline final def addAndGet(delta: Long): Long = { + val newValue = value + delta + value = newValue + newValue + } + + override def toString(): String = + value.toString() + + def intValue(): Int = value.toInt + def longValue(): Long = value + def floatValue(): Float = value.toFloat + def doubleValue(): Double = value.toDouble +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala new file mode 100644 index 0000000..650b1e0 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala @@ -0,0 +1,34 @@ +package java.util.concurrent.atomic + +class AtomicReference[T <: AnyRef]( + private[this] var value: T) extends Serializable { + + def this() = this(null.asInstanceOf[T]) + + final def get(): T = value + + final def set(newValue: T): Unit = + value = newValue + + final def lazySet(newValue: T): Unit = + set(newValue) + + final def compareAndSet(expect: T, update: T): Boolean = { + if (expect ne value) false else { + value = update + true + } + } + + final def weakCompareAndSet(expect: T, update: T): Boolean = + compareAndSet(expect, update) + + final def getAndSet(newValue: T): T = { + val old = value + value = newValue + old + } + + override def toString(): String = + String.valueOf(value) +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/regex/MatchResult.scala b/examples/scala-js/javalib/src/main/scala/java/util/regex/MatchResult.scala new file mode 100644 index 0000000..f321c60 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/regex/MatchResult.scala @@ -0,0 +1,13 @@ +package java.util.regex + +trait MatchResult { + def groupCount(): Int + + def start(): Int + def end(): Int + def group(): String + + def start(group: Int): Int + def end(group: Int): Int + def group(group: Int): String +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/regex/Matcher.scala b/examples/scala-js/javalib/src/main/scala/java/util/regex/Matcher.scala new file mode 100644 index 0000000..331f56b --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/regex/Matcher.scala @@ -0,0 +1,274 @@ +package java.util.regex + +import scala.language.implicitConversions + +import scala.annotation.switch + +import scala.scalajs.js + +final class Matcher private[regex] ( + private var pattern0: Pattern, private var input0: CharSequence, + private var regionStart0: Int, private var regionEnd0: Int) + extends AnyRef with MatchResult { + + import Matcher._ + + def pattern(): Pattern = pattern0 + + // Configuration (updated manually) + private var regexp = new js.RegExp(pattern0.jspattern, pattern0.jsflags) + private var inputstr = input0.subSequence(regionStart0, regionEnd0).toString + + // Match result (updated by successful matches) + private var lastMatch: js.RegExp.ExecResult = null + private var lastMatchIsValid = false + private var canStillFind = true + + // Append state (updated by replacement methods) + private var appendPos: Int = 0 + + // Lookup methods + + def matches(): Boolean = { + reset() + find() + // TODO this check is wrong with non-greedy patterns + // Further, it might be wrong to just use ^$ delimiters for two reasons: + // - They might already be there + // - They might not behave as expected when newline characters are present + if ((lastMatch ne null) && (start != 0 || end != inputstr.length)) + reset() + lastMatch ne null + } + + def lookingAt(): Boolean = { + reset() + find() + if ((lastMatch ne null) && (start != 0)) + reset() + lastMatch ne null + } + + def find(): Boolean = if (canStillFind) { + lastMatchIsValid = true + lastMatch = regexp.exec(inputstr) + if (lastMatch ne null) { + if (lastMatch(0).get.isEmpty) + regexp.lastIndex += 1 + } else { + canStillFind = false + } + lastMatch ne null + } else false + + def find(start: Int): Boolean = { + reset() + regexp.lastIndex = start + find() + } + + // Replace methods + + def appendReplacement(sb: StringBuffer, replacement: String): Matcher = { + sb.append(inputstr.substring(appendPos, start)) + + @inline def isDigit(c: Char) = c >= '0' && c <= '9' + + val len = replacement.length + var i = 0 + while (i < len) { + replacement.charAt(i) match { + case '$' => + i += 1 + val j = i + while (i < len && isDigit(replacement.charAt(i))) + i += 1 + val group = Integer.parseInt(replacement.substring(j, i)) + sb.append(this.group(group)) + + case '\\' => + i += 1 + if (i < len) + sb.append(replacement.charAt(i)) + i += 1 + + case c => + sb.append(c) + i += 1 + } + } + + appendPos = end + this + } + + def appendTail(sb: StringBuffer): StringBuffer = { + sb.append(inputstr.substring(appendPos)) + appendPos = inputstr.length + sb + } + + def replaceFirst(replacement: String): String = { + reset() + + if (find()) { + val sb = new StringBuffer + appendReplacement(sb, replacement) + appendTail(sb) + sb.toString + } else { + inputstr + } + } + + def replaceAll(replacement: String): String = { + reset() + + val sb = new StringBuffer + while (find()) { + appendReplacement(sb, replacement) + } + appendTail(sb) + + sb.toString + } + + // Reset methods + + def reset(): Matcher = { + regexp.lastIndex = 0 + lastMatch = null + lastMatchIsValid = false + canStillFind = true + appendPos = 0 + this + } + + def reset(input: CharSequence): Matcher = { + regionStart0 = 0 + regionEnd0 = input.length() + input0 = input + inputstr = input0.toString + reset() + } + + def usePattern(pattern: Pattern): Matcher = { + val prevLastIndex = regexp.lastIndex + pattern0 = pattern + regexp = new js.RegExp(pattern.jspattern, pattern.jsflags) + regexp.lastIndex = prevLastIndex + lastMatch = null + this + } + + // Query state methods - implementation of MatchResult + + private def ensureLastMatch: js.RegExp.ExecResult = { + if (lastMatch == null) + throw new IllegalStateException("No match available") + lastMatch + } + + def groupCount(): Int = ensureLastMatch.length-1 + + def start(): Int = ensureLastMatch.index + def end(): Int = start() + group().length + def group(): String = ensureLastMatch(0).get + + def start(group: Int): Int = { + if (group == 0) start() + else { + val last = ensureLastMatch + // not provided by JS RegExp, so we make up something that at least + // will have some sound behavior from scala.util.matching.Regex + last(group).fold(-1) { + groupStr => inputstr.indexOf(groupStr, last.index) + } + } + } + + def end(group: Int): Int = { + val s = start(group) + if (s == -1) -1 + else s + this.group(group).length + } + + def group(group: Int): String = ensureLastMatch(group).orNull + + // Seal the state + + def toMatchResult(): MatchResult = new SealedResult(inputstr, lastMatch) + + // Other query state methods + + def hitEnd(): Boolean = + lastMatchIsValid && (lastMatch == null || end() == inputstr.length) + + //def requireEnd(): Boolean // I don't understand the spec + + // Stub methods for region management + + def regionStart(): Int = regionStart0 + def regionEnd(): Int = regionEnd0 + def region(start: Int, end: Int): Matcher = + new Matcher(pattern0, input0, start, end) + + def hasTransparentBounds(): Boolean = false + //def useTransparentBounds(b: Boolean): Matcher + + def hasAnchoringBounds(): Boolean = true + //def useAnchoringBounds(b: Boolean): Matcher +} + +object Matcher { + def quoteReplacement(s: String): String = { + var result = "" + var i = 0 + while (i < s.length) { + val c = s.charAt(i) + result += ((c: @switch) match { + case '\\' | '$' => "\\"+c + case _ => c + }) + i += 1 + } + result + } + + private final class SealedResult(inputstr: String, + lastMatch: js.RegExp.ExecResult) extends MatchResult { + + def groupCount(): Int = ensureLastMatch.length-1 + + def start(): Int = ensureLastMatch.index + def end(): Int = start() + group().length + def group(): String = ensureLastMatch(0).get + + def start(group: Int): Int = { + if (group == 0) start() + else { + val last = ensureLastMatch + + // not provided by JS RegExp, so we make up something that at least + // will have some sound behavior from scala.util.matching.Regex + last(group).fold(-1) { + groupStr => inputstr.indexOf(groupStr, last.index) + } + } + } + + def end(group: Int): Int = { + val s = start(group) + if (s == -1) -1 + else s + this.group(group).length + } + + def group(group: Int): String = ensureLastMatch(group).orNull + + private def ensureLastMatch: js.RegExp.ExecResult = { + if (lastMatch == null) + throw new IllegalStateException("No match available") + lastMatch + } + } +} diff --git a/examples/scala-js/javalib/src/main/scala/java/util/regex/Pattern.scala b/examples/scala-js/javalib/src/main/scala/java/util/regex/Pattern.scala new file mode 100644 index 0000000..fda103f --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/regex/Pattern.scala @@ -0,0 +1,154 @@ +package java.util.regex + +import scala.annotation.switch + +import scala.scalajs.js + +final class Pattern private (pattern0: String, flags0: Int) + extends Serializable { + + import Pattern._ + + def pattern(): String = pattern0 + def flags(): Int = flags1 + + private[regex] val (jspattern, flags1) = { + if ((flags0 & LITERAL) != 0) (quote(pattern0), flags0) + else { + trySplitHack(pattern0, flags0) orElse + tryFlagHack(pattern0, flags0) getOrElse + (pattern0, flags0) + } + } + + private[regex] val jsflags = { + var f = "g" + if ((flags & CASE_INSENSITIVE) != 0) + f += "i" + if ((flags & MULTILINE) != 0) + f += "m" + f + } + + override def toString(): String = pattern0 + + def matcher(input: CharSequence): Matcher = + new Matcher(this, input, 0, input.length) + + def split(input: CharSequence): Array[String] = + split(input, 0) + + def split(input: CharSequence, limit: Int): Array[String] = { + val lim = if (limit > 0) limit else Int.MaxValue + + val result = js.Array[String]() + val inputStr = input.toString + val matcher = this.matcher(inputStr) + var prevEnd = 0 + + // Actually split original string + while ((result.length < lim-1) && matcher.find()) { + result.push(inputStr.substring(prevEnd, matcher.start)) + prevEnd = matcher.end + } + result.push(inputStr.substring(prevEnd)) + + // Remove a leading empty element iff the first match was zero-length + // and there is no other place the regex matches + if (prevEnd == 0 && result.length == 2 && (lim > 2 || !matcher.find())) { + Array(inputStr) + } else { + var len = result.length + if (limit == 0) { + while (len > 1 && result(len-1).isEmpty) + len -= 1 + } + + val actualResult = new Array[String](len) + result.copyToArray(actualResult) + actualResult + } + } +} + +object Pattern { + final val UNIX_LINES = 0x01 + final val CASE_INSENSITIVE = 0x02 + final val COMMENTS = 0x04 + final val MULTILINE = 0x08 + final val LITERAL = 0x10 + final val DOTALL = 0x20 + final val UNICODE_CASE = 0x40 + final val CANON_EQ = 0x80 + final val UNICODE_CHARACTER_CLASS = 0x100 + + def compile(regex: String, flags: Int): Pattern = + new Pattern(regex, flags) + + def compile(regex: String): Pattern = + new Pattern(regex, 0) + + def matches(regex: String, input: CharSequence): Boolean = + compile(regex).matcher(input).matches() + + def quote(s: String): String = { + var result = "" + var i = 0 + while (i < s.length) { + val c = s.charAt(i) + result += ((c: @switch) match { + case '\\' | '.' | '(' | ')' | '[' | ']' | '{' | '}' | '|' + | '?' | '*' | '+' | '^' | '$' => "\\"+c + case _ => c + }) + i += 1 + } + result + } + + /** This is a hack to support StringLike.split(). + * It replaces occurrences of \Q<char>\E by quoted(<char>) + */ + @inline + private def trySplitHack(pat: String, flags: Int) = { + val m = splitHackPat.exec(pat) + if (m != null) + Some((quote(m(1).get), flags)) + else + None + } + + @inline + private def tryFlagHack(pat: String, flags0: Int) = { + val m = flagHackPat.exec(pat) + if (m != null) { + val newPat = pat.substring(m(0).get.length) // cut off the flag specifiers + val flags1 = m(1).fold(flags0) { chars => + chars.foldLeft(flags0) { (f, c) => f | charToFlag(c) } + } + val flags2 = m(2).fold(flags1) { chars => + chars.foldLeft(flags1) { (f, c) => f & ~charToFlag(c) } + } + Some((newPat, flags2)) + } else + None + } + + private def charToFlag(c: Char) = (c: @switch) match { + case 'i' => CASE_INSENSITIVE + case 'd' => UNIX_LINES + case 'm' => MULTILINE + case 's' => DOTALL + case 'u' => UNICODE_CASE + case 'x' => COMMENTS + case 'U' => UNICODE_CHARACTER_CLASS + case _ => sys.error("bad in-pattern flag") + } + + /** matches \Q<char>\E to support StringLike.split */ + private val splitHackPat = new js.RegExp("^\\\\Q(.|\\n|\\r)\\\\E$") + + /** regex to match flag specifiers in regex. E.g. (?u), (?-i), (?U-i) */ + private val flagHackPat = + new js.RegExp("^\\(\\?([idmsuxU]*)(?:-([idmsuxU]*))?\\)") +} |