diff options
Diffstat (limited to 'javalib/src/main/scala/java/util')
19 files changed, 2066 insertions, 0 deletions
diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala new file mode 100644 index 0000000..ed9afd1 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/Comparator.scala b/javalib/src/main/scala/java/util/Comparator.scala new file mode 100644 index 0000000..d63c48e --- /dev/null +++ b/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/javalib/src/main/scala/java/util/Date.scala b/javalib/src/main/scala/java/util/Date.scala new file mode 100644 index 0000000..8f4e85f --- /dev/null +++ b/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/javalib/src/main/scala/java/util/Formattable.scala b/javalib/src/main/scala/java/util/Formattable.scala new file mode 100644 index 0000000..e651fbb --- /dev/null +++ b/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/javalib/src/main/scala/java/util/FormattableFlags.scala b/javalib/src/main/scala/java/util/FormattableFlags.scala new file mode 100644 index 0000000..02f5bce --- /dev/null +++ b/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/javalib/src/main/scala/java/util/Formatter.scala b/javalib/src/main/scala/java/util/Formatter.scala new file mode 100644 index 0000000..5e0ab22 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/Random.scala b/javalib/src/main/scala/java/util/Random.scala new file mode 100644 index 0000000..80428fb --- /dev/null +++ b/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/javalib/src/main/scala/java/util/Throwables.scala b/javalib/src/main/scala/java/util/Throwables.scala new file mode 100644 index 0000000..c4bb3d6 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/UUID.scala b/javalib/src/main/scala/java/util/UUID.scala new file mode 100644 index 0000000..9e6c1d4 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala b/javalib/src/main/scala/java/util/concurrent/ExecutionException.scala new file mode 100644 index 0000000..6d04889 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/concurrent/Executor.scala b/javalib/src/main/scala/java/util/concurrent/Executor.scala new file mode 100644 index 0000000..d030551 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala b/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala new file mode 100644 index 0000000..a77dbfc --- /dev/null +++ b/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/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicBoolean.scala new file mode 100644 index 0000000..5675c31 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala new file mode 100644 index 0000000..1f24b7b --- /dev/null +++ b/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/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala new file mode 100644 index 0000000..5bfecf2 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala new file mode 100644 index 0000000..650b1e0 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/regex/MatchResult.scala b/javalib/src/main/scala/java/util/regex/MatchResult.scala new file mode 100644 index 0000000..f321c60 --- /dev/null +++ b/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/javalib/src/main/scala/java/util/regex/Matcher.scala b/javalib/src/main/scala/java/util/regex/Matcher.scala new file mode 100644 index 0000000..331f56b --- /dev/null +++ b/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/javalib/src/main/scala/java/util/regex/Pattern.scala b/javalib/src/main/scala/java/util/regex/Pattern.scala new file mode 100644 index 0000000..fda103f --- /dev/null +++ b/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]*))?\\)") +} |