/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Norris Boyd * Mike McCabe * Igor Bukanov * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package org.mozilla.javascript; /** * This class implements the Array native object. * @author Norris Boyd * @author Mike McCabe */ public class NativeArray extends IdScriptableObject { static final long serialVersionUID = 7331366857676127338L; /* * Optimization possibilities and open issues: * - Long vs. double schizophrenia. I suspect it might be better * to use double throughout. * - Most array operations go through getElem or setElem (defined * in this file) to handle the full 2^32 range; it might be faster * to have versions of most of the loops in this file for the * (infinitely more common) case of indices < 2^31. * - 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 = new Object(); private static final Integer NEGATIVE_ONE = new Integer(-1); static void init(Scriptable scope, boolean sealed) { NativeArray obj = new NativeArray(); obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); } /** * Zero-parameter constructor: just used to create Array.prototype */ private NativeArray() { dense = null; this.length = 0; } public NativeArray(long length) { int intLength = (int) length; if (intLength == length && intLength > 0) { if (intLength > maximumDenseLength) intLength = maximumDenseLength; dense = new Object[intLength]; for (int i=0; i < intLength; i++) dense[i] = NOT_FOUND; } this.length = length; } public NativeArray(Object[] array) { dense = array; this.length = array.length; } public String getClassName() { return "Array"; } private static final int Id_length = 1, MAX_INSTANCE_ID = 1; protected int getMaxInstanceId() { return MAX_INSTANCE_ID; } protected int findInstanceIdInfo(String s) { if (s.equals("length")) { return instanceIdInfo(DONTENUM | PERMANENT, Id_length); } return super.findInstanceIdInfo(s); } protected String getInstanceIdName(int id) { if (id == Id_length) { return "length"; } return super.getInstanceIdName(id); } protected Object getInstanceIdValue(int id) { if (id == Id_length) { return ScriptRuntime.wrapNumber(length); } return super.getInstanceIdValue(id); } protected void setInstanceIdValue(int id, Object value) { if (id == Id_length) { setLength(value); return; } super.setInstanceIdValue(id, value); } protected void initPrototypeId(int id) { String s; int arity; switch (id) { case Id_constructor: arity=1; s="constructor"; break; case Id_toString: arity=0; s="toString"; break; case Id_toLocaleString: arity=1; 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=1; s="pop"; break; case Id_shift: arity=1; s="shift"; break; case Id_unshift: arity=1; s="unshift"; break; case Id_splice: arity=1; s="splice"; break; case Id_concat: arity=1; s="concat"; break; case Id_slice: arity=1; 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; default: throw new IllegalArgumentException(String.valueOf(id)); } initPrototypeMethod(ARRAY_TAG, id, s, arity); } 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(); switch (id) { 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, thisObj, args); case Id_reverse: return js_reverse(cx, thisObj, args); case Id_sort: return js_sort(cx, scope, thisObj, args); case Id_push: return js_push(cx, thisObj, args); case Id_pop: return js_pop(cx, thisObj, args); case Id_shift: return js_shift(cx, thisObj, args); case Id_unshift: return js_unshift(cx, 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, thisObj, args); case Id_indexOf: return indexOfHelper(cx, thisObj, args, false); case Id_lastIndexOf: return indexOfHelper(cx, thisObj, args, true); case Id_every: case Id_filter: case Id_forEach: case Id_map: case Id_some: return iterativeMethod(cx, id, scope, thisObj, args); } throw new IllegalArgumentException(String.valueOf(id)); } public Object get(int index, Scriptable start) { if (dense != null && 0 <= index && index < dense.length) return dense[index]; return super.get(index, start); } public boolean has(int index, Scriptable start) { if (dense != null && 0 <= index && index < dense.length) return dense[index] != NOT_FOUND; return super.has(index, start); } // if id is an array index (ECMA 15.4.0), return the number, // otherwise return -1L private static long toArrayIndex(String id) { double d = ScriptRuntime.toNumber(id); if (d == d) { long index = ScriptRuntime.toUint32(d); if (index == d && index != 4294967295L) { // 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; } 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; } } } public void put(int index, Scriptable start, Object value) { if (start == this && !isSealed() && dense != null && 0 <= index && index < dense.length) { // If start == this && sealed, super will throw exception dense[index] = value; } else { super.put(index, start, value); } if (start == this) { // 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; } } } public void delete(int index) { if (!isSealed() && dense != null && 0 <= index && index < dense.length) { dense[index] = NOT_FOUND; } else { super.delete(index); } } public Object[] getIds() { Object[] superIds = super.getIds(); 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]; // Make a copy of dense to be immune to removing // of array elems from other thread when calculating presentCount System.arraycopy(dense, 0, ids, 0, N); int presentCount = 0; for (int i = 0; i != N; ++i) { // Replace existing elements by their indexes if (ids[i] != NOT_FOUND) { ids[presentCount] = new Integer(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 Object getDefaultValue(Class hint) { if (hint == ScriptRuntime.NumberClass) { Context cx = Context.getContext(); if (cx.getLanguageVersion() == Context.VERSION_1_2) return new Long(length); } return super.getDefaultValue(hint); } /** * See ECMA 15.4.1,2 */ private static Object jsConstructor(Context cx, Scriptable scope, Object[] args) { if (args.length == 0) return new NativeArray(); // 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); } else { Object arg0 = args[0]; if (args.length > 1 || !(arg0 instanceof Number)) { return new NativeArray(args); } else { long len = ScriptRuntime.toUint32(arg0); if (len != ((Number)arg0).doubleValue()) throw Context.reportRuntimeError0("msg.arraylength.bad"); return new NativeArray(len); } } } public long getLength() { return length; } /** @deprecated Use {@link #getLength()} instead. */ public long jsGet_length() { return getLength(); } 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. * ? */ double d = ScriptRuntime.toNumber(val); long longVal = ScriptRuntime.toUint32(d); if (longVal != d) throw Context.reportRuntimeError0("msg.arraylength.bad"); 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; } /* 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 both give numeric lengths within Uint32 range. if (obj instanceof NativeString) { return ((NativeString)obj).getLength(); } else if (obj instanceof NativeArray) { return ((NativeArray)obj).getLength(); } return ScriptRuntime.toUint32( ScriptRuntime.getObjectProp(obj, "length", cx)); } private static Object setLengthProperty(Context cx, Scriptable target, long length) { return ScriptRuntime.setObjectProp( target, "length", ScriptRuntime.wrapNumber(length), cx); } /* 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) { if (index > Integer.MAX_VALUE) { String id = Long.toString(index); return ScriptRuntime.getObjectProp(target, id, cx); } else { return ScriptRuntime.getObjectIndex(target, (int)index, cx); } } private static void setElem(Context cx, Scriptable target, long index, Object value) { if (index > Integer.MAX_VALUE) { String id = Long.toString(index); ScriptRuntime.setObjectProp(target, id, value, cx); } else { ScriptRuntime.setObjectIndex(target, (int)index, value, cx); } } private static String toStringHelper(Context cx, Scriptable scope, Scriptable thisObj, boolean toSource, boolean toLocale) { /* It's probably redundant to handle long lengths in this * function; StringBuffers are limited to 2^31 in java. */ long length = getLengthProperty(cx, thisObj); StringBuffer result = new StringBuffer(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(thisObj); } // Make sure cx.iterating is set to null when done // so we don't leak memory try { if (!iterating) { cx.iterating.put(thisObj, 0); // stop recursion. for (i = 0; i < length; i++) { if (i > 0) result.append(separator); Object elem = getElem(cx, thisObj, i); if (elem == null || elem == Undefined.instance) { haslast = false; continue; } haslast = true; if (toSource) { result.append(ScriptRuntime.uneval(cx, scope, elem)); } else if (elem instanceof String) { String s = (String)elem; if (toSource) { result.append('\"'); result.append(ScriptRuntime.escapeString(s)); result.append('\"'); } else { result.append(s); } } else { if (toLocale) { Callable fun; Scriptable funThis; fun = ScriptRuntime.getPropFunctionAndThis( elem, "toLocaleString", cx); funThis = ScriptRuntime.lastStoredScriptable(cx); elem = fun.call(cx, scope, funThis, ScriptRuntime.emptyArgs); } result.append(ScriptRuntime.toString(elem)); } } } } 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 thisObj, Object[] args) { String separator; long llength = getLengthProperty(cx, thisObj); int length = (int)llength; if (llength != length) { throw Context.reportRuntimeError1( "msg.arraylength.too.big", String.valueOf(llength)); } // if no args, use "," as separator if (args.length < 1 || args[0] == Undefined.instance) { separator = ","; } else { separator = ScriptRuntime.toString(args[0]); } if (length == 0) { return ""; } String[] buf = new String[length]; int total_size = 0; for (int i = 0; i != length; i++) { Object temp = getElem(cx, thisObj, 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(); StringBuffer sb = new StringBuffer(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 thisObj, Object[] args) { long len = getLengthProperty(cx, thisObj); long half = len / 2; for(long i=0; i < half; i++) { long j = len - i - 1; Object temp1 = getElem(cx, thisObj, i); Object temp2 = getElem(cx, thisObj, j); setElem(cx, thisObj, i, temp2); setElem(cx, thisObj, j, temp1); } return thisObj; } /** * See ECMA 15.4.4.5 */ private static Scriptable js_sort(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { long length = getLengthProperty(cx, thisObj); if (length <= 1) { return thisObj; } Object compare; Object[] cmpBuf; if (args.length > 0 && Undefined.instance != args[0]) { // sort with given compare function compare = args[0]; cmpBuf = new Object[2]; // Buffer for cmp arguments } else { // sort with default compare compare = null; cmpBuf = null; } // Should we use the extended sort function, or the faster one? if (length >= Integer.MAX_VALUE) { heapsort_extended(cx, scope, thisObj, length, compare, cmpBuf); } else { int ilength = (int)length; // copy the JS array into a working array, so it can be // sorted cheaply. Object[] working = new Object[ilength]; for (int i = 0; i != ilength; ++i) { working[i] = getElem(cx, thisObj, i); } heapsort(cx, scope, working, ilength, compare, cmpBuf); // copy the working array back into thisObj for (int i = 0; i != ilength; ++i) { setElem(cx, thisObj, i, working[i]); } } return thisObj; } // Return true only if x > y private static boolean isBigger(Context cx, Scriptable scope, Object x, Object y, Object cmp, Object[] cmpBuf) { if (cmp == null) { if (cmpBuf != null) Kit.codeBug(); } else { if (cmpBuf == null || cmpBuf.length != 2) Kit.codeBug(); } Object undef = Undefined.instance; // sort undefined to end if (undef == y) { return false; // x can not be bigger then undef } else if (undef == x) { return true; // y != undef here, so x > y } if (cmp == null) { // if no cmp function supplied, sort lexicographically String a = ScriptRuntime.toString(x); String b = ScriptRuntime.toString(y); return a.compareTo(b) > 0; } else { // assemble args and call supplied JS cmp function cmpBuf[0] = x; cmpBuf[1] = y; Callable fun = ScriptRuntime.getValueFunctionAndThis(cmp, cx); Scriptable funThis = ScriptRuntime.lastStoredScriptable(cx); Object ret = fun.call(cx, scope, funThis, cmpBuf); double d = ScriptRuntime.toNumber(ret); // XXX what to do when cmp function returns NaN? ECMA states // that it's then not a 'consistent compararison function'... but // then what do we do? Back out and start over with the generic // cmp function when we see a NaN? Throw an error? // for now, just ignore it: return d > 0; } } /** Heapsort implementation. * See "Introduction to Algorithms" by Cormen, Leiserson, Rivest for details. * Adjusted for zero based indexes. */ private static void heapsort(Context cx, Scriptable scope, Object[] array, int length, Object cmp, Object[] cmpBuf) { if (length <= 1) Kit.codeBug(); // Build heap for (int i = length / 2; i != 0;) { --i; Object pivot = array[i]; heapify(cx, scope, pivot, array, i, length, cmp, cmpBuf); } // Sort heap for (int i = length; i != 1;) { --i; Object pivot = array[i]; array[i] = array[0]; heapify(cx, scope, pivot, array, 0, i, cmp, cmpBuf); } } /** pivot and child heaps of i should be made into heap starting at i, * original array[i] is never used to have less array access during sorting. */ private static void heapify(Context cx, Scriptable scope, Object pivot, Object[] array, int i, int end, Object cmp, Object[] cmpBuf) { for (;;) { int child = i * 2 + 1; if (child >= end) { break; } Object childVal = array[child]; if (child + 1 < end) { Object nextVal = array[child + 1]; if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) { ++child; childVal = nextVal; } } if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) { break; } array[i] = childVal; i = child; } array[i] = pivot; } /** Version of heapsort that call getElem/setElem on target to query/assign * array elements instead of Java array access */ private static void heapsort_extended(Context cx, Scriptable scope, Scriptable target, long length, Object cmp, Object[] cmpBuf) { if (length <= 1) Kit.codeBug(); // Build heap for (long i = length / 2; i != 0;) { --i; Object pivot = getElem(cx, target, i); heapify_extended(cx, scope, pivot, target, i, length, cmp, cmpBuf); } // Sort heap for (long i = length; i != 1;) { --i; Object pivot = getElem(cx, target, i); setElem(cx, target, i, getElem(cx, target, 0)); heapify_extended(cx, scope, pivot, target, 0, i, cmp, cmpBuf); } } private static void heapify_extended(Context cx, Scriptable scope, Object pivot, Scriptable target, long i, long end, Object cmp, Object[] cmpBuf) { for (;;) { long child = i * 2 + 1; if (child >= end) { break; } Object childVal = getElem(cx, target, child); if (child + 1 < end) { Object nextVal = getElem(cx, target, child + 1); if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) { ++child; childVal = nextVal; } } if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) { break; } setElem(cx, target, i, childVal); i = child; } setElem(cx, target, i, pivot); } /** * Non-ECMA methods. */ private static Object js_push(Context cx, Scriptable thisObj, Object[] args) { long length = getLengthProperty(cx, thisObj); for (int i = 0; i < args.length; i++) { setElem(cx, thisObj, length + i, args[i]); } length += args.length; Object lengthObj = setLengthProperty(cx, thisObj, length); /* * If JS1.2, follow Perl4 by returning the last thing pushed. * Otherwise, return the new array length. */ if (cx.getLanguageVersion() == Context.VERSION_1_2) // if JS1.2 && no arguments, return undefined. return args.length == 0 ? Undefined.instance : args[args.length - 1]; else return lengthObj; } private static Object js_pop(Context cx, Scriptable thisObj, Object[] args) { Object result; long length = getLengthProperty(cx, thisObj); if (length > 0) { length--; // Get the to-be-deleted property's value. result = getElem(cx, thisObj, length); // We don't need to delete the last property, because // setLength does that for us. } else { result = Undefined.instance; } // necessary to match js even when length < 0; js pop will give a // length property to any target it is called on. setLengthProperty(cx, thisObj, length); return result; } private static Object js_shift(Context cx, Scriptable thisObj, Object[] args) { Object result; long length = getLengthProperty(cx, thisObj); if (length > 0) { long i = 0; length--; // Get the to-be-deleted property's value. result = getElem(cx, thisObj, i); /* * Slide down the array above the first element. Leave i * set to point to the last element. */ if (length > 0) { for (i = 1; i <= length; i++) { Object temp = getElem(cx, thisObj, i); setElem(cx, thisObj, i - 1, temp); } } // We don't need to delete the last property, because // setLength does that for us. } else { result = Undefined.instance; } setLengthProperty(cx, thisObj, length); return result; } private static Object js_unshift(Context cx, Scriptable thisObj, Object[] args) { long length = getLengthProperty(cx, thisObj); int argc = args.length; if (args.length > 0) { /* Slide up the array to make room for args at the bottom */ if (length > 0) { for (long last = length - 1; last >= 0; last--) { Object temp = getElem(cx, thisObj, last); setElem(cx, thisObj, last + argc, temp); } } /* Copy from argv to the bottom of the array. */ for (int i = 0; i < args.length; i++) { setElem(cx, thisObj, i, args[i]); } /* Follow Perl by returning the new array length. */ length += args.length; return setLengthProperty(cx, thisObj, length); } return ScriptRuntime.wrapNumber(length); } private static Object js_splice(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { /* create an empty Array to return. */ scope = getTopLevelScope(scope); Object result = ScriptRuntime.newObject(cx, scope, "Array", null); int argc = args.length; if (argc == 0) return result; long length = getLengthProperty(cx, thisObj); /* Convert the first argument into a starting index. */ long begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length); argc--; /* Convert the second argument into count */ long count; if (args.length == 1) { count = length - begin; } else { double dcount = ScriptRuntime.toInteger(args[1]); if (dcount < 0) { count = 0; } else if (dcount > (length - begin)) { count = length - begin; } else { count = (long)dcount; } argc--; } long end = begin + count; /* If there are elements to remove, put them into the return value. */ if (count != 0) { if (count == 1 && (cx.getLanguageVersion() == Context.VERSION_1_2)) { /* * JS lacks "list context", whereby in Perl one turns the * single scalar that's spliced out into an array just by * assigning it to @single instead of $single, or by using it * as Perl push's first argument, for instance. * * JS1.2 emulated Perl too closely and returned a non-Array for * the single-splice-out case, requiring callers to test and * wrap in [] if necessary. So JS1.3, default, and other * versions all return an array of length 1 for uniformity. */ result = getElem(cx, thisObj, begin); } else { for (long last = begin; last != end; last++) { Scriptable resultArray = (Scriptable)result; Object temp = getElem(cx, thisObj, last); setElem(cx, resultArray, last - begin, temp); } } } else if (count == 0 && cx.getLanguageVersion() == Context.VERSION_1_2) { /* Emulate C JS1.2; if no elements are removed, return undefined. */ result = Undefined.instance; } /* Find the direction (up or down) to copy and make way for argv. */ long delta = argc - count; if (delta > 0) { for (long last = length - 1; last >= end; last--) { Object temp = getElem(cx, thisObj, last); setElem(cx, thisObj, last + delta, temp); } } else if (delta < 0) { for (long last = end; last < length; last++) { Object temp = getElem(cx, thisObj, last); setElem(cx, thisObj, last + delta, temp); } } /* Copy from argv into the hole to complete the splice. */ int argoffset = args.length - argc; for (int i = 0; i < argc; i++) { setElem(cx, thisObj, begin + i, args[i + argoffset]); } /* Update length in case we deleted elements from the end. */ setLengthProperty(cx, thisObj, length + delta); return result; } /* * See Ecma 262v3 15.4.4.4 */ private static Scriptable js_concat(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { // create an empty Array to return. scope = getTopLevelScope(scope); Function ctor = ScriptRuntime.getExistingCtor(cx, scope, "Array"); Scriptable result = ctor.construct(cx, scope, ScriptRuntime.emptyArgs); long length; long slot = 0; /* Put the target in the result array; only add it as an array * if it looks like one. */ if (ScriptRuntime.instanceOf(thisObj, ctor, cx)) { length = getLengthProperty(cx, thisObj); // Copy from the target object into the result for (slot = 0; slot < length; slot++) { Object temp = getElem(cx, thisObj, slot); setElem(cx, result, slot, temp); } } else { setElem(cx, result, slot++, thisObj); } /* Copy from the arguments into the result. If any argument * has a numeric length property, treat it as an array and add * elements separately; otherwise, just copy the argument. */ for (int i = 0; i < args.length; i++) { if (ScriptRuntime.instanceOf(args[i], ctor, cx)) { // ScriptRuntime.instanceOf => instanceof Scriptable Scriptable arg = (Scriptable)args[i]; length = getLengthProperty(cx, arg); for (long j = 0; j < length; j++, slot++) { Object temp = getElem(cx, arg, j); setElem(cx, result, slot, temp); } } else { setElem(cx, result, slot++, args[i]); } } return result; } private Scriptable js_slice(Context cx, Scriptable thisObj, Object[] args) { Scriptable scope = getTopLevelScope(this); Scriptable result = ScriptRuntime.newObject(cx, scope, "Array", null); long length = getLengthProperty(cx, thisObj); long begin, end; if (args.length == 0) { begin = 0; end = length; } else { begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length); if (args.length == 1) { end = length; } else { end = toSliceIndex(ScriptRuntime.toInteger(args[1]), length); } } for (long slot = begin; slot < end; slot++) { Object temp = getElem(cx, thisObj, slot); setElem(cx, result, slot - begin, temp); } return result; } private static long toSliceIndex(double value, long length) { long result; if (value < 0.0) { if (value + length < 0.0) { result = 0; } else { result = (long)(value + length); } } else if (value > length) { result = length; } else { result = (long)value; } return result; } /** * Implements the methods "indexOf" and "lastIndexOf". */ private Object indexOfHelper(Context cx, Scriptable thisObj, Object[] args, boolean isLast) { Object compareTo = args.length > 0 ? args[0] : Undefined.instance; long length = getLengthProperty(cx, thisObj); long start = args.length > 1 ? ScriptRuntime.toInt32(ScriptRuntime.toNumber(args[1])) : (isLast ? length : 0); if (start < 0) { start += length; if (start < 0) start = 0; } if (isLast) { for (long i=start; i >= 0 ; i--) { if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) { return new Long(i); } } } else { for (long i=start; i < length; i++) { if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) { return new Long(i); } } } return NEGATIVE_ONE; } /** * Implements the methods "every", "filter", "forEach", "map", and "some". */ private Object iterativeMethod(Context cx, int id, Scriptable scope, Scriptable thisObj, Object[] args) { Object callbackArg = args.length > 0 ? args[0] : Undefined.instance; if (callbackArg == null || !(callbackArg instanceof Function)) { throw ScriptRuntime.notFunctionError( ScriptRuntime.toString(callbackArg)); } Function f = (Function) callbackArg; Scriptable parent = ScriptableObject.getTopLevelScope(f); Scriptable thisArg = args.length > 1 && args[1] instanceof Scriptable ? (Scriptable) args[1] : parent; long length = getLengthProperty(cx, thisObj); Scriptable array = null; if (id == Id_filter) { array = ScriptRuntime.newObject(cx, scope, "Array", null); } else if (id == Id_map) { // allocate dense array for efficiency Object[] ctorArgs = { new Long(length) }; array = ScriptRuntime.newObject(cx, scope, "Array", ctorArgs); } Object[] innerArgs = new Object[3]; long j=0; for (long i=0; i < length; i++) { innerArgs[0] = getElem(cx, thisObj, i); innerArgs[1] = new Long(i); innerArgs[2] = thisObj; Object result = f.call(cx, parent, thisArg, innerArgs); switch (id) { case Id_every: if (!ScriptRuntime.toBoolean(result)) return Boolean.FALSE; break; case Id_filter: if (ScriptRuntime.toBoolean(result)) setElem(cx, array, j++, innerArgs[0]); break; case Id_forEach: break; case Id_map: setElem(cx, array, j++, result); break; case Id_some: if (ScriptRuntime.toBoolean(result)) return Boolean.TRUE; break; } } switch (id) { case Id_every: return Boolean.TRUE; case Id_filter: case Id_map: return array; case Id_some: return Boolean.FALSE; case Id_forEach: default: return Undefined.instance; } } // #string_id_map# protected int findPrototypeId(String s) { int id; // #generated# Last update: 2005-09-26 15:47:42 EDT L0: { id = 0; String X = null; int c; L: switch (s.length()) { case 3: c=s.charAt(0); if (c=='m') { if (s.charAt(2)=='p' && s.charAt(1)=='a') {id=Id_map; break L0;} } else if (c=='p') { if (s.charAt(2)=='p' && s.charAt(1)=='o') {id=Id_pop; break L0;} } break L; case 4: switch (s.charAt(2)) { case 'i': X="join";id=Id_join; break L; case 'm': X="some";id=Id_some; break L; case 'r': X="sort";id=Id_sort; break L; case 's': X="push";id=Id_push; break L; } break L; case 5: c=s.charAt(1); if (c=='h') { X="shift";id=Id_shift; } else if (c=='l') { X="slice";id=Id_slice; } else if (c=='v') { X="every";id=Id_every; } break L; case 6: c=s.charAt(0); if (c=='c') { X="concat";id=Id_concat; } else if (c=='f') { X="filter";id=Id_filter; } else if (c=='s') { X="splice";id=Id_splice; } break L; case 7: switch (s.charAt(0)) { case 'f': X="forEach";id=Id_forEach; break L; case 'i': X="indexOf";id=Id_indexOf; break L; case 'r': X="reverse";id=Id_reverse; break L; case 'u': X="unshift";id=Id_unshift; break L; } break L; case 8: c=s.charAt(3); if (c=='o') { X="toSource";id=Id_toSource; } else if (c=='t') { X="toString";id=Id_toString; } break L; case 11: c=s.charAt(0); if (c=='c') { X="constructor";id=Id_constructor; } else if (c=='l') { X="lastIndexOf";id=Id_lastIndexOf; } break L; case 14: X="toLocaleString";id=Id_toLocaleString; break L; } if (X!=null && X!=s && !X.equals(s)) id = 0; } // #/generated# return id; } private static final int Id_constructor = 1, Id_toString = 2, Id_toLocaleString = 3, Id_toSource = 4, Id_join = 5, Id_reverse = 6, Id_sort = 7, Id_push = 8, Id_pop = 9, Id_shift = 10, Id_unshift = 11, Id_splice = 12, Id_concat = 13, Id_slice = 14, Id_indexOf = 15, Id_lastIndexOf = 16, Id_every = 17, Id_filter = 18, Id_forEach = 19, Id_map = 20, Id_some = 21, MAX_PROTOTYPE_ID = 21; // #/string_id_map# private long length; private Object[] dense; private static final int maximumDenseLength = 10000; }