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