diff options
Diffstat (limited to 'javalib/src/main/scala/java/nio')
22 files changed, 1622 insertions, 0 deletions
diff --git a/javalib/src/main/scala/java/nio/Buffer.scala b/javalib/src/main/scala/java/nio/Buffer.scala new file mode 100644 index 0000000..be7ab7f --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/BufferOverflowException.scala b/javalib/src/main/scala/java/nio/BufferOverflowException.scala new file mode 100644 index 0000000..03f359e --- /dev/null +++ b/javalib/src/main/scala/java/nio/BufferOverflowException.scala @@ -0,0 +1,3 @@ +package java.nio + +class BufferOverflowException extends RuntimeException diff --git a/javalib/src/main/scala/java/nio/BufferUnderflowException.scala b/javalib/src/main/scala/java/nio/BufferUnderflowException.scala new file mode 100644 index 0000000..e286975 --- /dev/null +++ b/javalib/src/main/scala/java/nio/BufferUnderflowException.scala @@ -0,0 +1,3 @@ +package java.nio + +class BufferUnderflowException extends RuntimeException diff --git a/javalib/src/main/scala/java/nio/ByteBuffer.scala b/javalib/src/main/scala/java/nio/ByteBuffer.scala new file mode 100644 index 0000000..b743b39 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/ByteOrder.scala b/javalib/src/main/scala/java/nio/ByteOrder.scala new file mode 100644 index 0000000..20bac6a --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/CharBuffer.scala b/javalib/src/main/scala/java/nio/CharBuffer.scala new file mode 100644 index 0000000..5e74953 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/HeapByteBuffer.scala b/javalib/src/main/scala/java/nio/HeapByteBuffer.scala new file mode 100644 index 0000000..ed3fd29 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/HeapCharBuffer.scala b/javalib/src/main/scala/java/nio/HeapCharBuffer.scala new file mode 100644 index 0000000..546c55d --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/InvalidMarkException.scala b/javalib/src/main/scala/java/nio/InvalidMarkException.scala new file mode 100644 index 0000000..c2d3714 --- /dev/null +++ b/javalib/src/main/scala/java/nio/InvalidMarkException.scala @@ -0,0 +1,3 @@ +package java.nio + +class InvalidMarkException extends IllegalStateException diff --git a/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala b/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala new file mode 100644 index 0000000..ee0868b --- /dev/null +++ b/javalib/src/main/scala/java/nio/ReadOnlyBufferException.scala @@ -0,0 +1,3 @@ +package java.nio + +class ReadOnlyBufferException extends UnsupportedOperationException diff --git a/javalib/src/main/scala/java/nio/StringCharBuffer.scala b/javalib/src/main/scala/java/nio/StringCharBuffer.scala new file mode 100644 index 0000000..25bc594 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala b/javalib/src/main/scala/java/nio/charset/CharacterCodingException.scala new file mode 100644 index 0000000..8017348 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/Charset.scala b/javalib/src/main/scala/java/nio/charset/Charset.scala new file mode 100644 index 0000000..6d1af47 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala b/javalib/src/main/scala/java/nio/charset/CharsetDecoder.scala new file mode 100644 index 0000000..a3532ba --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala b/javalib/src/main/scala/java/nio/charset/CharsetEncoder.scala new file mode 100644 index 0000000..37d2296 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala b/javalib/src/main/scala/java/nio/charset/CoderMalfunctionError.scala new file mode 100644 index 0000000..33174f3 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/javalib/src/main/scala/java/nio/charset/CoderResult.scala new file mode 100644 index 0000000..fdc63cc --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala b/javalib/src/main/scala/java/nio/charset/CodingErrorAction.scala new file mode 100644 index 0000000..63b48bb --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala b/javalib/src/main/scala/java/nio/charset/MalformedInputException.scala new file mode 100644 index 0000000..4c91c1b --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala b/javalib/src/main/scala/java/nio/charset/StandardCharsets.scala new file mode 100644 index 0000000..38f3f98 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala b/javalib/src/main/scala/java/nio/charset/UnmappableCharacterException.scala new file mode 100644 index 0000000..5748f70 --- /dev/null +++ b/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/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala b/javalib/src/main/scala/java/nio/charset/UnsupportedCharsetException.scala new file mode 100644 index 0000000..97a7a4e --- /dev/null +++ b/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 +} |