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 }