/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.javascript.typedarrays; import org.mozilla.javascript.Context; import org.mozilla.javascript.ExternalArrayData; import org.mozilla.javascript.IdFunctionObject; import org.mozilla.javascript.NativeArray; import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Undefined; import java.lang.reflect.Array; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; /** * This class is the abstract parent for all of the various typed arrays. Each one * shows a view of a specific NativeArrayBuffer, and modifications here will affect the rest. */ public abstract class NativeTypedArrayView<T> extends NativeArrayBufferView implements List<T>, RandomAccess, ExternalArrayData { /** The length, in elements, of the array */ protected final int length; protected NativeTypedArrayView() { super(); length = 0; } protected NativeTypedArrayView(NativeArrayBuffer ab, int off, int len, int byteLen) { super(ab, off, byteLen); length = len; } // Array properties implementation @Override public Object get(int index, Scriptable start) { return js_get(index); } @Override public boolean has(int index, Scriptable start) { return ((index > 0) && (index < length)); } @Override public void put(int index, Scriptable start, Object val) { js_set(index, val); } @Override public void delete(int index) { } @Override public Object[] getIds() { Object[] ret = new Object[length]; for (int i = 0; i < length; i++) { ret[i] = Integer.valueOf(i); } return ret; } // Actual functions protected boolean checkIndex(int index) { return ((index < 0) || (index >= length)); } /** * Return the number of bytes represented by each element in the array. This can be useful * when wishing to manipulate the byte array directly from Java. */ public abstract int getBytesPerElement(); protected abstract NativeTypedArrayView construct(NativeArrayBuffer ab, int off, int len); protected abstract Object js_get(int index); protected abstract Object js_set(int index, Object c); protected abstract NativeTypedArrayView realThis(Scriptable thisObj, IdFunctionObject f); private NativeArrayBuffer makeArrayBuffer(Context cx, Scriptable scope, int length) { return (NativeArrayBuffer)cx.newObject(scope, NativeArrayBuffer.CLASS_NAME, new Object[] { length }); } private NativeTypedArrayView js_constructor(Context cx, Scriptable scope, Object[] args) { if (!isArg(args, 0)) { return construct(NativeArrayBuffer.EMPTY_BUFFER, 0, 0); } else if ((args[0] instanceof Number) || (args[0] instanceof String)) { // Create a zeroed-out array of a certain length int length = ScriptRuntime.toInt32(args[0]); NativeArrayBuffer buffer = makeArrayBuffer(cx, scope, length * getBytesPerElement()); return construct(buffer, 0, length); } else if (args[0] instanceof NativeTypedArrayView) { // Copy elements from the old array and convert them into our own NativeTypedArrayView src = (NativeTypedArrayView)args[0]; NativeArrayBuffer na = makeArrayBuffer(cx, scope, src.length * getBytesPerElement()); NativeTypedArrayView v = construct(na, 0, src.length); for (int i = 0; i < src.length; i++) { v.js_set(i, src.js_get(i)); } return v; } else if (args[0] instanceof NativeArrayBuffer) { // Make a slice of an existing buffer, with shared storage NativeArrayBuffer na = (NativeArrayBuffer)args[0]; int byteOff = isArg(args, 1) ? ScriptRuntime.toInt32(args[1]) : 0; int byteLen; if (isArg(args, 2)) { byteLen = ScriptRuntime.toInt32(args[2]) * getBytesPerElement(); } else { byteLen = na.getLength() - byteOff; } if ((byteOff < 0) || (byteOff > na.buffer.length)) { throw ScriptRuntime.constructError("RangeError", "offset out of range"); } if ((byteLen < 0) || ((byteOff + byteLen) > na.buffer.length)) { throw ScriptRuntime.constructError("RangeError", "length out of range"); } if ((byteOff % getBytesPerElement()) != 0) { throw ScriptRuntime.constructError("RangeError", "offset must be a multiple of the byte size"); } if ((byteLen % getBytesPerElement()) != 0) { throw ScriptRuntime.constructError("RangeError", "offset and buffer must be a multiple of the byte size"); } return construct(na, byteOff, byteLen / getBytesPerElement()); } else if (args[0] instanceof NativeArray) { // Copy elements of the array and convert them to the correct type List l = (List)args[0]; NativeArrayBuffer na = makeArrayBuffer(cx, scope, l.size() * getBytesPerElement()); NativeTypedArrayView v = construct(na, 0, l.size()); int p = 0; for (Object o : l) { v.js_set(p, o); p++; } return v; } else { throw ScriptRuntime.constructError("Error", "invalid argument"); } } private void setRange(NativeTypedArrayView v, int off) { if (off >= length) { throw ScriptRuntime.constructError("RangeError", "offset out of range"); } if (v.length > (length - off)) { throw ScriptRuntime.constructError("RangeError", "source array too long"); } if (v.arrayBuffer == arrayBuffer) { // Copy to temporary space first, as per spec, to avoid messing up overlapping copies Object[] tmp = new Object[v.length]; for (int i = 0; i < v.length; i++) { tmp[i] = v.js_get(i); } for (int i = 0; i < v.length; i++) { js_set(i + off, tmp[i]); } } else { for (int i = 0; i < v.length; i++) { js_set(i + off, v.js_get(i)); } } } private void setRange(NativeArray a, int off) { if (off > length) { throw ScriptRuntime.constructError("RangeError", "offset out of range"); } if ((off + a.size()) > length) { throw ScriptRuntime.constructError("RangeError", "offset + length out of range"); } int pos = off; for (Object val : a) { js_set(pos, val); pos++; } } private Object js_subarray(Context cx, Scriptable scope, int s, int e) { int start = (s < 0 ? length + s : s); int end = (e < 0 ? length + e : e); // Clamping behavior as described by the spec. start = Math.max(0, start); end = Math.min(length, end); int len = Math.max(0, (end - start)); int byteOff = Math.min(start * getBytesPerElement(), arrayBuffer.getLength()); return cx.newObject(scope, getClassName(), new Object[]{arrayBuffer, byteOff, len}); } // Dispatcher @Override public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { if (!f.hasTag(getClassName())) { return super.execIdCall(f, cx, scope, thisObj, args); } int id = f.methodId(); switch (id) { case Id_constructor: return js_constructor(cx, scope, args); case Id_get: if (args.length > 0) { return realThis(thisObj, f).js_get(ScriptRuntime.toInt32(args[0])); } else { throw ScriptRuntime.constructError("Error", "invalid arguments"); } case Id_set: if (args.length > 0) { NativeTypedArrayView self = realThis(thisObj, f); if (args[0] instanceof NativeTypedArrayView) { int offset = isArg(args, 1) ? ScriptRuntime.toInt32(args[1]) : 0; self.setRange((NativeTypedArrayView)args[0], offset); return Undefined.instance; } if (args[0] instanceof NativeArray) { int offset = isArg(args, 1) ? ScriptRuntime.toInt32(args[1]) : 0; self.setRange((NativeArray)args[0], offset); return Undefined.instance; } if (args[0] instanceof Scriptable) { // Tests show that we need to ignore a non-array object return Undefined.instance; } if (isArg(args, 2)) { return self.js_set(ScriptRuntime.toInt32(args[0]), args[1]); } } throw ScriptRuntime.constructError("Error", "invalid arguments"); case Id_subarray: if (args.length > 0) { NativeTypedArrayView self = realThis(thisObj, f); int start = ScriptRuntime.toInt32(args[0]); int end = isArg(args, 1) ? ScriptRuntime.toInt32(args[1]) : self.length; return self.js_subarray(cx, scope, start, end); } else { throw ScriptRuntime.constructError("Error", "invalid arguments"); } } throw new IllegalArgumentException(String.valueOf(id)); } @Override protected void initPrototypeId(int id) { String s; int arity; switch (id) { case Id_constructor: arity = 1; s = "constructor"; break; case Id_get: arity = 1; s = "get"; break; case Id_set: arity = 2; s = "set"; break; case Id_subarray: arity = 2; s = "subarray"; break; default: throw new IllegalArgumentException(String.valueOf(id)); } initPrototypeMethod(getClassName(), id, s, arity); } // #string_id_map# @Override protected int findPrototypeId(String s) { int id; // #generated# Last update: 2014-12-04 18:21:01 PST L0: { id = 0; String X = null; int c; int s_length = s.length(); if (s_length==3) { c=s.charAt(0); if (c=='g') { if (s.charAt(2)=='t' && s.charAt(1)=='e') {id=Id_get; break L0;} } else if (c=='s') { if (s.charAt(2)=='t' && s.charAt(1)=='e') {id=Id_set; break L0;} } } else if (s_length==8) { X="subarray";id=Id_subarray; } else if (s_length==11) { X="constructor";id=Id_constructor; } if (X!=null && X!=s && !X.equals(s)) id = 0; break L0; } // #/generated# return id; } // Table of all functions private static final int Id_constructor = 1, Id_get = 2, Id_set = 3, Id_subarray = 4; protected static final int MAX_PROTOTYPE_ID = Id_subarray; // #/string_id_map# // Constructor properties @Override protected void fillConstructorProperties(IdFunctionObject ctor) { ctor.put("BYTES_PER_ELEMENT", ctor, ScriptRuntime.wrapInt(getBytesPerElement())); } // Property dispatcher @Override protected int getMaxInstanceId() { return MAX_INSTANCE_ID; } @Override protected String getInstanceIdName(int id) { switch (id) { case Id_length: return "length"; case Id_BYTES_PER_ELEMENT: return "BYTES_PER_ELEMENT"; default: return super.getInstanceIdName(id); } } @Override protected Object getInstanceIdValue(int id) { switch (id) { case Id_length: return ScriptRuntime.wrapInt(length); case Id_BYTES_PER_ELEMENT: return ScriptRuntime.wrapInt(getBytesPerElement()); default: return super.getInstanceIdValue(id); } } // #string_id_map# @Override protected int findInstanceIdInfo(String s) { int id; // #generated# Last update: 2014-12-08 17:33:28 PST L0: { id = 0; String X = null; int s_length = s.length(); if (s_length==6) { X="length";id=Id_length; } else if (s_length==17) { X="BYTES_PER_ELEMENT";id=Id_BYTES_PER_ELEMENT; } if (X!=null && X!=s && !X.equals(s)) id = 0; break L0; } // #/generated# if (id == 0) { return super.findInstanceIdInfo(s); } return instanceIdInfo(READONLY | PERMANENT, id); } /* * These must not conflict with ids in the parent since we delegate there for property dispatching. */ private static final int Id_length = 10, Id_BYTES_PER_ELEMENT = 11, MAX_INSTANCE_ID = Id_BYTES_PER_ELEMENT; // #/string_id_map# // External Array implementation @Override public Object getArrayElement(int index) { return js_get(index); } @Override public void setArrayElement(int index, Object value) { js_set(index, value); } @Override public int getArrayLength() { return length; } // Abstract List implementation @SuppressWarnings("unused") public int size() { return length; } @SuppressWarnings("unused") public boolean isEmpty() { return (length == 0); } @SuppressWarnings("unused") public boolean contains(Object o) { return (indexOf(o) >= 0); } @SuppressWarnings("unused") public boolean containsAll(Collection<?> objects) { for (Object o : objects) { if (!contains(o)) { return false; } } return true; } @SuppressWarnings("unused") public int indexOf(Object o) { for (int i = 0; i < length; i++) { if (o.equals(js_get(i))) { return i; } } return -1; } @SuppressWarnings("unused") public int lastIndexOf(Object o) { for (int i = length - 1; i >= 0; i--) { if (o.equals(js_get(i))) { return i; } } return -1; } @SuppressWarnings("unused") public Object[] toArray() { Object[] a = new Object[length]; for (int i = 0; i < length; i++) { a[i] = js_get(i); } return a; } @SuppressWarnings("unused") public <U> U[] toArray(U[] ts) { U[] a; if (ts.length >= length) { a = ts; } else { a = (U[])Array.newInstance(ts.getClass().getComponentType(), length); } for (int i = 0; i < length; i++) { try { a[i] = (U)js_get(i); } catch (ClassCastException cce) { throw new ArrayStoreException(); } } return a; } @Override public boolean equals(Object o) { try { NativeTypedArrayView<T> v = (NativeTypedArrayView<T>)o; if (length != v.length) { return false; } for (int i = 0; i < length; i++) { if (!js_get(i).equals(v.js_get(i))) { return false; } } return true; } catch (ClassCastException cce) { return false; } } @Override public int hashCode() { int hc = 0; for (int i = 0; i < length; i++) { hc += js_get(i).hashCode(); } return 0; } @SuppressWarnings("unused") public Iterator<T> iterator() { return new NativeTypedArrayIterator<T>(this, 0); } @SuppressWarnings("unused") public ListIterator<T> listIterator() { return new NativeTypedArrayIterator<T>(this, 0); } @SuppressWarnings("unused") public ListIterator<T> listIterator(int start) { if (checkIndex(start)) { throw new IndexOutOfBoundsException(); } return new NativeTypedArrayIterator<T>(this, start); } @SuppressWarnings("unused") public List<T> subList(int i, int i2) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public boolean add(T aByte) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public void add(int i, T aByte) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public boolean addAll(Collection<? extends T> bytes) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public boolean addAll(int i, Collection<? extends T> bytes) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public void clear() { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public T remove(int i) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public boolean remove(Object o) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public boolean removeAll(Collection<?> objects) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") public boolean retainAll(Collection<?> objects) { throw new UnsupportedOperationException(); } }