1 /*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.nashorn.internal.objects;
27
28 import static jdk.nashorn.internal.runtime.ECMAErrors.rangeError;
29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
30 import static jdk.nashorn.internal.runtime.PropertyDescriptor.VALUE;
31 import static jdk.nashorn.internal.runtime.PropertyDescriptor.WRITABLE;
32 import static jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator.arrayLikeIterator;
33 import static jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator.reverseArrayLikeIterator;
34
35 import java.lang.invoke.MethodHandle;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.Comparator;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.concurrent.Callable;
43
44 import jdk.nashorn.api.scripting.JSObject;
45 import jdk.nashorn.internal.objects.annotations.Attribute;
46 import jdk.nashorn.internal.objects.annotations.Constructor;
47 import jdk.nashorn.internal.objects.annotations.Function;
48 import jdk.nashorn.internal.objects.annotations.Getter;
49 import jdk.nashorn.internal.objects.annotations.ScriptClass;
50 import jdk.nashorn.internal.objects.annotations.Setter;
51 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
52 import jdk.nashorn.internal.objects.annotations.Where;
53 import jdk.nashorn.internal.runtime.JSType;
54 import jdk.nashorn.internal.runtime.PropertyDescriptor;
55 import jdk.nashorn.internal.runtime.PropertyMap;
56 import jdk.nashorn.internal.runtime.ScriptFunction;
57 import jdk.nashorn.internal.runtime.ScriptObject;
58 import jdk.nashorn.internal.runtime.ScriptRuntime;
59 import jdk.nashorn.internal.runtime.Undefined;
60 import jdk.nashorn.internal.runtime.arrays.ArrayData;
61 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
62 import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
63 import jdk.nashorn.internal.runtime.arrays.IteratorAction;
64 import jdk.nashorn.internal.runtime.linker.Bootstrap;
65 import jdk.nashorn.internal.runtime.linker.InvokeByName;
66
67 /**
68 * Runtime representation of a JavaScript array. NativeArray only holds numeric
69 * keyed values. All other values are stored in spill.
70 */
71 @ScriptClass("Array")
72 public final class NativeArray extends ScriptObject {
73 private static final Object JOIN = new Object();
74 private static final Object EVERY_CALLBACK_INVOKER = new Object();
75 private static final Object SOME_CALLBACK_INVOKER = new Object();
76 private static final Object FOREACH_CALLBACK_INVOKER = new Object();
77 private static final Object MAP_CALLBACK_INVOKER = new Object();
78 private static final Object FILTER_CALLBACK_INVOKER = new Object();
79 private static final Object REDUCE_CALLBACK_INVOKER = new Object();
80 private static final Object CALL_CMP = new Object();
81 private static final Object TO_LOCALE_STRING = new Object();
82
83 private static InvokeByName getJOIN() {
84 return Global.instance().getInvokeByName(JOIN,
85 new Callable<InvokeByName>() {
86 @Override
87 public InvokeByName call() {
88 return new InvokeByName("join", ScriptObject.class);
89 }
90 });
91 }
92
93 private static MethodHandle createIteratorCallbackInvoker(final Object key, final Class<?> rtype) {
94 return Global.instance().getDynamicInvoker(key,
95 new Callable<MethodHandle>() {
96 @Override
97 public MethodHandle call() {
98 return Bootstrap.createDynamicInvoker("dyn:call", rtype, Object.class, Object.class, Object.class,
99 long.class, Object.class);
100 }
101 });
102 }
103
104 private static MethodHandle getEVERY_CALLBACK_INVOKER() {
105 return createIteratorCallbackInvoker(EVERY_CALLBACK_INVOKER, boolean.class);
106 }
107
108 private static MethodHandle getSOME_CALLBACK_INVOKER() {
109 return createIteratorCallbackInvoker(SOME_CALLBACK_INVOKER, boolean.class);
110 }
111
112 private static MethodHandle getFOREACH_CALLBACK_INVOKER() {
113 return createIteratorCallbackInvoker(FOREACH_CALLBACK_INVOKER, void.class);
114 }
115
116 private static MethodHandle getMAP_CALLBACK_INVOKER() {
117 return createIteratorCallbackInvoker(MAP_CALLBACK_INVOKER, Object.class);
118 }
119
120 private static MethodHandle getFILTER_CALLBACK_INVOKER() {
121 return createIteratorCallbackInvoker(FILTER_CALLBACK_INVOKER, boolean.class);
122 }
123
124 private static MethodHandle getREDUCE_CALLBACK_INVOKER() {
125 return Global.instance().getDynamicInvoker(REDUCE_CALLBACK_INVOKER,
126 new Callable<MethodHandle>() {
127 @Override
128 public MethodHandle call() {
129 return Bootstrap.createDynamicInvoker("dyn:call", Object.class, Object.class,
130 Undefined.class, Object.class, Object.class, long.class, Object.class);
131 }
132 });
133 }
134
135 private static MethodHandle getCALL_CMP() {
136 return Global.instance().getDynamicInvoker(CALL_CMP,
137 new Callable<MethodHandle>() {
138 @Override
139 public MethodHandle call() {
140 return Bootstrap.createDynamicInvoker("dyn:call", double.class,
141 ScriptFunction.class, Object.class, Object.class, Object.class);
142 }
143 });
144 }
145
146 private static InvokeByName getTO_LOCALE_STRING() {
147 return Global.instance().getInvokeByName(TO_LOCALE_STRING,
148 new Callable<InvokeByName>() {
149 @Override
150 public InvokeByName call() {
151 return new InvokeByName("toLocaleString", ScriptObject.class, String.class);
152 }
153 });
154 }
155
156 // initialized by nasgen
157 private static PropertyMap $nasgenmap$;
158
159 /*
160 * Constructors.
161 */
162 NativeArray() {
163 this(ArrayData.initialArray());
164 }
165
166 NativeArray(final long length) {
167 // TODO assert valid index in long before casting
168 this(ArrayData.allocate((int) length));
169 }
170
171 NativeArray(final int[] array) {
172 this(ArrayData.allocate(array));
173 }
174
175 NativeArray(final long[] array) {
176 this(ArrayData.allocate(array));
177 }
178
179 NativeArray(final double[] array) {
180 this(ArrayData.allocate(array));
181 }
182
183 NativeArray(final Object[] array) {
184 this(ArrayData.allocate(array.length));
185
186 ArrayData arrayData = this.getArray();
187 arrayData.ensure(array.length - 1);
188
189 for (int index = 0; index < array.length; index++) {
190 final Object value = array[index];
191
192 if (value == ScriptRuntime.EMPTY) {
193 arrayData = arrayData.delete(index);
194 } else {
195 arrayData = arrayData.set(index, value, false);
196 }
197 }
198
199 this.setArray(arrayData);
200 }
201
202 NativeArray(final ArrayData arrayData) {
203 this(arrayData, Global.instance());
204 }
205
206 NativeArray(final ArrayData arrayData, final Global global) {
207 super(global.getArrayPrototype(), $nasgenmap$);
208 this.setArray(arrayData);
209 this.setIsArray();
210 }
211
212 @Override
213 public String getClassName() {
214 return "Array";
215 }
216
217 @Override
218 public Object getLength() {
219 return getArray().length() & JSType.MAX_UINT;
220 }
221
222 /**
223 * ECMA 15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw )
224 */
225 @Override
226 public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) {
227 final PropertyDescriptor desc = toPropertyDescriptor(Global.instance(), propertyDesc);
228
229 // never be undefined as "length" is always defined and can't be deleted for arrays
230 // Step 1
231 final PropertyDescriptor oldLenDesc = (PropertyDescriptor) super.getOwnPropertyDescriptor("length");
232
233 // Step 2
234 // get old length and convert to long
235 long oldLen = NativeArray.validLength(oldLenDesc.getValue(), true);
236
237 // Step 3
238 if ("length".equals(key)) {
239 // check for length being made non-writable
240 if (desc.has(WRITABLE) && !desc.isWritable()) {
241 setIsLengthNotWritable();
242 }
243
244 // Step 3a
245 if (!desc.has(VALUE)) {
246 return super.defineOwnProperty("length", desc, reject);
247 }
248
249 // Step 3b
250 final PropertyDescriptor newLenDesc = desc;
251
252 // Step 3c and 3d - get new length and convert to long
253 final long newLen = NativeArray.validLength(newLenDesc.getValue(), true);
254
255 // Step 3e
256 newLenDesc.setValue(newLen);
257
258 // Step 3f
259 // increasing array length - just need to set new length value (and attributes if any) and return
260 if (newLen >= oldLen) {
261 return super.defineOwnProperty("length", newLenDesc, reject);
262 }
263
264 // Step 3g
265 if (!oldLenDesc.isWritable()) {
266 if (reject) {
267 throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this));
268 }
269 return false;
270 }
271
272 // Step 3h and 3i
273 final boolean newWritable = (!newLenDesc.has(WRITABLE) || newLenDesc.isWritable());
274 if (!newWritable) {
275 newLenDesc.setWritable(true);
276 }
277
278 // Step 3j and 3k
279 final boolean succeeded = super.defineOwnProperty("length", newLenDesc, reject);
280 if (!succeeded) {
281 return false;
282 }
283
284 // Step 3l
285 // make sure that length is set till the point we can delete the old elements
286 while (newLen < oldLen) {
287 oldLen--;
288 final boolean deleteSucceeded = delete(oldLen, false);
289 if (!deleteSucceeded) {
290 newLenDesc.setValue(oldLen + 1);
291 if (!newWritable) {
292 newLenDesc.setWritable(false);
293 }
294 super.defineOwnProperty("length", newLenDesc, false);
295 if (reject) {
296 throw typeError("property.not.writable", "length", ScriptRuntime.safeToString(this));
297 }
298 return false;
299 }
300 }
301
302 // Step 3m
303 if (!newWritable) {
304 // make 'length' property not writable
305 final ScriptObject newDesc = Global.newEmptyInstance();
306 newDesc.set(WRITABLE, false, false);
307 return super.defineOwnProperty("length", newDesc, false);
308 }
309
310 return true;
311 }
312
313 // Step 4a
314 final int index = ArrayIndex.getArrayIndex(key);
315 if (ArrayIndex.isValidArrayIndex(index)) {
316 final long longIndex = ArrayIndex.toLongIndex(index);
317 // Step 4b
318 // setting an element beyond current length, but 'length' is not writable
319 if (longIndex >= oldLen && !oldLenDesc.isWritable()) {
320 if (reject) {
321 throw typeError("property.not.writable", Long.toString(longIndex), ScriptRuntime.safeToString(this));
322 }
323 return false;
324 }
325
326 // Step 4c
327 // set the new array element
328 final boolean succeeded = super.defineOwnProperty(key, desc, false);
329
330 // Step 4d
331 if (!succeeded) {
332 if (reject) {
333 throw typeError("cant.redefine.property", key, ScriptRuntime.safeToString(this));
334 }
335 return false;
336 }
337
338 // Step 4e -- adjust new length based on new element index that is set
339 if (longIndex >= oldLen) {
340 oldLenDesc.setValue(longIndex + 1);
341 super.defineOwnProperty("length", oldLenDesc, false);
342 }
343
344 // Step 4f
345 return true;
346 }
347
348 // not an index property
349 return super.defineOwnProperty(key, desc, reject);
350 }
351
352 /**
353 * Return the array contents upcasted as an ObjectArray, regardless of
354 * representation
355 *
356 * @return an object array
357 */
358 public Object[] asObjectArray() {
359 return getArray().asObjectArray();
360 }
361
362 /**
363 * ECMA 15.4.3.2 Array.isArray ( arg )
364 *
365 * @param self self reference
366 * @param arg argument - object to check
367 * @return true if argument is an array
368 */
369 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
370 public static Object isArray(final Object self, final Object arg) {
371 return isArray(arg) || (arg instanceof JSObject && ((JSObject)arg).isArray());
372 }
373
374 /**
375 * Length getter
376 * @param self self reference
377 * @return the length of the object
378 */
379 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
380 public static Object length(final Object self) {
381 if (isArray(self)) {
382 return ((ScriptObject) self).getArray().length() & JSType.MAX_UINT;
383 }
384
385 return 0;
386 }
387
388 /**
389 * Length setter
390 * @param self self reference
391 * @param length new length property
392 */
393 @Setter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
394 public static void length(final Object self, final Object length) {
395 if (isArray(self)) {
396 ((ScriptObject) self).setLength(validLength(length, true));
397 }
398 }
399
400 /**
401 * Prototype length getter
402 * @param self self reference
403 * @return the length of the object
404 */
405 @Getter(name = "length", where = Where.PROTOTYPE, attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
406 public static Object getProtoLength(final Object self) {
407 return length(self); // Same as instance getter but we can't make nasgen use the same method for prototype
408 }
409
410 /**
411 * Prototype length setter
412 * @param self self reference
413 * @param length new length property
414 */
415 @Setter(name = "length", where = Where.PROTOTYPE, attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
416 public static void setProtoLength(final Object self, final Object length) {
417 length(self, length); // Same as instance setter but we can't make nasgen use the same method for prototype
418 }
419
420 static long validLength(final Object length, final boolean reject) {
421 final double doubleLength = JSType.toNumber(length);
422 if (!Double.isNaN(doubleLength) && JSType.isRepresentableAsLong(doubleLength)) {
423 final long len = (long) doubleLength;
424 if (len >= 0 && len <= JSType.MAX_UINT) {
425 return len;
426 }
427 }
428 if (reject) {
429 throw rangeError("inappropriate.array.length", ScriptRuntime.safeToString(length));
430 }
431 return -1;
432 }
433
434 /**
435 * ECMA 15.4.4.2 Array.prototype.toString ( )
436 *
437 * @param self self reference
438 * @return string representation of array
439 */
440 @Function(attributes = Attribute.NOT_ENUMERABLE)
441 public static Object toString(final Object self) {
442 final Object obj = Global.toObject(self);
443 if (obj instanceof ScriptObject) {
444 final InvokeByName joinInvoker = getJOIN();
445 final ScriptObject sobj = (ScriptObject)obj;
446 try {
447 final Object join = joinInvoker.getGetter().invokeExact(sobj);
448 if (Bootstrap.isCallable(join)) {
449 return joinInvoker.getInvoker().invokeExact(join, sobj);
450 }
451 } catch (final RuntimeException | Error e) {
452 throw e;
453 } catch (final Throwable t) {
454 throw new RuntimeException(t);
455 }
456 }
457
458 // FIXME: should lookup Object.prototype.toString and call that?
459 return ScriptRuntime.builtinObjectToString(self);
460 }
461
462 /**
463 * ECMA 15.4.4.3 Array.prototype.toLocaleString ( )
464 *
465 * @param self self reference
466 * @return locale specific string representation for array
467 */
468 @Function(attributes = Attribute.NOT_ENUMERABLE)
469 public static Object toLocaleString(final Object self) {
470 final StringBuilder sb = new StringBuilder();
471 final Iterator<Object> iter = arrayLikeIterator(self, true);
472
473 while (iter.hasNext()) {
474 final Object obj = iter.next();
475
476 if (obj != null && obj != ScriptRuntime.UNDEFINED) {
477 final Object val = JSType.toScriptObject(obj);
478
479 try {
480 if (val instanceof ScriptObject) {
481 final InvokeByName localeInvoker = getTO_LOCALE_STRING();
482 final ScriptObject sobj = (ScriptObject)val;
483 final Object toLocaleString = localeInvoker.getGetter().invokeExact(sobj);
484
485 if (Bootstrap.isCallable(toLocaleString)) {
486 sb.append((String)localeInvoker.getInvoker().invokeExact(toLocaleString, sobj));
487 } else {
488 throw typeError("not.a.function", "toLocaleString");
489 }
490 }
491 } catch (final Error|RuntimeException t) {
492 throw t;
493 } catch (final Throwable t) {
494 throw new RuntimeException(t);
495 }
496 }
497
498 if (iter.hasNext()) {
499 sb.append(",");
500 }
501 }
502
503 return sb.toString();
504 }
505
506 /**
507 * ECMA 15.4.2.2 new Array (len)
508 *
509 * @param newObj was the new operator used to instantiate this array
510 * @param self self reference
511 * @param args arguments (length)
512 * @return the new NativeArray
513 */
514 @Constructor(arity = 1)
515 public static Object construct(final boolean newObj, final Object self, final Object... args) {
516 switch (args.length) {
517 case 0:
518 return new NativeArray(0);
519 case 1:
520 final Object len = args[0];
521 if (len instanceof Number) {
522 long length;
523 if (len instanceof Integer || len instanceof Long) {
524 length = ((Number) len).longValue();
525 if (length >= 0 && length < JSType.MAX_UINT) {
526 return new NativeArray(length);
527 }
528 }
529
530 length = JSType.toUint32(len);
531
532 /*
533 * If the argument len is a Number and ToUint32(len) is equal to
534 * len, then the length property of the newly constructed object
535 * is set to ToUint32(len). If the argument len is a Number and
536 * ToUint32(len) is not equal to len, a RangeError exception is
537 * thrown.
538 */
539 final double numberLength = ((Number) len).doubleValue();
540 if (length != numberLength) {
541 throw rangeError("inappropriate.array.length", JSType.toString(numberLength));
542 }
543
544 return new NativeArray(length);
545 }
546 /*
547 * If the argument len is not a Number, then the length property of
548 * the newly constructed object is set to 1 and the 0 property of
549 * the newly constructed object is set to len
550 */
551 return new NativeArray(new Object[]{args[0]});
552 //fallthru
553 default:
554 return new NativeArray(args);
555 }
556 }
557
558 /**
559 * ECMA 15.4.2.2 new Array (len)
560 *
561 * Specialized constructor for zero arguments - empty array
562 *
563 * @param newObj was the new operator used to instantiate this array
564 * @param self self reference
565 * @return the new NativeArray
566 */
567 @SpecializedConstructor
568 public static Object construct(final boolean newObj, final Object self) {
569 return new NativeArray(0);
570 }
571
572 /**
573 * ECMA 15.4.2.2 new Array (len)
574 *
575 * Specialized constructor for one integer argument (length)
576 *
577 * @param newObj was the new operator used to instantiate this array
578 * @param self self reference
579 * @param length array length
580 * @return the new NativeArray
581 */
582 @SpecializedConstructor
583 public static Object construct(final boolean newObj, final Object self, final int length) {
584 if (length >= 0) {
585 return new NativeArray(length);
586 }
587
588 return construct(newObj, self, new Object[]{length});
589 }
590
591 /**
592 * ECMA 15.4.2.2 new Array (len)
593 *
594 * Specialized constructor for one long argument (length)
595 *
596 * @param newObj was the new operator used to instantiate this array
597 * @param self self reference
598 * @param length array length
599 * @return the new NativeArray
600 */
601 @SpecializedConstructor
602 public static Object construct(final boolean newObj, final Object self, final long length) {
603 if (length >= 0L && length <= JSType.MAX_UINT) {
604 return new NativeArray(length);
605 }
606
607 return construct(newObj, self, new Object[]{length});
608 }
609
610 /**
611 * ECMA 15.4.2.2 new Array (len)
612 *
613 * Specialized constructor for one double argument (length)
614 *
615 * @param newObj was the new operator used to instantiate this array
616 * @param self self reference
617 * @param length array length
618 * @return the new NativeArray
619 */
620 @SpecializedConstructor
621 public static Object construct(final boolean newObj, final Object self, final double length) {
622 final long uint32length = JSType.toUint32(length);
623
624 if (uint32length == length) {
625 return new NativeArray(uint32length);
626 }
627
628 return construct(newObj, self, new Object[]{length});
629 }
630
631 /**
632 * ECMA 15.4.4.4 Array.prototype.concat ( [ item1 [ , item2 [ , ... ] ] ] )
633 *
634 * @param self self reference
635 * @param args arguments to concat
636 * @return resulting NativeArray
637 */
638 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
639 public static Object concat(final Object self, final Object... args) {
640 final ArrayList<Object> list = new ArrayList<>();
641 concatToList(list, Global.toObject(self));
642
643 for (final Object obj : args) {
644 concatToList(list, obj);
645 }
646
647 return new NativeArray(list.toArray());
648 }
649
650 @SuppressWarnings("null")
651 private static void concatToList(final ArrayList<Object> list, final Object obj) {
652 final boolean isScriptArray = isArray(obj);
653 final boolean isScriptObject = isScriptArray || obj instanceof ScriptObject;
654 if (isScriptArray || obj instanceof Iterable || (obj != null && obj.getClass().isArray())) {
655 final Iterator<Object> iter = arrayLikeIterator(obj, true);
656 if (iter.hasNext()) {
657 for (int i = 0; iter.hasNext(); ++i) {
658 final Object value = iter.next();
659 if (value == ScriptRuntime.UNDEFINED && isScriptObject && !((ScriptObject)obj).has(i)) {
660 // TODO: eventually rewrite arrayLikeIterator to use a three-state enum for handling
661 // UNDEFINED instead of an "includeUndefined" boolean with states SKIP, INCLUDE,
662 // RETURN_EMPTY. Until then, this is how we'll make sure that empty elements don't make it
663 // into the concatenated array.
664 list.add(ScriptRuntime.EMPTY);
665 } else {
666 list.add(value);
667 }
668 }
669 } else if (!isScriptArray) {
670 list.add(obj); // add empty object, but not an empty array
671 }
672 } else {
673 // single element, add it
674 list.add(obj);
675 }
676 }
677
678 /**
679 * ECMA 15.4.4.5 Array.prototype.join (separator)
680 *
681 * @param self self reference
682 * @param separator element separator
683 * @return string representation after join
684 */
685 @Function(attributes = Attribute.NOT_ENUMERABLE)
686 public static Object join(final Object self, final Object separator) {
687 final StringBuilder sb = new StringBuilder();
688 final Iterator<Object> iter = arrayLikeIterator(self, true);
689 final String sep = separator == ScriptRuntime.UNDEFINED ? "," : JSType.toString(separator);
690
691 while (iter.hasNext()) {
692 final Object obj = iter.next();
693
694 if (obj != null && obj != ScriptRuntime.UNDEFINED) {
695 sb.append(JSType.toString(obj));
696 }
697
698 if (iter.hasNext()) {
699 sb.append(sep);
700 }
701 }
702
703 return sb.toString();
704 }
705
706 /**
707 * ECMA 15.4.4.6 Array.prototype.pop ()
708 *
709 * @param self self reference
710 * @return array after pop
711 */
712 @Function(attributes = Attribute.NOT_ENUMERABLE)
713 public static Object pop(final Object self) {
714 try {
715 final ScriptObject sobj = (ScriptObject)self;
716
717 if (bulkable(sobj)) {
718 return sobj.getArray().pop();
719 }
720
721 final long len = JSType.toUint32(sobj.getLength());
722
723 if (len == 0) {
724 sobj.set("length", 0, true);
725 return ScriptRuntime.UNDEFINED;
726 }
727
728 final long index = len - 1;
729 final Object element = sobj.get(index);
730
731 sobj.delete(index, true);
732 sobj.set("length", index, true);
733
734 return element;
735 } catch (final ClassCastException | NullPointerException e) {
736 throw typeError("not.an.object", ScriptRuntime.safeToString(self));
737 }
738 }
739
740 /**
741 * ECMA 15.4.4.7 Array.prototype.push (args...)
742 *
743 * @param self self reference
744 * @param args arguments to push
745 * @return array after pushes
746 */
747 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
748 public static Object push(final Object self, final Object... args) {
749 try {
750 final ScriptObject sobj = (ScriptObject)self;
751
752 if (bulkable(sobj)) {
753 if (sobj.getArray().length() + args.length <= JSType.MAX_UINT) {
754 final ArrayData newData = sobj.getArray().push(true, args);
755 sobj.setArray(newData);
756 return newData.length();
757 }
758 //fallthru
759 }
760
761 long len = JSType.toUint32(sobj.getLength());
762 for (final Object element : args) {
763 sobj.set(len++, element, true);
764 }
765 sobj.set("length", len, true);
766
767 return len;
768 } catch (final ClassCastException | NullPointerException e) {
769 throw typeError("not.an.object", ScriptRuntime.safeToString(self));
770 }
771 }
772
773 /**
774 * ECMA 15.4.4.8 Array.prototype.reverse ()
775 *
776 * @param self self reference
777 * @return reversed array
778 */
779 @Function(attributes = Attribute.NOT_ENUMERABLE)
780 public static Object reverse(final Object self) {
781 try {
782 final ScriptObject sobj = (ScriptObject)self;
783 final long len = JSType.toUint32(sobj.getLength());
784 final long middle = len / 2;
785
786 for (long lower = 0; lower != middle; lower++) {
787 final long upper = len - lower - 1;
788 final Object lowerValue = sobj.get(lower);
789 final Object upperValue = sobj.get(upper);
790 final boolean lowerExists = sobj.has(lower);
791 final boolean upperExists = sobj.has(upper);
792
793 if (lowerExists && upperExists) {
794 sobj.set(lower, upperValue, true);
795 sobj.set(upper, lowerValue, true);
796 } else if (!lowerExists && upperExists) {
797 sobj.set(lower, upperValue, true);
798 sobj.delete(upper, true);
799 } else if (lowerExists && !upperExists) {
800 sobj.delete(lower, true);
801 sobj.set(upper, lowerValue, true);
802 }
803 }
804 return sobj;
805 } catch (final ClassCastException | NullPointerException e) {
806 throw typeError("not.an.object", ScriptRuntime.safeToString(self));
807 }
808 }
809
810 /**
811 * ECMA 15.4.4.9 Array.prototype.shift ()
812 *
813 * @param self self reference
814 * @return shifted array
815 */
816 @Function(attributes = Attribute.NOT_ENUMERABLE)
817 public static Object shift(final Object self) {
818 final Object obj = Global.toObject(self);
819
820 Object first = ScriptRuntime.UNDEFINED;
821
822 if (!(obj instanceof ScriptObject)) {
823 return first;
824 }
825
826 final ScriptObject sobj = (ScriptObject) obj;
827
828 long len = JSType.toUint32(sobj.getLength());
829
830 if (len > 0) {
831 first = sobj.get(0);
832
833 if (bulkable(sobj)) {
834 sobj.getArray().shiftLeft(1);
835 } else {
836 boolean hasPrevious = true;
837 for (long k = 1; k < len; k++) {
838 boolean hasCurrent = sobj.has(k);
839 if (hasCurrent) {
840 sobj.set(k - 1, sobj.get(k), true);
841 } else if (hasPrevious) {
842 sobj.delete(k - 1, true);
843 }
844 hasPrevious = hasCurrent;
845 }
846 }
847 sobj.delete(--len, true);
848 } else {
849 len = 0;
850 }
851
852 sobj.set("length", len, true);
853
854 return first;
855 }
856
857 /**
858 * ECMA 15.4.4.10 Array.prototype.slice ( start [ , end ] )
859 *
860 * @param self self reference
861 * @param start start of slice (inclusive)
862 * @param end end of slice (optional, exclusive)
863 * @return sliced array
864 */
865 @Function(attributes = Attribute.NOT_ENUMERABLE)
866 public static Object slice(final Object self, final Object start, final Object end) {
867 final Object obj = Global.toObject(self);
868 if (!(obj instanceof ScriptObject)) {
869 return ScriptRuntime.UNDEFINED;
870 }
871
872 final ScriptObject sobj = (ScriptObject)obj;
873 final long len = JSType.toUint32(sobj.getLength());
874 final long relativeStart = JSType.toLong(start);
875 final long relativeEnd = (end == ScriptRuntime.UNDEFINED) ? len : JSType.toLong(end);
876
877 long k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
878 final long finale = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
879
880 if (k >= finale) {
881 return new NativeArray(0);
882 }
883
884 if (bulkable(sobj)) {
885 return new NativeArray(sobj.getArray().slice(k, finale));
886 }
887
888 // Construct array with proper length to have a deleted filter on undefined elements
889 final NativeArray copy = new NativeArray(finale - k);
890 for (long n = 0; k < finale; n++, k++) {
891 if (sobj.has(k)) {
892 copy.defineOwnProperty(ArrayIndex.getArrayIndex(n), sobj.get(k));
893 }
894 }
895
896 return copy;
897 }
898
899 private static ScriptFunction compareFunction(final Object comparefn) {
900 if (comparefn == ScriptRuntime.UNDEFINED) {
901 return null;
902 }
903
904 if (! (comparefn instanceof ScriptFunction)) {
905 throw typeError("not.a.function", ScriptRuntime.safeToString(comparefn));
906 }
907
908 return (ScriptFunction)comparefn;
909 }
910
911 private static Object[] sort(final Object[] array, final Object comparefn) {
912 final ScriptFunction cmp = compareFunction(comparefn);
913
914 final List<Object> list = Arrays.asList(array);
915 final Object cmpThis = cmp == null || cmp.isStrict() ? ScriptRuntime.UNDEFINED : Global.instance();
916
917 Collections.sort(list, new Comparator<Object>() {
918 private final MethodHandle call_cmp = getCALL_CMP();
919 @Override
920 public int compare(final Object x, final Object y) {
921 if (x == ScriptRuntime.UNDEFINED && y == ScriptRuntime.UNDEFINED) {
922 return 0;
923 } else if (x == ScriptRuntime.UNDEFINED) {
924 return 1;
925 } else if (y == ScriptRuntime.UNDEFINED) {
926 return -1;
927 }
928
929 if (cmp != null) {
930 try {
931 return (int)Math.signum((double)call_cmp.invokeExact(cmp, cmpThis, x, y));
932 } catch (final RuntimeException | Error e) {
933 throw e;
934 } catch (final Throwable t) {
935 throw new RuntimeException(t);
936 }
937 }
938
939 return JSType.toString(x).compareTo(JSType.toString(y));
940 }
941 });
942
943 return list.toArray(new Object[array.length]);
944 }
945
946 /**
947 * ECMA 15.4.4.11 Array.prototype.sort ( comparefn )
948 *
949 * @param self self reference
950 * @param comparefn element comparison function
951 * @return sorted array
952 */
953 @Function(attributes = Attribute.NOT_ENUMERABLE)
954 public static Object sort(final Object self, final Object comparefn) {
955 try {
956 final ScriptObject sobj = (ScriptObject) self;
957 final long len = JSType.toUint32(sobj.getLength());
958 ArrayData array = sobj.getArray();
959
960 if (len > 1) {
961 // Get only non-missing elements. Missing elements go at the end
962 // of the sorted array. So, just don't copy these to sort input.
963 final ArrayList<Object> src = new ArrayList<>();
964 for (long i = 0; i < len; i = array.nextIndex(i)) {
965 if (array.has((int) i)) {
966 src.add(array.getObject((int) i));
967 }
968 }
969
970 final Object[] sorted = sort(src.toArray(), comparefn);
971
972 for (int i = 0; i < sorted.length; i++) {
973 array = array.set(i, sorted[i], true);
974 }
975
976 // delete missing elements - which are at the end of sorted array
977 if (sorted.length != len) {
978 array = array.delete(sorted.length, len - 1);
979 }
980
981 sobj.setArray(array);
982 }
983
984 return sobj;
985 } catch (final ClassCastException | NullPointerException e) {
986 throw typeError("not.an.object", ScriptRuntime.safeToString(self));
987 }
988 }
989
990 /**
991 * ECMA 15.4.4.12 Array.prototype.splice ( start, deleteCount [ item1 [ , item2 [ , ... ] ] ] )
992 *
993 * @param self self reference
994 * @param args arguments
995 * @return result of splice
996 */
997 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 2)
998 public static Object splice(final Object self, final Object... args) {
999 final Object obj = Global.toObject(self);
1000
1001 if (!(obj instanceof ScriptObject)) {
1002 return ScriptRuntime.UNDEFINED;
1003 }
1004
1005 final Object start = (args.length > 0) ? args[0] : ScriptRuntime.UNDEFINED;
1006 final Object deleteCount = (args.length > 1) ? args[1] : ScriptRuntime.UNDEFINED;
1007
1008 Object[] items;
1009
1010 if (args.length > 2) {
1011 items = new Object[args.length - 2];
1012 System.arraycopy(args, 2, items, 0, items.length);
1013 } else {
1014 items = ScriptRuntime.EMPTY_ARRAY;
1015 }
1016
1017 final ScriptObject sobj = (ScriptObject)obj;
1018 final long len = JSType.toUint32(sobj.getLength());
1019 final long relativeStart = JSType.toLong(start);
1020
1021 final long actualStart = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
1022 final long actualDeleteCount = Math.min(Math.max(JSType.toLong(deleteCount), 0), len - actualStart);
1023
1024 NativeArray returnValue;
1025
1026 if (actualStart <= Integer.MAX_VALUE && actualDeleteCount <= Integer.MAX_VALUE && bulkable(sobj)) {
1027 try {
1028 returnValue = new NativeArray(sobj.getArray().fastSplice((int)actualStart, (int)actualDeleteCount, items.length));
1029
1030 // Since this is a dense bulkable array we can use faster defineOwnProperty to copy new elements
1031 int k = (int) actualStart;
1032 for (int i = 0; i < items.length; i++, k++) {
1033 sobj.defineOwnProperty(k, items[i]);
1034 }
1035 } catch (UnsupportedOperationException uoe) {
1036 returnValue = slowSplice(sobj, actualStart, actualDeleteCount, items, len);
1037 }
1038 } else {
1039 returnValue = slowSplice(sobj, actualStart, actualDeleteCount, items, len);
1040 }
1041
1042 return returnValue;
1043 }
1044
1045 private static NativeArray slowSplice(final ScriptObject sobj, final long start, final long deleteCount, final Object[] items, final long len) {
1046
1047 final NativeArray array = new NativeArray(deleteCount);
1048
1049 for (long k = 0; k < deleteCount; k++) {
1050 final long from = start + k;
1051
1052 if (sobj.has(from)) {
1053 array.defineOwnProperty(ArrayIndex.getArrayIndex(k), sobj.get(from));
1054 }
1055 }
1056
1057 if (items.length < deleteCount) {
1058 for (long k = start; k < (len - deleteCount); k++) {
1059 final long from = k + deleteCount;
1060 final long to = k + items.length;
1061
1062 if (sobj.has(from)) {
1063 sobj.set(to, sobj.get(from), true);
1064 } else {
1065 sobj.delete(to, true);
1066 }
1067 }
1068
1069 for (long k = len; k > (len - deleteCount + items.length); k--) {
1070 sobj.delete(k - 1, true);
1071 }
1072 } else if (items.length > deleteCount) {
1073 for (long k = len - deleteCount; k > start; k--) {
1074 final long from = k + deleteCount - 1;
1075 final long to = k + items.length - 1;
1076
1077 if (sobj.has(from)) {
1078 final Object fromValue = sobj.get(from);
1079 sobj.set(to, fromValue, true);
1080 } else {
1081 sobj.delete(to, true);
1082 }
1083 }
1084 }
1085
1086 long k = start;
1087 for (int i = 0; i < items.length; i++, k++) {
1088 sobj.set(k, items[i], true);
1089 }
1090
1091 final long newLength = len - deleteCount + items.length;
1092 sobj.set("length", newLength, true);
1093
1094 return array;
1095 }
1096
1097 /**
1098 * ECMA 15.4.4.13 Array.prototype.unshift ( [ item1 [ , item2 [ , ... ] ] ] )
1099 *
1100 * @param self self reference
1101 * @param items items for unshift
1102 * @return unshifted array
1103 */
1104 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1105 public static Object unshift(final Object self, final Object... items) {
1106 final Object obj = Global.toObject(self);
1107
1108 if (!(obj instanceof ScriptObject)) {
1109 return ScriptRuntime.UNDEFINED;
1110 }
1111
1112 final ScriptObject sobj = (ScriptObject)obj;
1113 final long len = JSType.toUint32(sobj.getLength());
1114
1115 if (items == null) {
1116 return ScriptRuntime.UNDEFINED;
1117 }
1118
1119 if (bulkable(sobj)) {
1120 sobj.getArray().shiftRight(items.length);
1121
1122 for (int j = 0; j < items.length; j++) {
1123 sobj.setArray(sobj.getArray().set(j, items[j], true));
1124 }
1125 } else {
1126 for (long k = len; k > 0; k--) {
1127 final long from = k - 1;
1128 final long to = k + items.length - 1;
1129
1130 if (sobj.has(from)) {
1131 final Object fromValue = sobj.get(from);
1132 sobj.set(to, fromValue, true);
1133 } else {
1134 sobj.delete(to, true);
1135 }
1136 }
1137
1138 for (int j = 0; j < items.length; j++) {
1139 sobj.set(j, items[j], true);
1140 }
1141 }
1142
1143 final long newLength = len + items.length;
1144 sobj.set("length", newLength, true);
1145
1146 return newLength;
1147 }
1148
1149 /**
1150 * ECMA 15.4.4.14 Array.prototype.indexOf ( searchElement [ , fromIndex ] )
1151 *
1152 * @param self self reference
1153 * @param searchElement element to search for
1154 * @param fromIndex start index of search
1155 * @return index of element, or -1 if not found
1156 */
1157 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1158 public static Object indexOf(final Object self, final Object searchElement, final Object fromIndex) {
1159 try {
1160 final ScriptObject sobj = (ScriptObject)Global.toObject(self);
1161 final long len = JSType.toUint32(sobj.getLength());
1162 if (len == 0) {
1163 return -1;
1164 }
1165
1166 final long n = JSType.toLong(fromIndex);
1167 if (n >= len) {
1168 return -1;
1169 }
1170
1171
1172 for (long k = Math.max(0, (n < 0) ? (len - Math.abs(n)) : n); k < len; k++) {
1173 if (sobj.has(k)) {
1174 if (ScriptRuntime.EQ_STRICT(sobj.get(k), searchElement)) {
1175 return k;
1176 }
1177 }
1178 }
1179 } catch (final ClassCastException | NullPointerException e) {
1180 //fallthru
1181 }
1182
1183 return -1;
1184 }
1185
1186 /**
1187 * ECMA 15.4.4.15 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] )
1188 *
1189 * @param self self reference
1190 * @param args arguments: element to search for and optional from index
1191 * @return index of element, or -1 if not found
1192 */
1193 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1194 public static Object lastIndexOf(final Object self, final Object... args) {
1195 try {
1196 final ScriptObject sobj = (ScriptObject)Global.toObject(self);
1197 final long len = JSType.toUint32(sobj.getLength());
1198
1199 if (len == 0) {
1200 return -1;
1201 }
1202
1203 final Object searchElement = (args.length > 0) ? args[0] : ScriptRuntime.UNDEFINED;
1204 final long n = (args.length > 1) ? JSType.toLong(args[1]) : (len - 1);
1205
1206 for (long k = (n < 0) ? (len - Math.abs(n)) : Math.min(n, len - 1); k >= 0; k--) {
1207 if (sobj.has(k)) {
1208 if (ScriptRuntime.EQ_STRICT(sobj.get(k), searchElement)) {
1209 return k;
1210 }
1211 }
1212 }
1213 } catch (final ClassCastException | NullPointerException e) {
1214 throw typeError("not.an.object", ScriptRuntime.safeToString(self));
1215 }
1216
1217 return -1;
1218 }
1219
1220 /**
1221 * ECMA 15.4.4.16 Array.prototype.every ( callbackfn [ , thisArg ] )
1222 *
1223 * @param self self reference
1224 * @param callbackfn callback function per element
1225 * @param thisArg this argument
1226 * @return true if callback function return true for every element in the array, false otherwise
1227 */
1228 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1229 public static Object every(final Object self, final Object callbackfn, final Object thisArg) {
1230 return applyEvery(Global.toObject(self), callbackfn, thisArg);
1231 }
1232
1233 private static boolean applyEvery(final Object self, final Object callbackfn, final Object thisArg) {
1234 return new IteratorAction<Boolean>(Global.toObject(self), callbackfn, thisArg, true) {
1235 private final MethodHandle everyInvoker = getEVERY_CALLBACK_INVOKER();
1236
1237 @Override
1238 protected boolean forEach(final Object val, final long i) throws Throwable {
1239 return (result = (boolean)everyInvoker.invokeExact(callbackfn, thisArg, val, i, self));
1240 }
1241 }.apply();
1242 }
1243
1244 /**
1245 * ECMA 15.4.4.17 Array.prototype.some ( callbackfn [ , thisArg ] )
1246 *
1247 * @param self self reference
1248 * @param callbackfn callback function per element
1249 * @param thisArg this argument
1250 * @return true if callback function returned true for any element in the array, false otherwise
1251 */
1252 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1253 public static Object some(final Object self, final Object callbackfn, final Object thisArg) {
1254 return new IteratorAction<Boolean>(Global.toObject(self), callbackfn, thisArg, false) {
1255 private final MethodHandle someInvoker = getSOME_CALLBACK_INVOKER();
1256
1257 @Override
1258 protected boolean forEach(final Object val, final long i) throws Throwable {
1259 return !(result = (boolean)someInvoker.invokeExact(callbackfn, thisArg, val, i, self));
1260 }
1261 }.apply();
1262 }
1263
1264 /**
1265 * ECMA 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
1266 *
1267 * @param self self reference
1268 * @param callbackfn callback function per element
1269 * @param thisArg this argument
1270 * @return undefined
1271 */
1272 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1273 public static Object forEach(final Object self, final Object callbackfn, final Object thisArg) {
1274 return new IteratorAction<Object>(Global.toObject(self), callbackfn, thisArg, ScriptRuntime.UNDEFINED) {
1275 private final MethodHandle forEachInvoker = getFOREACH_CALLBACK_INVOKER();
1276
1277 @Override
1278 protected boolean forEach(final Object val, final long i) throws Throwable {
1279 forEachInvoker.invokeExact(callbackfn, thisArg, val, i, self);
1280 return true;
1281 }
1282 }.apply();
1283 }
1284
1285 /**
1286 * ECMA 15.4.4.19 Array.prototype.map ( callbackfn [ , thisArg ] )
1287 *
1288 * @param self self reference
1289 * @param callbackfn callback function per element
1290 * @param thisArg this argument
1291 * @return array with elements transformed by map function
1292 */
1293 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1294 public static Object map(final Object self, final Object callbackfn, final Object thisArg) {
1295 return new IteratorAction<NativeArray>(Global.toObject(self), callbackfn, thisArg, null) {
1296 private final MethodHandle mapInvoker = getMAP_CALLBACK_INVOKER();
1297
1298 @Override
1299 protected boolean forEach(final Object val, final long i) throws Throwable {
1300 final Object r = mapInvoker.invokeExact(callbackfn, thisArg, val, i, self);
1301 result.defineOwnProperty(ArrayIndex.getArrayIndex(index), r);
1302 return true;
1303 }
1304
1305 @Override
1306 public void applyLoopBegin(final ArrayLikeIterator<Object> iter0) {
1307 // map return array should be of same length as source array
1308 // even if callback reduces source array length
1309 result = new NativeArray(iter0.getLength());
1310 }
1311 }.apply();
1312 }
1313
1314 /**
1315 * ECMA 15.4.4.20 Array.prototype.filter ( callbackfn [ , thisArg ] )
1316 *
1317 * @param self self reference
1318 * @param callbackfn callback function per element
1319 * @param thisArg this argument
1320 * @return filtered array
1321 */
1322 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1323 public static Object filter(final Object self, final Object callbackfn, final Object thisArg) {
1324 return new IteratorAction<NativeArray>(Global.toObject(self), callbackfn, thisArg, new NativeArray()) {
1325 private long to = 0;
1326 private final MethodHandle filterInvoker = getFILTER_CALLBACK_INVOKER();
1327
1328 @Override
1329 protected boolean forEach(final Object val, final long i) throws Throwable {
1330 if ((boolean)filterInvoker.invokeExact(callbackfn, thisArg, val, i, self)) {
1331 result.defineOwnProperty(ArrayIndex.getArrayIndex(to++), val);
1332 }
1333 return true;
1334 }
1335 }.apply();
1336 }
1337
1338 private static Object reduceInner(final ArrayLikeIterator<Object> iter, final Object self, final Object... args) {
1339 final Object callbackfn = args.length > 0 ? args[0] : ScriptRuntime.UNDEFINED;
1340 final boolean initialValuePresent = args.length > 1;
1341
1342 Object initialValue = initialValuePresent ? args[1] : ScriptRuntime.UNDEFINED;
1343
1344 if (callbackfn == ScriptRuntime.UNDEFINED) {
1345 throw typeError("not.a.function", "undefined");
1346 }
1347
1348 if (!initialValuePresent) {
1349 if (iter.hasNext()) {
1350 initialValue = iter.next();
1351 } else {
1352 throw typeError("array.reduce.invalid.init");
1353 }
1354 }
1355
1356 //if initial value is ScriptRuntime.UNDEFINED - step forward once.
1357 return new IteratorAction<Object>(Global.toObject(self), callbackfn, ScriptRuntime.UNDEFINED, initialValue, iter) {
1358 private final MethodHandle reduceInvoker = getREDUCE_CALLBACK_INVOKER();
1359
1360 @Override
1361 protected boolean forEach(final Object val, final long i) throws Throwable {
1362 // TODO: why can't I declare the second arg as Undefined.class?
1363 result = reduceInvoker.invokeExact(callbackfn, ScriptRuntime.UNDEFINED, result, val, i, self);
1364 return true;
1365 }
1366 }.apply();
1367 }
1368
1369 /**
1370 * ECMA 15.4.4.21 Array.prototype.reduce ( callbackfn [ , initialValue ] )
1371 *
1372 * @param self self reference
1373 * @param args arguments to reduce
1374 * @return accumulated result
1375 */
1376 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1377 public static Object reduce(final Object self, final Object... args) {
1378 return reduceInner(arrayLikeIterator(self), self, args);
1379 }
1380
1381 /**
1382 * ECMA 15.4.4.22 Array.prototype.reduceRight ( callbackfn [ , initialValue ] )
1383 *
1384 * @param self self reference
1385 * @param args arguments to reduce
1386 * @return accumulated result
1387 */
1388 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
1389 public static Object reduceRight(final Object self, final Object... args) {
1390 return reduceInner(reverseArrayLikeIterator(self), self, args);
1391 }
1392
1393 /**
1394 * Determine if Java bulk array operations may be used on the underlying
1395 * storage. This is possible only if the object's prototype chain is empty
1396 * or each of the prototypes in the chain is empty.
1397 *
1398 * @param self the object to examine
1399 * @return true if optimizable
1400 */
1401 private static boolean bulkable(final ScriptObject self) {
1402 return self.isArray() && !hasInheritedArrayEntries(self) && !self.isLengthNotWritable();
1403 }
1404
1405 private static boolean hasInheritedArrayEntries(final ScriptObject self) {
1406 ScriptObject proto = self.getProto();
1407 while (proto != null) {
1408 if (proto.hasArrayEntries()) {
1409 return true;
1410 }
1411 proto = proto.getProto();
1412 }
1413
1414 return false;
1415 }
1416 }
--- EOF ---