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 Show documentation
Show all versions of rhino Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically
embedded into Java applications to provide scripting to end users.
The newest version!
/* -*- 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(Context cx, Scriptable scope, boolean sealed) {
NativeArray obj = new NativeArray(0);
IdFunctionObject constructor = obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
ScriptRuntimeES6.addSymbolSpecies(cx, scope, constructor);
}
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) {
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;
case Id_at:
arity = 1;
s = "at";
break;
case Id_flat:
arity = 0;
s = "flat";
break;
case Id_flatMap:
arity = 1;
s = "flatMap";
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];
System.arraycopy(args, 1, newArgs, 0, newArgs.length);
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_at:
return js_at(cx, scope, thisObj, args);
case Id_flat:
return js_flat(cx, scope, thisObj, args);
case Id_flatMap:
return js_flatMap(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:
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 void setPrototype(Scriptable p) {
super.setPrototype(p);
if (!(p instanceof NativeArray)) {
setDenseOnly(false);
}
}
@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);
}
@Override
public boolean has(Symbol key, Scriptable start) {
if (SymbolKey.ITERATOR.equals(key)) {
return super.has("values", start);
}
return super.has(key, start);
}
@Override
public Object get(Symbol key, Scriptable start) {
if (SymbolKey.ITERATOR.equals(key)) {
return super.get("values", start);
}
return super.get(key, start);
}
@Override
public void put(Symbol key, Scriptable start, Object value) {
if (SymbolKey.ITERATOR.equals(key)) {
super.put("values", start, value);
}
super.put(key, start, value);
}
@Override
public void delete(Symbol key) {
if (SymbolKey.ITERATOR.equals(key)) {
super.delete("values");
}
super.delete(key);
}
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 ("length".equals(id)) {
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);
if (cx.getLanguageVersion() >= Context.VERSION_ES6 && result instanceof ScriptableObject) {
ScriptableObject desc = ScriptableObject.buildDataDescriptor(result, null, EMPTY);
for (int i = 0; i < args.length; i++) {
desc.put("value", desc, args[i]);
((ScriptableObject) result).defineOwnProperty(cx, i, desc);
}
} else {
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 (Object id : e) {
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) 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 defineElemOrThrow(Context cx, Scriptable target, long index, Object value) {
if (index > NativeNumber.MAX_SAFE_INTEGER) {
throw ScriptRuntime.typeErrorById("msg.arraylength.too.big", String.valueOf(index));
} else {
defineElem(cx, target, index, 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();
}
private static Function getCallbackArg(Context cx, Object callbackArg) {
if (!(callbackArg instanceof Function)) {
throw ScriptRuntime.notFunctionError(callbackArg);
}
if (cx.getLanguageVersion() >= Context.VERSION_ES6
&& (callbackArg instanceof NativeRegExp)) {
// Previously, it was allowed to pass RegExp instance as a callback (it implements
// Function)
// But according to ES2015 21.2.6 Properties of RegExp Instances:
// > RegExp instances are ordinary objects that inherit properties from the RegExp
// prototype object.
// > RegExp instances have internal slots [[RegExpMatcher]], [[OriginalSource]], and
// [[OriginalFlags]].
// so, no [[Call]] for RegExp-s
throw ScriptRuntime.notFunctionError(callbackArg);
}
Function f = (Function) callbackArg;
return f;
}
/** 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
© 2015 - 2024 Weber Informatics LLC | Privacy Policy