org.mozilla.javascript.NativeArray Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino-runtime Show documentation
Show all versions of rhino-runtime Show documentation
Rhino JavaScript runtime jar, excludes tools & JSR-223 Script Engine wrapper.
/* -*- 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;
import static org.mozilla.javascript.ScriptRuntimeES6.requireObjectCoercible;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import org.mozilla.javascript.regexp.NativeRegExp;
import org.mozilla.javascript.xml.XMLObject;
/**
* This class implements the Array native object.
*
* @author Norris Boyd
* @author Mike McCabe
*/
public class NativeArray extends IdScriptableObject implements List {
private static final long serialVersionUID = 7331366857676127338L;
/*
* Optimization possibilities and open issues:
* - Long vs. double schizophrenia. I suspect it might be better
* to use double throughout.
*
* - Functions that need a new Array call "new Array" in the
* current scope rather than using a hardwired constructor;
* "Array" could be redefined. It turns out that js calls the
* equivalent of "new Array" in the current scope, except that it
* always gets at least an object back, even when Array == null.
*/
private static final Object ARRAY_TAG = "Array";
private static final Long NEGATIVE_ONE = Long.valueOf(-1);
static void init(Scriptable scope, boolean sealed) {
NativeArray obj = new NativeArray(0);
obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
}
static int getMaximumInitialCapacity() {
return maximumInitialCapacity;
}
static void setMaximumInitialCapacity(int maximumInitialCapacity) {
NativeArray.maximumInitialCapacity = maximumInitialCapacity;
}
public NativeArray(long lengthArg) {
denseOnly = lengthArg <= maximumInitialCapacity;
if (denseOnly) {
int intLength = (int) lengthArg;
if (intLength < DEFAULT_INITIAL_CAPACITY) intLength = DEFAULT_INITIAL_CAPACITY;
dense = new Object[intLength];
Arrays.fill(dense, Scriptable.NOT_FOUND);
}
length = lengthArg;
}
public NativeArray(Object[] array) {
denseOnly = true;
dense = array;
length = array.length;
}
@Override
public String getClassName() {
return "Array";
}
private static final int Id_length = 1, MAX_INSTANCE_ID = 1;
@Override
protected int getMaxInstanceId() {
return MAX_INSTANCE_ID;
}
@Override
protected void setInstanceIdAttributes(int id, int attr) {
if (id == Id_length) {
lengthAttr = attr;
}
}
@Override
protected int findInstanceIdInfo(String s) {
if (s.equals("length")) {
return instanceIdInfo(lengthAttr, Id_length);
}
return super.findInstanceIdInfo(s);
}
@Override
protected String getInstanceIdName(int id) {
if (id == Id_length) {
return "length";
}
return super.getInstanceIdName(id);
}
@Override
protected Object getInstanceIdValue(int id) {
if (id == Id_length) {
return ScriptRuntime.wrapNumber(length);
}
return super.getInstanceIdValue(id);
}
@Override
protected void setInstanceIdValue(int id, Object value) {
if (id == Id_length) {
setLength(value);
return;
}
super.setInstanceIdValue(id, value);
}
@Override
protected void fillConstructorProperties(IdFunctionObject ctor) {
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_join, "join", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reverse, "reverse", 0);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_sort, "sort", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_push, "push", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_pop, "pop", 0);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_shift, "shift", 0);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_unshift, "unshift", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_splice, "splice", 2);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_concat, "concat", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_slice, "slice", 2);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_indexOf, "indexOf", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_lastIndexOf, "lastIndexOf", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_every, "every", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_filter, "filter", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_forEach, "forEach", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_map, "map", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_some, "some", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_find, "find", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_findIndex, "findIndex", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reduce, "reduce", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reduceRight, "reduceRight", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_isArray, "isArray", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_of, "of", 0);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_from, "from", 1);
super.fillConstructorProperties(ctor);
}
@Override
protected void initPrototypeId(int id) {
if (id == SymbolId_iterator) {
initPrototypeMethod(ARRAY_TAG, id, SymbolKey.ITERATOR, "[Symbol.iterator]", 0);
return;
}
String s, fnName = null;
int arity;
switch (id) {
case Id_constructor:
arity = 1;
s = "constructor";
break;
case Id_toString:
arity = 0;
s = "toString";
break;
case Id_toLocaleString:
arity = 0;
s = "toLocaleString";
break;
case Id_toSource:
arity = 0;
s = "toSource";
break;
case Id_join:
arity = 1;
s = "join";
break;
case Id_reverse:
arity = 0;
s = "reverse";
break;
case Id_sort:
arity = 1;
s = "sort";
break;
case Id_push:
arity = 1;
s = "push";
break;
case Id_pop:
arity = 0;
s = "pop";
break;
case Id_shift:
arity = 0;
s = "shift";
break;
case Id_unshift:
arity = 1;
s = "unshift";
break;
case Id_splice:
arity = 2;
s = "splice";
break;
case Id_concat:
arity = 1;
s = "concat";
break;
case Id_slice:
arity = 2;
s = "slice";
break;
case Id_indexOf:
arity = 1;
s = "indexOf";
break;
case Id_lastIndexOf:
arity = 1;
s = "lastIndexOf";
break;
case Id_every:
arity = 1;
s = "every";
break;
case Id_filter:
arity = 1;
s = "filter";
break;
case Id_forEach:
arity = 1;
s = "forEach";
break;
case Id_map:
arity = 1;
s = "map";
break;
case Id_some:
arity = 1;
s = "some";
break;
case Id_find:
arity = 1;
s = "find";
break;
case Id_findIndex:
arity = 1;
s = "findIndex";
break;
case Id_reduce:
arity = 1;
s = "reduce";
break;
case Id_reduceRight:
arity = 1;
s = "reduceRight";
break;
case Id_fill:
arity = 1;
s = "fill";
break;
case Id_keys:
arity = 0;
s = "keys";
break;
case Id_values:
arity = 0;
s = "values";
break;
case Id_entries:
arity = 0;
s = "entries";
break;
case Id_includes:
arity = 1;
s = "includes";
break;
case Id_copyWithin:
arity = 2;
s = "copyWithin";
break;
default:
throw new IllegalArgumentException(String.valueOf(id));
}
initPrototypeMethod(ARRAY_TAG, id, s, fnName, arity);
}
@Override
public Object execIdCall(
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!f.hasTag(ARRAY_TAG)) {
return super.execIdCall(f, cx, scope, thisObj, args);
}
int id = f.methodId();
again:
for (; ; ) {
switch (id) {
case ConstructorId_join:
case ConstructorId_reverse:
case ConstructorId_sort:
case ConstructorId_push:
case ConstructorId_pop:
case ConstructorId_shift:
case ConstructorId_unshift:
case ConstructorId_splice:
case ConstructorId_concat:
case ConstructorId_slice:
case ConstructorId_indexOf:
case ConstructorId_lastIndexOf:
case ConstructorId_every:
case ConstructorId_filter:
case ConstructorId_forEach:
case ConstructorId_map:
case ConstructorId_some:
case ConstructorId_find:
case ConstructorId_findIndex:
case ConstructorId_reduce:
case ConstructorId_reduceRight:
{
// this is a small trick; we will handle all the ConstructorId_xxx calls
// the same way the object calls are processed
// so we adjust the args, inverting the id and
// restarting the method selection
// Attention: the implementations have to be aware of this
if (args.length > 0) {
thisObj = ScriptRuntime.toObject(cx, scope, args[0]);
Object[] newArgs = new Object[args.length - 1];
for (int i = 0; i < newArgs.length; i++) newArgs[i] = args[i + 1];
args = newArgs;
}
id = -id;
continue again;
}
case ConstructorId_isArray:
return Boolean.valueOf(args.length > 0 && js_isArray(args[0]));
case ConstructorId_of:
{
return js_of(cx, scope, thisObj, args);
}
case ConstructorId_from:
{
return js_from(cx, scope, thisObj, args);
}
case Id_constructor:
{
boolean inNewExpr = (thisObj == null);
if (!inNewExpr) {
// IdFunctionObject.construct will set up parent, proto
return f.construct(cx, scope, args);
}
return jsConstructor(cx, scope, args);
}
case Id_toString:
return toStringHelper(
cx,
scope,
thisObj,
cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE),
false);
case Id_toLocaleString:
return toStringHelper(cx, scope, thisObj, false, true);
case Id_toSource:
return toStringHelper(cx, scope, thisObj, true, false);
case Id_join:
return js_join(cx, scope, thisObj, args);
case Id_reverse:
return js_reverse(cx, scope, thisObj, args);
case Id_sort:
return js_sort(cx, scope, thisObj, args);
case Id_push:
return js_push(cx, scope, thisObj, args);
case Id_pop:
return js_pop(cx, scope, thisObj, args);
case Id_shift:
return js_shift(cx, scope, thisObj, args);
case Id_unshift:
return js_unshift(cx, scope, thisObj, args);
case Id_splice:
return js_splice(cx, scope, thisObj, args);
case Id_concat:
return js_concat(cx, scope, thisObj, args);
case Id_slice:
return js_slice(cx, scope, thisObj, args);
case Id_indexOf:
return js_indexOf(cx, scope, thisObj, args);
case Id_lastIndexOf:
return js_lastIndexOf(cx, scope, thisObj, args);
case Id_includes:
return js_includes(cx, scope, thisObj, args);
case Id_fill:
return js_fill(cx, scope, thisObj, args);
case Id_copyWithin:
return js_copyWithin(cx, scope, thisObj, args);
case Id_every:
case Id_filter:
case Id_forEach:
case Id_map:
case Id_some:
case Id_find:
case Id_findIndex:
return iterativeMethod(cx, f, scope, thisObj, args);
case Id_reduce:
case Id_reduceRight:
return reduceMethod(cx, id, scope, thisObj, args);
case Id_keys:
thisObj = ScriptRuntime.toObject(cx, scope, thisObj);
return new NativeArrayIterator(
scope, thisObj, NativeArrayIterator.ARRAY_ITERATOR_TYPE.KEYS);
case Id_entries:
thisObj = ScriptRuntime.toObject(cx, scope, thisObj);
return new NativeArrayIterator(
scope, thisObj, NativeArrayIterator.ARRAY_ITERATOR_TYPE.ENTRIES);
case Id_values:
case SymbolId_iterator:
thisObj = ScriptRuntime.toObject(cx, scope, thisObj);
return new NativeArrayIterator(
scope, thisObj, NativeArrayIterator.ARRAY_ITERATOR_TYPE.VALUES);
}
throw new IllegalArgumentException(
"Array.prototype has no method: " + f.getFunctionName());
}
}
@Override
public Object get(int index, Scriptable start) {
if (!denseOnly && isGetterOrSetter(null, index, false)) return super.get(index, start);
if (dense != null && 0 <= index && index < dense.length) return dense[index];
return super.get(index, start);
}
@Override
public boolean has(int index, Scriptable start) {
if (!denseOnly && isGetterOrSetter(null, index, false)) return super.has(index, start);
if (dense != null && 0 <= index && index < dense.length) return dense[index] != NOT_FOUND;
return super.has(index, start);
}
private static long toArrayIndex(Object id) {
if (id instanceof String) {
return toArrayIndex((String) id);
} else if (id instanceof Number) {
return toArrayIndex(((Number) id).doubleValue());
}
return -1;
}
// if id is an array index (ECMA 15.4.0), return the number,
// otherwise return -1L
private static long toArrayIndex(String id) {
long index = toArrayIndex(ScriptRuntime.toNumber(id));
// Assume that ScriptRuntime.toString(index) is the same
// as java.lang.Long.toString(index) for long
if (Long.toString(index).equals(id)) {
return index;
}
return -1;
}
private static long toArrayIndex(double d) {
if (!Double.isNaN(d)) {
long index = ScriptRuntime.toUint32(d);
if (index == d && index != 4294967295L) {
return index;
}
}
return -1;
}
private static int toDenseIndex(Object id) {
long index = toArrayIndex(id);
return 0 <= index && index < Integer.MAX_VALUE ? (int) index : -1;
}
@Override
public void put(String id, Scriptable start, Object value) {
super.put(id, start, value);
if (start == this) {
// If the object is sealed, super will throw exception
long index = toArrayIndex(id);
if (index >= length) {
length = index + 1;
modCount++;
denseOnly = false;
}
}
}
private boolean ensureCapacity(int capacity) {
if (capacity > dense.length) {
if (capacity > MAX_PRE_GROW_SIZE) {
denseOnly = false;
return false;
}
capacity = Math.max(capacity, (int) (dense.length * GROW_FACTOR));
Object[] newDense = new Object[capacity];
System.arraycopy(dense, 0, newDense, 0, dense.length);
Arrays.fill(newDense, dense.length, newDense.length, Scriptable.NOT_FOUND);
dense = newDense;
}
return true;
}
@Override
public void put(int index, Scriptable start, Object value) {
if (start == this
&& !isSealed()
&& dense != null
&& 0 <= index
&& (denseOnly || !isGetterOrSetter(null, index, true))) {
if (!isExtensible() && this.length <= index) {
return;
} else if (index < dense.length) {
dense[index] = value;
if (this.length <= index) {
this.length = (long) index + 1;
this.modCount++;
}
return;
} else if (denseOnly
&& index < dense.length * GROW_FACTOR
&& ensureCapacity(index + 1)) {
dense[index] = value;
this.length = (long) index + 1;
this.modCount++;
return;
} else {
denseOnly = false;
}
}
super.put(index, start, value);
if (start == this && (lengthAttr & READONLY) == 0) {
// only set the array length if given an array index (ECMA 15.4.0)
if (this.length <= index) {
// avoid overflowing index!
this.length = (long) index + 1;
this.modCount++;
}
}
}
@Override
public void delete(int index) {
if (dense != null
&& 0 <= index
&& index < dense.length
&& !isSealed()
&& (denseOnly || !isGetterOrSetter(null, index, true))) {
dense[index] = NOT_FOUND;
} else {
super.delete(index);
}
}
@Override
public Object[] getIds(boolean nonEnumerable, boolean getSymbols) {
Object[] superIds = super.getIds(nonEnumerable, getSymbols);
if (dense == null) {
return superIds;
}
int N = dense.length;
long currentLength = length;
if (N > currentLength) {
N = (int) currentLength;
}
if (N == 0) {
return superIds;
}
int superLength = superIds.length;
Object[] ids = new Object[N + superLength];
int presentCount = 0;
for (int i = 0; i != N; ++i) {
// Replace existing elements by their indexes
if (dense[i] != NOT_FOUND) {
ids[presentCount] = Integer.valueOf(i);
++presentCount;
}
}
if (presentCount != N) {
// dense contains deleted elems, need to shrink the result
Object[] tmp = new Object[presentCount + superLength];
System.arraycopy(ids, 0, tmp, 0, presentCount);
ids = tmp;
}
System.arraycopy(superIds, 0, ids, presentCount, superLength);
return ids;
}
public List getIndexIds() {
Object[] ids = getIds();
List indices = new ArrayList(ids.length);
for (Object id : ids) {
int int32Id = ScriptRuntime.toInt32(id);
if (int32Id >= 0
&& ScriptRuntime.toString(int32Id).equals(ScriptRuntime.toString(id))) {
indices.add(Integer.valueOf(int32Id));
}
}
return indices;
}
@Override
public Object getDefaultValue(Class> hint) {
if (hint == ScriptRuntime.NumberClass) {
Context cx = Context.getContext();
if (cx.getLanguageVersion() == Context.VERSION_1_2) return Long.valueOf(length);
}
return super.getDefaultValue(hint);
}
private ScriptableObject defaultIndexPropertyDescriptor(Object value) {
Scriptable scope = getParentScope();
if (scope == null) scope = this;
ScriptableObject desc = new NativeObject();
ScriptRuntime.setBuiltinProtoAndParent(desc, scope, TopLevel.Builtins.Object);
desc.defineProperty("value", value, EMPTY);
desc.defineProperty("writable", Boolean.TRUE, EMPTY);
desc.defineProperty("enumerable", Boolean.TRUE, EMPTY);
desc.defineProperty("configurable", Boolean.TRUE, EMPTY);
return desc;
}
@Override
public int getAttributes(int index) {
if (dense != null && index >= 0 && index < dense.length && dense[index] != NOT_FOUND) {
return EMPTY;
}
return super.getAttributes(index);
}
@Override
protected ScriptableObject getOwnPropertyDescriptor(Context cx, Object id) {
if (dense != null) {
int index = toDenseIndex(id);
if (0 <= index && index < dense.length && dense[index] != NOT_FOUND) {
Object value = dense[index];
return defaultIndexPropertyDescriptor(value);
}
}
return super.getOwnPropertyDescriptor(cx, id);
}
@Override
protected void defineOwnProperty(
Context cx, Object id, ScriptableObject desc, boolean checkValid) {
long index = toArrayIndex(id);
if (index >= length) {
length = index + 1;
modCount++;
}
if (index != -1 && dense != null) {
Object[] values = dense;
dense = null;
denseOnly = false;
for (int i = 0; i < values.length; i++) {
if (values[i] != NOT_FOUND) {
if (!isExtensible()) {
// Force creating a slot, before calling .put(...) on the next line, which
// would otherwise fail on a array on which preventExtensions() has been
// called
setAttributes(i, 0);
}
put(i, this, values[i]);
}
}
}
super.defineOwnProperty(cx, id, desc, checkValid);
if (id instanceof String && ((String) id).equals("length")) {
lengthAttr =
getAttributes("length"); // Update cached attributes value for length property
}
}
/** See ECMA 15.4.1,2 */
private static Object jsConstructor(Context cx, Scriptable scope, Object[] args) {
if (args.length == 0) return new NativeArray(0);
// Only use 1 arg as first element for version 1.2; for
// any other version (including 1.3) follow ECMA and use it as
// a length.
if (cx.getLanguageVersion() == Context.VERSION_1_2) {
return new NativeArray(args);
}
Object arg0 = args[0];
if (args.length > 1 || !(arg0 instanceof Number)) {
return new NativeArray(args);
}
long len = ScriptRuntime.toUint32(arg0);
if (len != ((Number) arg0).doubleValue()) {
String msg = ScriptRuntime.getMessageById("msg.arraylength.bad");
throw ScriptRuntime.rangeError(msg);
}
return new NativeArray(len);
}
private static Scriptable callConstructorOrCreateArray(
Context cx, Scriptable scope, Scriptable arg, long length, boolean lengthAlways) {
Scriptable result = null;
if (arg instanceof Function) {
try {
final Object[] args =
(lengthAlways || (length > 0))
? new Object[] {Long.valueOf(length)}
: ScriptRuntime.emptyArgs;
result = ((Function) arg).construct(cx, scope, args);
} catch (EcmaError ee) {
if (!"TypeError".equals(ee.getName())) {
throw ee;
}
// If we get here then it is likely that the function we called is not really
// a constructor. Unfortunately there's no better way to tell in Rhino right now.
}
}
if (result == null) {
// "length" below is really a hint so don't worry if it's really large
result = cx.newArray(scope, (length > Integer.MAX_VALUE) ? 0 : (int) length);
}
return result;
}
private static Object js_from(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
final Scriptable items =
ScriptRuntime.toObject(scope, (args.length >= 1) ? args[0] : Undefined.instance);
Object mapArg = (args.length >= 2) ? args[1] : Undefined.instance;
Scriptable thisArg = Undefined.SCRIPTABLE_UNDEFINED;
final boolean mapping = !Undefined.isUndefined(mapArg);
Function mapFn = null;
if (mapping) {
if (!(mapArg instanceof Function)) {
throw ScriptRuntime.typeErrorById("msg.map.function.not");
}
mapFn = (Function) mapArg;
if (args.length >= 3) {
thisArg = ensureScriptable(args[2]);
}
}
Object iteratorProp = ScriptableObject.getProperty(items, SymbolKey.ITERATOR);
if (!(items instanceof NativeArray)
&& (iteratorProp != Scriptable.NOT_FOUND)
&& !Undefined.isUndefined(iteratorProp)) {
final Object iterator = ScriptRuntime.callIterator(items, cx, scope);
if (!Undefined.isUndefined(iterator)) {
final Scriptable result =
callConstructorOrCreateArray(cx, scope, thisObj, 0, false);
long k = 0;
try (IteratorLikeIterable it = new IteratorLikeIterable(cx, scope, iterator)) {
for (Object temp : it) {
if (mapping) {
temp =
mapFn.call(
cx,
scope,
thisArg,
new Object[] {temp, Long.valueOf(k)});
}
defineElem(cx, result, k, temp);
k++;
}
}
setLengthProperty(cx, result, k);
return result;
}
}
final long length = getLengthProperty(cx, items);
final Scriptable result = callConstructorOrCreateArray(cx, scope, thisObj, length, true);
for (long k = 0; k < length; k++) {
Object temp = getElem(cx, items, k);
if (mapping) {
temp = mapFn.call(cx, scope, thisArg, new Object[] {temp, Long.valueOf(k)});
}
defineElem(cx, result, k, temp);
}
setLengthProperty(cx, result, length);
return result;
}
private static Object js_of(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
final Scriptable result =
callConstructorOrCreateArray(cx, scope, thisObj, args.length, true);
for (int i = 0; i < args.length; i++) {
defineElem(cx, result, i, args[i]);
}
setLengthProperty(cx, result, args.length);
return result;
}
public long getLength() {
return length;
}
/** @deprecated Use {@link #getLength()} instead. */
@Deprecated
public long jsGet_length() {
return getLength();
}
/**
* Change the value of the internal flag that determines whether all storage is handed by a
* dense backing array rather than an associative store.
*
* @param denseOnly new value for denseOnly flag
* @throws IllegalArgumentException if an attempt is made to enable denseOnly after it was
* disabled; NativeArray code is not written to handle switching back to a dense
* representation
*/
void setDenseOnly(boolean denseOnly) {
if (denseOnly && !this.denseOnly) throw new IllegalArgumentException();
this.denseOnly = denseOnly;
}
private void setLength(Object val) {
/* XXX do we satisfy this?
* 15.4.5.1 [[Put]](P, V):
* 1. Call the [[CanPut]] method of A with name P.
* 2. If Result(1) is false, return.
* ?
*/
if ((lengthAttr & READONLY) != 0) {
return;
}
double d = ScriptRuntime.toNumber(val);
long longVal = ScriptRuntime.toUint32(d);
if (longVal != d) {
String msg = ScriptRuntime.getMessageById("msg.arraylength.bad");
throw ScriptRuntime.rangeError(msg);
}
if (denseOnly) {
if (longVal < length) {
// downcast okay because denseOnly
Arrays.fill(dense, (int) longVal, dense.length, NOT_FOUND);
length = longVal;
modCount++;
return;
} else if (longVal < MAX_PRE_GROW_SIZE
&& longVal < (length * GROW_FACTOR)
&& ensureCapacity((int) longVal)) {
length = longVal;
modCount++;
return;
} else {
denseOnly = false;
}
}
if (longVal < length) {
// remove all properties between longVal and length
if (length - longVal > 0x1000) {
// assume that the representation is sparse
Object[] e = getIds(); // will only find in object itself
for (int i = 0; i < e.length; i++) {
Object id = e[i];
if (id instanceof String) {
// > MAXINT will appear as string
String strId = (String) id;
long index = toArrayIndex(strId);
if (index >= longVal) delete(strId);
} else {
int index = ((Integer) id).intValue();
if (index >= longVal) delete(index);
}
}
} else {
// assume a dense representation
for (long i = longVal; i < length; i++) {
deleteElem(this, i);
}
}
}
length = longVal;
modCount++;
}
/* Support for generic Array-ish objects. Most of the Array
* functions try to be generic; anything that has a length
* property is assumed to be an array.
* getLengthProperty returns 0 if obj does not have the length property
* or its value is not convertible to a number.
*/
static long getLengthProperty(Context cx, Scriptable obj) {
// These will give numeric lengths within Uint32 range.
if (obj instanceof NativeString) {
return ((NativeString) obj).getLength();
}
if (obj instanceof NativeArray) {
return ((NativeArray) obj).getLength();
}
if (obj instanceof XMLObject) {
Callable lengthFunc = (Callable) ((XMLObject) obj).get("length", obj);
return ((Number) lengthFunc.call(cx, obj, obj, ScriptRuntime.emptyArgs)).longValue();
}
Object len = ScriptableObject.getProperty(obj, "length");
if (len == Scriptable.NOT_FOUND) {
// toUint32(undefined) == 0
return 0;
}
double doubleLen = ScriptRuntime.toNumber(len);
// ToLength
if (doubleLen > NativeNumber.MAX_SAFE_INTEGER) {
return (long) NativeNumber.MAX_SAFE_INTEGER;
}
if (doubleLen < 0) {
return 0;
}
return (long) doubleLen;
}
private static Object setLengthProperty(Context cx, Scriptable target, long length) {
Object len = ScriptRuntime.wrapNumber(length);
ScriptableObject.putProperty(target, "length", len);
return len;
}
/* Utility functions to encapsulate index > Integer.MAX_VALUE
* handling. Also avoids unnecessary object creation that would
* be necessary to use the general ScriptRuntime.get/setElem
* functions... though this is probably premature optimization.
*/
private static void deleteElem(Scriptable target, long index) {
int i = (int) index;
if (i == index) {
target.delete(i);
} else {
target.delete(Long.toString(index));
}
}
private static Object getElem(Context cx, Scriptable target, long index) {
Object elem = getRawElem(target, index);
return (elem != Scriptable.NOT_FOUND ? elem : Undefined.instance);
}
// same as getElem, but without converting NOT_FOUND to undefined
private static Object getRawElem(Scriptable target, long index) {
if (index > Integer.MAX_VALUE) {
return ScriptableObject.getProperty(target, Long.toString(index));
}
return ScriptableObject.getProperty(target, (int) index);
}
private static void defineElem(Context cx, Scriptable target, long index, Object value) {
if (index > Integer.MAX_VALUE) {
String id = Long.toString(index);
target.put(id, target, value);
} else {
target.put((int) index, target, value);
}
}
private static void setElem(Context cx, Scriptable target, long index, Object value) {
if (index > Integer.MAX_VALUE) {
String id = Long.toString(index);
ScriptableObject.putProperty(target, id, value);
} else {
ScriptableObject.putProperty(target, (int) index, value);
}
}
// Similar as setElem(), but triggers deleteElem() if value is NOT_FOUND
private static void setRawElem(Context cx, Scriptable target, long index, Object value) {
if (value == NOT_FOUND) {
deleteElem(target, index);
} else {
setElem(cx, target, index, value);
}
}
private static String toStringHelper(
Context cx, Scriptable scope, Scriptable thisObj, boolean toSource, boolean toLocale) {
Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);
/* It's probably redundant to handle long lengths in this
* function; StringBuilders are limited to 2^31 in java.
*/
long length = getLengthProperty(cx, o);
StringBuilder result = new StringBuilder(256);
// whether to return '4,unquoted,5' or '[4, "quoted", 5]'
String separator;
if (toSource) {
result.append('[');
separator = ", ";
} else {
separator = ",";
}
boolean haslast = false;
long i = 0;
boolean toplevel, iterating;
if (cx.iterating == null) {
toplevel = true;
iterating = false;
cx.iterating = new ObjToIntMap(31);
} else {
toplevel = false;
iterating = cx.iterating.has(o);
}
// Make sure cx.iterating is set to null when done
// so we don't leak memory
try {
if (!iterating) {
// stop recursion
cx.iterating.put(o, 0);
// make toSource print null and undefined values in recent versions
boolean skipUndefinedAndNull =
!toSource || cx.getLanguageVersion() < Context.VERSION_1_5;
for (i = 0; i < length; i++) {
if (i > 0) result.append(separator);
Object elem = getRawElem(o, i);
if (elem == NOT_FOUND
|| (skipUndefinedAndNull
&& (elem == null || elem == Undefined.instance))) {
haslast = false;
continue;
}
haslast = true;
if (toSource) {
result.append(ScriptRuntime.uneval(cx, scope, elem));
} else if (elem instanceof String) {
result.append((String) elem);
} else {
if (toLocale) {
Callable fun;
Scriptable funThis;
fun =
ScriptRuntime.getPropFunctionAndThis(
elem, "toLocaleString", cx, scope);
funThis = ScriptRuntime.lastStoredScriptable(cx);
elem = fun.call(cx, scope, funThis, ScriptRuntime.emptyArgs);
}
result.append(ScriptRuntime.toString(elem));
}
}
// processing of thisObj done, remove it from the recursion detector
// to allow thisObj to be again in the array later on
cx.iterating.remove(o);
}
} finally {
if (toplevel) {
cx.iterating = null;
}
}
if (toSource) {
// for [,,].length behavior; we want toString to be symmetric.
if (!haslast && i > 0) result.append(", ]");
else result.append(']');
}
return result.toString();
}
/** See ECMA 15.4.4.3 */
private static String js_join(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);
long llength = getLengthProperty(cx, o);
int length = (int) llength;
if (llength != length) {
throw Context.reportRuntimeErrorById(
"msg.arraylength.too.big", String.valueOf(llength));
}
// if no args, use "," as separator
String separator =
(args.length < 1 || args[0] == Undefined.instance)
? ","
: ScriptRuntime.toString(args[0]);
if (o instanceof NativeArray) {
NativeArray na = (NativeArray) o;
if (na.denseOnly) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i != 0) {
sb.append(separator);
}
if (i < na.dense.length) {
Object temp = na.dense[i];
if (temp != null
&& temp != Undefined.instance
&& temp != Scriptable.NOT_FOUND) {
sb.append(ScriptRuntime.toString(temp));
}
}
}
return sb.toString();
}
}
if (length == 0) {
return "";
}
String[] buf = new String[length];
int total_size = 0;
for (int i = 0; i != length; i++) {
Object temp = getElem(cx, o, i);
if (temp != null && temp != Undefined.instance) {
String str = ScriptRuntime.toString(temp);
total_size += str.length();
buf[i] = str;
}
}
total_size += (length - 1) * separator.length();
StringBuilder sb = new StringBuilder(total_size);
for (int i = 0; i != length; i++) {
if (i != 0) {
sb.append(separator);
}
String str = buf[i];
if (str != null) {
// str == null for undefined or null
sb.append(str);
}
}
return sb.toString();
}
/** See ECMA 15.4.4.4 */
private static Scriptable js_reverse(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);
if (o instanceof NativeArray) {
NativeArray na = (NativeArray) o;
if (na.denseOnly) {
for (int i = 0, j = ((int) na.length) - 1; i < j; i++, j--) {
Object temp = na.dense[i];
na.dense[i] = na.dense[j];
na.dense[j] = temp;
}
return o;
}
}
long len = getLengthProperty(cx, o);
long half = len / 2;
for (long i = 0; i < half; i++) {
long j = len - i - 1;
Object temp1 = getRawElem(o, i);
Object temp2 = getRawElem(o, j);
setRawElem(cx, o, i, temp2);
setRawElem(cx, o, j, temp1);
}
return o;
}
/** See ECMA 15.4.4.5 */
private static Scriptable js_sort(
final Context cx,
final Scriptable scope,
final Scriptable thisObj,
final Object[] args) {
Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj);
final Comparator