/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.objects.binary;
import static com.github.anba.es6draft.runtime.AbstractOperations.IsConstructor;
import static com.github.anba.es6draft.runtime.AbstractOperations.SpeciesConstructor;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToLength;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToNumber;
import static com.github.anba.es6draft.runtime.internal.Errors.newRangeError;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.Properties.Accessor;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.CompatibilityExtension;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.types.BuiltinSymbol;
import com.github.anba.es6draft.runtime.types.Constructor;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.BuiltinConstructor;
/**
* <h1>24 Structured Data</h1><br>
* <h2>24.1 ArrayBuffer Objects</h2>
* <ul>
* <li>24.1.1 Abstract Operations For ArrayBuffer Objects
* <li>24.1.2 The ArrayBuffer Constructor
* <li>24.1.3 Properties of the ArrayBuffer Constructor
* </ul>
*/
public final class ArrayBufferConstructor extends BuiltinConstructor implements Initializable {
private static final boolean IS_LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
/**
* Constructs a new ArrayBuffer constructor function.
*
* @param realm
* the realm object
*/
public ArrayBufferConstructor(Realm realm) {
super(realm, "ArrayBuffer", 1);
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
createProperties(realm, this, AdditionalProperties.class);
}
@Override
public ArrayBufferConstructor clone() {
return new ArrayBufferConstructor(getRealm());
}
/**
* 6.2.6.1 CreateByteDataBlock(size)
*
* @param cx
* the execution context
* @param size
* the byte buffer size in bytes
* @return the new byte buffer
*/
public static ByteBuffer CreateByteDataBlock(ExecutionContext cx, long size) {
/* step 1 */
assert size >= 0;
/* step 2 */
if (size > Integer.MAX_VALUE) {
throw newRangeError(cx, Messages.Key.OutOfMemory);
}
try {
/* step 3 */
// TODO: Call allocateDirect() if size exceeds predefined limit?
return ByteBuffer.allocate((int) size).order(ByteOrder.nativeOrder());
} catch (OutOfMemoryError e) {
/* step 2 */
throw newRangeError(cx, Messages.Key.OutOfMemoryVM);
}
}
/**
* 6.2.6.2 CopyDataBlockBytes(toBlock, toIndex, fromBlock, fromIndex, count)
*
* @param toBlock
* the target byte buffer
* @param toIndex
* the target offset
* @param fromBlock
* the source byte buffer
* @param fromIndex
* the source offset
* @param count
* the number of bytes to copy
*/
public static void CopyDataBlockBytes(ByteBuffer toBlock, long toIndex, ByteBuffer fromBlock,
long fromIndex, long count) {
/* step 1 */
assert fromBlock != toBlock;
/* step 2 */
assert fromIndex >= 0 && toIndex >= 0 && count >= 0;
/* steps 3-4 */
assert fromIndex + count <= fromBlock.capacity();
/* steps 5-6 */
assert toIndex + count <= toBlock.capacity();
/* steps 7-8 */
fromBlock.limit((int) (fromIndex + count)).position((int) fromIndex);
toBlock.limit((int) (toIndex + count)).position((int) toIndex);
toBlock.put(fromBlock);
toBlock.clear();
fromBlock.clear();
}
/**
* 24.1.1.1 AllocateArrayBuffer( constructor, byteLength )
*
* @param cx
* the execution context
* @param constructor
* the constructor function
* @param byteLength
* the buffer byte length
* @return the new array buffer object
*/
public static ArrayBufferObject AllocateArrayBuffer(ExecutionContext cx,
Constructor constructor, long byteLength) {
/* steps 1-2 */
ScriptObject proto = GetPrototypeFromConstructor(cx, constructor,
Intrinsics.ArrayBufferPrototype);
/* step 3 */
assert byteLength >= 0;
/* steps 4-5 */
ByteBuffer block = CreateByteDataBlock(cx, byteLength);
/* steps 1-2, 6-8 */
return new ArrayBufferObject(cx.getRealm(), block, byteLength, proto);
}
/**
* 24.1.1.2 IsDetachedBuffer( arrayBuffer )
*
* @param arrayBuffer
* the array buffer object
* @return {@code true} if the array buffer is detached
*/
public static boolean IsDetachedBuffer(ArrayBuffer arrayBuffer) {
/* step 1 (not applicable) */
/* steps 2-3 */
return arrayBuffer.isDetached();
}
/**
* 24.1.1.3 DetachArrayBuffer( arrayBuffer )
*
* @param cx
* the execution context
* @param arrayBuffer
* the array buffer object
*/
public static void DetachArrayBuffer(ExecutionContext cx, ArrayBuffer arrayBuffer) {
// TODO: Perform any checks here? E.g. already detached?
/* step 1 (not applicable) */
/* steps 2-3 */
arrayBuffer.detach();
/* step 4 (return) */
}
/**
* 24.1.1.4 CloneArrayBuffer (srcBuffer, srcByteOffset)
*
* @param cx
* the execution context
* @param srcBuffer
* the source buffer
* @param srcByteOffset
* the source offset
* @return the new array buffer object
*/
public static ArrayBufferObject CloneArrayBuffer(ExecutionContext cx, ArrayBuffer srcBuffer, long srcByteOffset) {
return CloneArrayBuffer(cx, srcBuffer, srcByteOffset, null);
}
/**
* 24.1.1.4 CloneArrayBuffer (srcBuffer, srcByteOffset)
*
* @param cx
* the execution context
* @param srcBuffer
* the source buffer
* @param srcByteOffset
* the source offset
* @param cloneConstructor
* the intrinsic constructor function
* @return the new array buffer object
*/
public static ArrayBufferObject CloneArrayBuffer(ExecutionContext cx, ArrayBuffer srcBuffer, long srcByteOffset,
Intrinsics cloneConstructor) {
/* step 1 (implicit) */
/* steps 2-3 */
Constructor bufferConstructor;
if (cloneConstructor == null) {
/* steps 2.a-b */
bufferConstructor = SpeciesConstructor(cx, srcBuffer, Intrinsics.ArrayBuffer);
/* step 2.c */
if (IsDetachedBuffer(srcBuffer)) {
throw newTypeError(cx, Messages.Key.BufferDetached);
}
} else {
/* step 3 */
assert IsConstructor(cx.getIntrinsic(cloneConstructor));
bufferConstructor = (Constructor) cx.getIntrinsic(cloneConstructor);
}
/* step 4 */
ByteBuffer srcBlock = srcBuffer.getData();
/* step 5 */
long srcLength = srcBuffer.getByteLength();
/* step 6 */
assert srcByteOffset <= srcLength;
/* step 7 */
long cloneLength = srcLength - srcByteOffset;
/* steps 8-9 */
ArrayBufferObject targetBuffer = AllocateArrayBuffer(cx, bufferConstructor, cloneLength);
/* step 10 */
if (IsDetachedBuffer(srcBuffer)) {
throw newTypeError(cx, Messages.Key.BufferDetached);
}
/* step 11 */
ByteBuffer targetBlock = targetBuffer.getData();
/* step 12 */
CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, cloneLength);
/* step 13 */
return targetBuffer;
}
/**
* 24.1.1.5 GetValueFromBuffer (arrayBuffer, byteIndex, type, isLittleEndian)
*
* @param arrayBuffer
* the array buffer object
* @param byteIndex
* the byte index
* @param type
* the element type
* @return the buffer value
*/
public static double GetValueFromBuffer(ArrayBuffer arrayBuffer, long byteIndex, ElementType type) {
return GetValueFromBuffer(arrayBuffer, byteIndex, type, IS_LITTLE_ENDIAN);
}
/**
* 24.1.1.5 GetValueFromBuffer (arrayBuffer, byteIndex, type, isLittleEndian)
*
* @param arrayBuffer
* the array buffer object
* @param byteIndex
* the byte index
* @param type
* the element type
* @param isLittleEndian
* the little endian flag
* @return the buffer value
*/
public static double GetValueFromBuffer(ArrayBuffer arrayBuffer, long byteIndex, ElementType type,
boolean isLittleEndian) {
/* step 1 */
assert !IsDetachedBuffer(arrayBuffer) : "ArrayBuffer is detached";
/* steps 2-3 */
assert byteIndex >= 0 && (byteIndex + type.size() <= arrayBuffer.getByteLength());
/* step 4 */
ByteBuffer block = arrayBuffer.getData();
/* steps 7-8 */
if ((block.order() == ByteOrder.LITTLE_ENDIAN) != isLittleEndian) {
// NB: Byte order is not reset after this call.
block.order(isLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
int index = (int) byteIndex;
switch (type) {
case Float32: {
/* steps 5-6, 9 */
double rawValue = block.getFloat(index);
return Double.isNaN(rawValue) ? Double.NaN : rawValue;
}
case Float64: {
/* steps 5-6, 10 */
double rawValue = block.getDouble(index);
return Double.isNaN(rawValue) ? Double.NaN : rawValue;
}
/* steps 5-6, 11, 13 */
case Uint8:
case Uint8C:
return block.get(index) & 0xff;
case Uint16:
return block.getShort(index) & 0xffff;
case Uint32:
return block.getInt(index) & 0xffff_ffffL;
/* steps 5-6, 12-13 */
case Int8:
return block.get(index);
case Int16:
return block.getShort(index);
case Int32:
return block.getInt(index);
default:
throw new AssertionError();
}
}
/**
* 24.1.1.6 SetValueInBuffer (arrayBuffer, byteIndex, type, value, isLittleEndian)
*
* @param arrayBuffer
* the array buffer object
* @param byteIndex
* the byte index
* @param type
* the element type
* @param value
* the new element value
*/
public static void SetValueInBuffer(ArrayBuffer arrayBuffer, long byteIndex, ElementType type, double value) {
SetValueInBuffer(arrayBuffer, byteIndex, type, value, IS_LITTLE_ENDIAN);
}
/**
* 24.1.1.6 SetValueInBuffer (arrayBuffer, byteIndex, type, value, isLittleEndian)
*
* @param arrayBuffer
* the array buffer object
* @param byteIndex
* the byte index
* @param type
* the element type
* @param value
* the new element value
* @param isLittleEndian
* the little endian flag
*/
public static void SetValueInBuffer(ArrayBuffer arrayBuffer, long byteIndex, ElementType type, double value,
boolean isLittleEndian) {
/* step 1 */
assert !IsDetachedBuffer(arrayBuffer) : "ArrayBuffer is detached";
/* steps 2-3 */
assert byteIndex >= 0 && (byteIndex + type.size() <= arrayBuffer.getByteLength());
/* step 4 (not applicable) */
/* step 5 */
ByteBuffer block = arrayBuffer.getData();
/* step 6 */
assert block != null;
/* step 8 */
if ((block.order() == ByteOrder.LITTLE_ENDIAN) != isLittleEndian) {
// NB: Byte order is not reset after this call.
block.order(isLittleEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
int index = (int) byteIndex;
switch (type) {
case Float32:
/* steps 5, 9, 12-13 */
block.putFloat(index, (float) value);
return;
case Float64:
/* steps 5, 10, 12-13 */
block.putDouble(index, value);
return;
/* steps 5, 11-13 */
case Int8:
block.put(index, ElementType.ToInt8(value));
return;
case Uint8:
block.put(index, ElementType.ToUint8(value));
return;
case Uint8C:
block.put(index, ElementType.ToUint8Clamp(value));
return;
case Int16:
block.putShort(index, ElementType.ToInt16(value));
return;
case Uint16:
block.putShort(index, ElementType.ToUint16(value));
return;
case Int32:
block.putInt(index, ElementType.ToInt32(value));
return;
case Uint32:
block.putInt(index, ElementType.ToUint32(value));
return;
default:
throw new AssertionError();
}
}
/**
* 24.1.2.1 ArrayBuffer(length)
*/
@Override
public ArrayBufferObject call(ExecutionContext callerContext, Object thisValue, Object... args) {
/* step 1 */
throw newTypeError(calleeContext(), Messages.Key.InvalidCall, "ArrayBuffer");
}
/**
* 24.1.2.1 ArrayBuffer(length)
*/
@Override
public ArrayBufferObject construct(ExecutionContext callerContext, Constructor newTarget,
Object... args) {
ExecutionContext calleeContext = calleeContext();
Object length = argument(args, 0);
/* step 1 (not applicable) */
// FIXME: spec issue? - missing length parameter same as 0 for bwcompat?
if (args.length == 0
&& getRealm().isEnabled(CompatibilityOption.ArrayBufferMissingLength)) {
length = 0;
}
/* step 2 */
double numberLength = ToNumber(calleeContext, length);
/* steps 3-4 */
long byteLength = ToLength(numberLength);
/* step 5 */
if (numberLength != byteLength) { // SameValueZero
throw newRangeError(calleeContext, Messages.Key.InvalidBufferSize);
}
/* step 6 */
return AllocateArrayBuffer(calleeContext, newTarget, byteLength);
}
/**
* 24.1.3 Properties of the ArrayBuffer Constructor
*/
public enum Properties {
;
@Prototype
public static final Intrinsics __proto__ = Intrinsics.FunctionPrototype;
@Value(name = "length", attributes = @Attributes(writable = false, enumerable = false, configurable = true))
public static final int length = 1;
@Value(name = "name", attributes = @Attributes(writable = false, enumerable = false, configurable = true))
public static final String name = "ArrayBuffer";
/**
* 24.1.3.2 ArrayBuffer.prototype
*/
@Value(name = "prototype", attributes = @Attributes(writable = false, enumerable = false, configurable = false))
public static final Intrinsics prototype = Intrinsics.ArrayBufferPrototype;
/**
* 24.1.3.1 ArrayBuffer.isView ( arg )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param arg
* the argument object
* @return {@code true} if the argument is an array buffer view object
*/
@Function(name = "isView", arity = 1)
public static Object isView(ExecutionContext cx, Object thisValue, Object arg) {
/* steps 1-3 */
return arg instanceof ArrayBufferView;
}
/**
* 24.1.3.3 get ArrayBuffer [ @@species ]
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the species object
*/
@Accessor(name = "get [Symbol.species]", symbol = BuiltinSymbol.species, type = Accessor.Type.Getter)
public static Object species(ExecutionContext cx, Object thisValue) {
/* step 1 */
return thisValue;
}
}
/**
* Proposed ECMAScript 7 additions
*/
@CompatibilityExtension(CompatibilityOption.ArrayBufferTransfer)
public enum AdditionalProperties {
;
private static ArrayBufferObject thisArrayBufferObjectChecked(ExecutionContext cx, Object m) {
if (m instanceof ArrayBufferObject) {
ArrayBufferObject buffer = (ArrayBufferObject) m;
if (IsDetachedBuffer(buffer)) {
throw newTypeError(cx, Messages.Key.BufferDetached);
}
return buffer;
}
throw newTypeError(cx, Messages.Key.IncompatibleObject);
}
/**
* ArrayBuffer.transfer(oldBuffer [, newByteLength])
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param oldBuffer
* the old array buffer
* @param newByteLength
* the optional new byte length
* @return the result index
*/
@Function(name = "transfer", arity = 1)
public static Object transfer(ExecutionContext cx, Object thisValue, Object oldBuffer, Object newByteLength) {
ArrayBufferObject oldArrayBuffer = thisArrayBufferObjectChecked(cx, oldBuffer);
long byteLength;
if (!Type.isUndefined(newByteLength)) {
// Perform same length validation as in new ArrayBuffer(length).
double numberLength = ToNumber(cx, newByteLength);
byteLength = ToLength(numberLength);
if (numberLength != byteLength) { // SameValueZero
throw newRangeError(cx, Messages.Key.InvalidBufferSize);
}
} else {
// newByteLength defaults to oldBuffer.byteLength
byteLength = oldArrayBuffer.getByteLength();
}
Constructor ctor;
if (IsConstructor(thisValue)) {
ctor = (Constructor) thisValue;
} else {
ctor = (Constructor) cx.getIntrinsic(Intrinsics.ArrayBuffer);
}
ScriptObject proto = GetPrototypeFromConstructor(cx, ctor, Intrinsics.ArrayBufferPrototype);
if (IsDetachedBuffer(oldArrayBuffer)) {
throw newTypeError(cx, Messages.Key.BufferDetached);
}
// Grab old array buffer data and call detach.
ByteBuffer oldData = oldArrayBuffer.getData();
long oldByteLength = oldArrayBuffer.getByteLength();
DetachArrayBuffer(cx, oldArrayBuffer);
// Extend byte buffer.
if (byteLength > oldByteLength) {
ByteBuffer newData = CreateByteDataBlock(cx, byteLength);
CopyDataBlockBytes(newData, 0, oldData, 0, oldByteLength);
return new ArrayBufferObject(cx.getRealm(), newData, byteLength, proto);
}
// Truncate byte buffer.
if (byteLength < oldByteLength) {
// Possible improvement: Use limit() or slice() to reduce buffer allocations.
ByteBuffer newData = CreateByteDataBlock(cx, byteLength);
CopyDataBlockBytes(newData, 0, oldData, 0, byteLength);
return new ArrayBufferObject(cx.getRealm(), newData, byteLength, proto);
}
// Create new array buffer with the same byte buffer if length did not change.
return new ArrayBufferObject(cx.getRealm(), oldData, byteLength, proto);
}
}
}