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.lookup.Lookup.MH; 29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 30 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 31 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 32 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow; 33 34 import java.lang.invoke.MethodHandle; 35 import java.lang.invoke.MethodHandles; 36 import java.text.Collator; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.LinkedList; 40 import java.util.List; 41 import java.util.Locale; 42 import jdk.internal.dynalink.CallSiteDescriptor; 43 import jdk.internal.dynalink.linker.GuardedInvocation; 44 import jdk.internal.dynalink.linker.LinkRequest; 45 import jdk.nashorn.internal.lookup.MethodHandleFactory; 46 import jdk.nashorn.internal.objects.annotations.Attribute; 47 import jdk.nashorn.internal.objects.annotations.Constructor; 48 import jdk.nashorn.internal.objects.annotations.Function; 49 import jdk.nashorn.internal.objects.annotations.Getter; 50 import jdk.nashorn.internal.objects.annotations.ScriptClass; 51 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor; 52 import jdk.nashorn.internal.objects.annotations.SpecializedFunction; 53 import jdk.nashorn.internal.objects.annotations.Where; 54 import jdk.nashorn.internal.runtime.ConsString; 55 import jdk.nashorn.internal.runtime.JSType; 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.arrays.ArrayIndex; 60 import jdk.nashorn.internal.runtime.linker.NashornGuards; 61 import jdk.nashorn.internal.runtime.linker.PrimitiveLookup; 62 63 64 /** 65 * ECMA 15.5 String Objects. 66 */ 67 @ScriptClass("String") 68 public final class NativeString extends ScriptObject { 69 70 private final CharSequence value; 71 72 static final MethodHandle WRAPFILTER = findWrapFilter(); 73 74 NativeString(final CharSequence value) { 75 this(value, Global.instance().getStringPrototype()); 76 } 77 78 private NativeString(final CharSequence value, final ScriptObject proto) { 79 assert value instanceof String || value instanceof ConsString; 80 this.value = value; 81 this.setProto(proto); 82 } 83 84 @Override 85 public String safeToString() { 86 return "[String " + toString() + "]"; 87 } 88 89 @Override 90 public String toString() { 91 return getStringValue(); 92 } 93 94 @Override 95 public boolean equals(final Object other) { 96 if (other instanceof NativeString) { 97 return getStringValue().equals(((NativeString) other).getStringValue()); 98 } 99 100 return false; 101 } 102 103 @Override 104 public int hashCode() { 105 return getStringValue().hashCode(); 106 } 107 108 private String getStringValue() { 109 return value instanceof String ? (String) value : value.toString(); 110 } 111 112 private CharSequence getValue() { 113 return value; 114 } 115 116 @Override 117 public String getClassName() { 118 return "String"; 119 } 120 121 @Override 122 public Object getLength() { 123 return value.length(); 124 } 125 126 // This is to support length as method call as well. 127 @Override 128 protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) { 129 final String name = desc.getNameToken(2); 130 131 // if str.length(), then let the bean linker handle it 132 if ("length".equals(name) && "getMethod".equals(operator)) { 133 return null; 134 } 135 136 return super.findGetMethod(desc, request, operator); 137 } 138 139 // This is to provide array-like access to string characters without creating a NativeString wrapper. 140 @Override 141 protected GuardedInvocation findGetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) { 142 final Object self = request.getReceiver(); 143 final Class<?> returnType = desc.getMethodType().returnType(); 144 145 if (returnType == Object.class && (self instanceof String || self instanceof ConsString)) { 146 try { 147 MethodHandle mh = MethodHandles.lookup().findStatic(NativeString.class, "get", desc.getMethodType()); 148 return new GuardedInvocation(mh, NashornGuards.getInstanceOf2Guard(String.class, ConsString.class)); 149 } catch (final NoSuchMethodException | IllegalAccessException e) { 150 // Shouldn't happen. Fall back to super 151 } 152 } 153 return super.findGetIndexMethod(desc, request); 154 } 155 156 @SuppressWarnings("unused") 157 private static Object get(final Object self, final Object key) { 158 final CharSequence cs = JSType.toCharSequence(self); 159 final int index = getArrayIndexNoThrow(key); 160 if (index >= 0 && index < cs.length()) { 161 return String.valueOf(cs.charAt(index)); 162 } 163 return ((ScriptObject) Global.toObject(self)).get(key); 164 } 165 166 @SuppressWarnings("unused") 167 private static Object get(final Object self, final double key) { 168 if (isRepresentableAsInt(key)) { 169 return get(self, (int)key); 170 } 171 return ((ScriptObject) Global.toObject(self)).get(key); 172 } 173 174 @SuppressWarnings("unused") 175 private static Object get(final Object self, final long key) { 176 final CharSequence cs = JSType.toCharSequence(self); 177 if (key >= 0 && key < cs.length()) { 178 return String.valueOf(cs.charAt((int)key)); 179 } 180 return ((ScriptObject) Global.toObject(self)).get(key); 181 } 182 183 private static Object get(final Object self, final int key) { 184 final CharSequence cs = JSType.toCharSequence(self); 185 if (key >= 0 && key < cs.length()) { 186 return String.valueOf(cs.charAt(key)); 187 } 188 return ((ScriptObject) Global.toObject(self)).get(key); 189 } 190 191 // String characters can be accessed with array-like indexing.. 192 @Override 193 public Object get(final Object key) { 194 final int index = getArrayIndexNoThrow(key); 195 if (index >= 0 && index < value.length()) { 196 return String.valueOf(value.charAt(index)); 197 } 198 return super.get(key); 199 } 200 201 @Override 202 public Object get(final double key) { 203 if (isRepresentableAsInt(key)) { 204 return get((int)key); 205 } 206 return super.get(key); 207 } 208 209 @Override 210 public Object get(final long key) { 211 if (key >= 0 && key < value.length()) { 212 return String.valueOf(value.charAt((int)key)); 213 } 214 return super.get(key); 215 } 216 217 @Override 218 public Object get(final int key) { 219 if (key >= 0 && key < value.length()) { 220 return String.valueOf(value.charAt(key)); 221 } 222 return super.get(key); 223 } 224 225 @Override 226 public int getInt(final Object key) { 227 return JSType.toInt32(get(key)); 228 } 229 230 @Override 231 public int getInt(final double key) { 232 return JSType.toInt32(get(key)); 233 } 234 235 @Override 236 public int getInt(final long key) { 237 return JSType.toInt32(get(key)); 238 } 239 240 @Override 241 public int getInt(final int key) { 242 return JSType.toInt32(get(key)); 243 } 244 245 @Override 246 public long getLong(final Object key) { 247 return JSType.toUint32(get(key)); 248 } 249 250 @Override 251 public long getLong(final double key) { 252 return JSType.toUint32(get(key)); 253 } 254 255 @Override 256 public long getLong(final long key) { 257 return JSType.toUint32(get(key)); 258 } 259 260 @Override 261 public long getLong(final int key) { 262 return JSType.toUint32(get(key)); 263 } 264 265 @Override 266 public double getDouble(final Object key) { 267 return JSType.toNumber(get(key)); 268 } 269 270 @Override 271 public double getDouble(final double key) { 272 return JSType.toNumber(get(key)); 273 } 274 275 @Override 276 public double getDouble(final long key) { 277 return JSType.toNumber(get(key)); 278 } 279 280 @Override 281 public double getDouble(final int key) { 282 return JSType.toNumber(get(key)); 283 } 284 285 @Override 286 public boolean has(final Object key) { 287 final int index = getArrayIndexNoThrow(key); 288 return isValid(index) || super.has(key); 289 } 290 291 @Override 292 public boolean has(final int key) { 293 return isValid(key) || super.has(key); 294 } 295 296 @Override 297 public boolean has(final long key) { 298 final int index = getArrayIndexNoThrow(key); 299 return isValid(index) || super.has(key); 300 } 301 302 @Override 303 public boolean has(final double key) { 304 final int index = getArrayIndexNoThrow(key); 305 return isValid(index) || super.has(key); 306 } 307 308 @Override 309 public boolean hasOwnProperty(final Object key) { 310 final int index = getArrayIndexNoThrow(key); 311 return isValid(index) || super.hasOwnProperty(key); 312 } 313 314 @Override 315 public boolean hasOwnProperty(final int key) { 316 return isValid(key) || super.hasOwnProperty(key); 317 } 318 319 @Override 320 public boolean hasOwnProperty(final long key) { 321 final int index = getArrayIndexNoThrow(key); 322 return isValid(index) || super.hasOwnProperty(key); 323 } 324 325 @Override 326 public boolean hasOwnProperty(final double key) { 327 final int index = getArrayIndexNoThrow(key); 328 return isValid(index) || super.hasOwnProperty(key); 329 } 330 331 @Override 332 public boolean delete(final int key, final boolean strict) { 333 return checkDeleteIndex(key, strict)? false : super.delete(key, strict); 334 } 335 336 @Override 337 public boolean delete(final long key, final boolean strict) { 338 final int index = getArrayIndexNoThrow(key); 339 return checkDeleteIndex(index, strict)? false : super.delete(key, strict); 340 } 341 342 @Override 343 public boolean delete(final double key, final boolean strict) { 344 final int index = getArrayIndexNoThrow(key); 345 return checkDeleteIndex(index, strict)? false : super.delete(key, strict); 346 } 347 348 @Override 349 public boolean delete(final Object key, final boolean strict) { 350 final int index = getArrayIndexNoThrow(key); 351 return checkDeleteIndex(index, strict)? false : super.delete(key, strict); 352 } 353 354 private boolean checkDeleteIndex(final int index, final boolean strict) { 355 if (isValid(index)) { 356 if (strict) { 357 throw typeError("cant.delete.property", Integer.toString(index), ScriptRuntime.safeToString(this)); 358 } 359 return true; 360 } 361 362 return false; 363 } 364 365 @Override 366 public Object getOwnPropertyDescriptor(final String key) { 367 final int index = ArrayIndex.getArrayIndexNoThrow(key); 368 if (index >= 0 && index < value.length()) { 369 final Global global = Global.instance(); 370 return global.newDataDescriptor(String.valueOf(value.charAt(index)), false, true, false); 371 } 372 373 return super.getOwnPropertyDescriptor(key); 374 } 375 376 /** 377 * return a List of own keys associated with the object. 378 * @param all True if to include non-enumerable keys. 379 * @return Array of keys. 380 */ 381 @Override 382 public String[] getOwnKeys(final boolean all) { 383 final List<Object> keys = new ArrayList<>(); 384 385 // add string index keys 386 for (int i = 0; i < value.length(); i++) { 387 keys.add(JSType.toString(i)); 388 } 389 390 // add super class properties 391 keys.addAll(Arrays.asList(super.getOwnKeys(all))); 392 return keys.toArray(new String[keys.size()]); 393 } 394 395 /** 396 * ECMA 15.5.3 String.length 397 * @param self self reference 398 * @return value of length property for string 399 */ 400 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) 401 public static Object length(final Object self) { 402 return getCharSequence(self).length(); 403 } 404 405 /** 406 * ECMA 15.5.3.2 String.fromCharCode ( [ char0 [ , char1 [ , ... ] ] ] ) 407 * @param self self reference 408 * @param args array of arguments to be interpreted as char 409 * @return string with arguments translated to charcodes 410 */ 411 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1, where = Where.CONSTRUCTOR) 412 public static Object fromCharCode(final Object self, final Object... args) { 413 final char[] buf = new char[args.length]; 414 int index = 0; 415 for (final Object arg : args) { 416 buf[index++] = (char)JSType.toUint16(arg); 417 } 418 return new String(buf); 419 } 420 421 /** 422 * ECMA 15.5.3.2 - specialization for one char 423 * @param self self reference 424 * @param value one argument to be interpreted as char 425 * @return string with one charcode 426 */ 427 @SpecializedFunction 428 public static Object fromCharCode(final Object self, final Object value) { 429 try { 430 return "" + (char)JSType.toUint16(((Number)value).doubleValue()); 431 } catch (final ClassCastException e) { 432 return fromCharCode(self, new Object[] { value }); 433 } 434 } 435 436 /** 437 * ECMA 15.5.3.2 - specialization for one char of int type 438 * @param self self reference 439 * @param value one argument to be interpreted as char 440 * @return string with one charcode 441 */ 442 @SpecializedFunction 443 public static Object fromCharCode(final Object self, final int value) { 444 return "" + (char)(value & 0xffff); 445 } 446 447 /** 448 * ECMA 15.5.3.2 - specialization for one char of long type 449 * @param self self reference 450 * @param value one argument to be interpreted as char 451 * @return string with one charcode 452 */ 453 @SpecializedFunction 454 public static Object fromCharCode(final Object self, final long value) { 455 return "" + (char)((int)value & 0xffff); 456 } 457 458 /** 459 * ECMA 15.5.3.2 - specialization for one char of double type 460 * @param self self reference 461 * @param value one argument to be interpreted as char 462 * @return string with one charcode 463 */ 464 @SpecializedFunction 465 public static Object fromCharCode(final Object self, final double value) { 466 return "" + (char)JSType.toUint16(value); 467 } 468 469 /** 470 * ECMA 15.5.4.2 String.prototype.toString ( ) 471 * @param self self reference 472 * @return self as string 473 */ 474 @Function(attributes = Attribute.NOT_ENUMERABLE) 475 public static Object toString(final Object self) { 476 return getString(self); 477 } 478 479 /** 480 * ECMA 15.5.4.3 String.prototype.valueOf ( ) 481 * @param self self reference 482 * @return self as string 483 */ 484 @Function(attributes = Attribute.NOT_ENUMERABLE) 485 public static Object valueOf(final Object self) { 486 return getString(self); 487 } 488 489 /** 490 * ECMA 15.5.4.4 String.prototype.charAt (pos) 491 * @param self self reference 492 * @param pos position in string 493 * @return string representing the char at the given position 494 */ 495 @Function(attributes = Attribute.NOT_ENUMERABLE) 496 public static Object charAt(final Object self, final Object pos) { 497 return charAt(self, JSType.toInteger(pos)); 498 } 499 500 /** 501 * ECMA 15.5.4.4 String.prototype.charAt (pos) - specialized version for double position 502 * @param self self reference 503 * @param pos position in string 504 * @return string representing the char at the given position 505 */ 506 @SpecializedFunction 507 public static String charAt(final Object self, final double pos) { 508 return charAt(self, (int)pos); 509 } 510 511 /** 512 * ECMA 15.5.4.4 String.prototype.charAt (pos) - specialized version for int position 513 * @param self self reference 514 * @param pos position in string 515 * @return string representing the char at the given position 516 */ 517 @SpecializedFunction 518 public static String charAt(final Object self, final int pos) { 519 final String str = checkObjectToString(self); 520 return (pos < 0 || pos >= str.length()) ? "" : String.valueOf(str.charAt(pos)); 521 } 522 523 /** 524 * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) 525 * @param self self reference 526 * @param pos position in string 527 * @return number representing charcode at position 528 */ 529 @Function(attributes = Attribute.NOT_ENUMERABLE) 530 public static Object charCodeAt(final Object self, final Object pos) { 531 return charCodeAt(self, JSType.toInteger(pos)); 532 } 533 534 /** 535 * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) - specialized version for double position 536 * @param self self reference 537 * @param pos position in string 538 * @return number representing charcode at position 539 */ 540 @SpecializedFunction 541 public static double charCodeAt(final Object self, final double pos) { 542 return charCodeAt(self, (int) pos); 543 } 544 545 /** 546 * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) - specialized version for int position 547 * @param self self reference 548 * @param pos position in string 549 * @return number representing charcode at position 550 */ 551 @SpecializedFunction 552 public static double charCodeAt(final Object self, final int pos) { 553 final String str = checkObjectToString(self); 554 return (pos < 0 || pos >= str.length()) ? Double.NaN : str.charAt(pos); 555 } 556 557 /** 558 * ECMA 15.5.4.6 String.prototype.concat ( [ string1 [ , string2 [ , ... ] ] ] ) 559 * @param self self reference 560 * @param args list of string to concatenate 561 * @return concatenated string 562 */ 563 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 564 public static Object concat(final Object self, final Object... args) { 565 CharSequence cs = checkObjectToString(self); 566 if (args != null) { 567 for (final Object obj : args) { 568 cs = new ConsString(cs, JSType.toCharSequence(obj)); 569 } 570 } 571 return cs; 572 } 573 574 /** 575 * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) 576 * @param self self reference 577 * @param search string to search for 578 * @param pos position to start search 579 * @return position of first match or -1 580 */ 581 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 582 public static Object indexOf(final Object self, final Object search, final Object pos) { 583 final String str = checkObjectToString(self); 584 return str.indexOf(JSType.toString(search), JSType.toInteger(pos)); 585 } 586 587 /** 588 * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for no position parameter 589 * @param self self reference 590 * @param search string to search for 591 * @return position of first match or -1 592 */ 593 @SpecializedFunction 594 public static int indexOf(final Object self, final Object search) { 595 return indexOf(self, search, 0); 596 } 597 598 /** 599 * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for double position parameter 600 * @param self self reference 601 * @param search string to search for 602 * @param pos position to start search 603 * @return position of first match or -1 604 */ 605 @SpecializedFunction 606 public static int indexOf(final Object self, final Object search, final double pos) { 607 return indexOf(self, search, (int) pos); 608 } 609 610 /** 611 * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for int position parameter 612 * @param self self reference 613 * @param search string to search for 614 * @param pos position to start search 615 * @return position of first match or -1 616 */ 617 @SpecializedFunction 618 public static int indexOf(final Object self, final Object search, final int pos) { 619 return checkObjectToString(self).indexOf(JSType.toString(search), pos); 620 } 621 622 /** 623 * ECMA 15.5.4.8 String.prototype.lastIndexOf (searchString, position) 624 * @param self self reference 625 * @param search string to search for 626 * @param pos position to start search 627 * @return last position of match or -1 628 */ 629 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 630 public static Object lastIndexOf(final Object self, final Object search, final Object pos) { 631 632 final String str = checkObjectToString(self); 633 final String searchStr = JSType.toString(search); 634 635 int from; 636 637 if (pos == UNDEFINED) { 638 from = str.length(); 639 } else { 640 final double numPos = JSType.toNumber(pos); 641 from = !Double.isNaN(numPos) ? (int)numPos : (int)Double.POSITIVE_INFINITY; 642 } 643 644 return str.lastIndexOf(searchStr, from); 645 } 646 647 /** 648 * ECMA 15.5.4.9 String.prototype.localeCompare (that) 649 * @param self self reference 650 * @param that comparison object 651 * @return result of locale sensitive comparison operation between {@code self} and {@code that} 652 */ 653 @Function(attributes = Attribute.NOT_ENUMERABLE) 654 public static Object localeCompare(final Object self, final Object that) { 655 656 final String str = checkObjectToString(self); 657 final Collator collator = Collator.getInstance(Global.getEnv()._locale); 658 659 collator.setStrength(Collator.IDENTICAL); 660 collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); 661 662 return (double)collator.compare(str, JSType.toString(that)); 663 } 664 665 /** 666 * ECMA 15.5.4.10 String.prototype.match (regexp) 667 * @param self self reference 668 * @param regexp regexp expression 669 * @return array of regexp matches 670 */ 671 @Function(attributes = Attribute.NOT_ENUMERABLE) 672 public static Object match(final Object self, final Object regexp) { 673 674 final String str = checkObjectToString(self); 675 676 NativeRegExp nativeRegExp; 677 if (regexp == UNDEFINED) { 678 nativeRegExp = new NativeRegExp(""); 679 } else { 680 nativeRegExp = Global.toRegExp(regexp); 681 } 682 683 if (!nativeRegExp.getGlobal()) { 684 return nativeRegExp.exec(str); 685 } 686 687 nativeRegExp.setLastIndex(0); 688 689 int previousLastIndex = 0; 690 final List<Object> matches = new ArrayList<>(); 691 692 Object result; 693 while ((result = nativeRegExp.exec(str)) != null) { 694 final int thisIndex = nativeRegExp.getLastIndex(); 695 if (thisIndex == previousLastIndex) { 696 nativeRegExp.setLastIndex(thisIndex + 1); 697 previousLastIndex = thisIndex + 1; 698 } else { 699 previousLastIndex = thisIndex; 700 } 701 matches.add(((ScriptObject)result).get(0)); 702 } 703 704 if (matches.isEmpty()) { 705 return null; 706 } 707 708 return new NativeArray(matches.toArray()); 709 } 710 711 /** 712 * ECMA 15.5.4.11 String.prototype.replace (searchValue, replaceValue) 713 * @param self self reference 714 * @param string item to replace 715 * @param replacement item to replace it with 716 * @return string after replacement 717 */ 718 @Function(attributes = Attribute.NOT_ENUMERABLE) 719 public static Object replace(final Object self, final Object string, final Object replacement) { 720 721 final String str = checkObjectToString(self); 722 723 final NativeRegExp nativeRegExp; 724 if (string instanceof NativeRegExp) { 725 nativeRegExp = (NativeRegExp) string; 726 } else { 727 nativeRegExp = NativeRegExp.flatRegExp(JSType.toString(string)); 728 } 729 730 if (replacement instanceof ScriptFunction) { 731 return nativeRegExp.replace(str, "", (ScriptFunction)replacement); 732 } 733 734 return nativeRegExp.replace(str, JSType.toString(replacement), null); 735 } 736 737 /** 738 * ECMA 15.5.4.12 String.prototype.search (regexp) 739 * 740 * @param self self reference 741 * @param string string to search for 742 * @return offset where match occurred 743 */ 744 @Function(attributes = Attribute.NOT_ENUMERABLE) 745 public static Object search(final Object self, final Object string) { 746 747 final String str = checkObjectToString(self); 748 final NativeRegExp nativeRegExp = Global.toRegExp(string == UNDEFINED ? "" : string); 749 750 return nativeRegExp.search(str); 751 } 752 753 /** 754 * ECMA 15.5.4.13 String.prototype.slice (start, end) 755 * 756 * @param self self reference 757 * @param start start position for slice 758 * @param end end position for slice 759 * @return sliced out substring 760 */ 761 @Function(attributes = Attribute.NOT_ENUMERABLE) 762 public static Object slice(final Object self, final Object start, final Object end) { 763 764 final String str = checkObjectToString(self); 765 if (end == UNDEFINED) { 766 return slice(str, JSType.toInteger(start)); 767 } 768 return slice(str, JSType.toInteger(start), JSType.toInteger(end)); 769 } 770 771 /** 772 * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for single int parameter 773 * 774 * @param self self reference 775 * @param start start position for slice 776 * @return sliced out substring 777 */ 778 @SpecializedFunction 779 public static Object slice(final Object self, final int start) { 780 final String str = checkObjectToString(self); 781 final int from = (start < 0) ? Math.max(str.length() + start, 0) : Math.min(start, str.length()); 782 783 return str.substring(from); 784 } 785 786 /** 787 * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for single double parameter 788 * 789 * @param self self reference 790 * @param start start position for slice 791 * @return sliced out substring 792 */ 793 @SpecializedFunction 794 public static Object slice(final Object self, final double start) { 795 return slice(self, (int)start); 796 } 797 798 /** 799 * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for two int parameters 800 * 801 * @param self self reference 802 * @param start start position for slice 803 * @param end end position for slice 804 * @return sliced out substring 805 */ 806 @SpecializedFunction 807 public static Object slice(final Object self, final int start, final int end) { 808 809 final String str = checkObjectToString(self); 810 final int len = str.length(); 811 812 final int from = (start < 0) ? Math.max(len + start, 0) : Math.min(start, len); 813 final int to = (end < 0) ? Math.max(len + end, 0) : Math.min(end, len); 814 815 return str.substring(Math.min(from, to), to); 816 } 817 818 /** 819 * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for two double parameters 820 * 821 * @param self self reference 822 * @param start start position for slice 823 * @param end end position for slice 824 * @return sliced out substring 825 */ 826 @SpecializedFunction 827 public static Object slice(final Object self, final double start, final double end) { 828 return slice(self, (int)start, (int)end); 829 } 830 831 /** 832 * ECMA 15.5.4.14 String.prototype.split (separator, limit) 833 * 834 * @param self self reference 835 * @param separator separator for split 836 * @param limit limit for splits 837 * @return array object in which splits have been placed 838 */ 839 @Function(attributes = Attribute.NOT_ENUMERABLE) 840 public static Object split(final Object self, final Object separator, final Object limit) { 841 final String str = checkObjectToString(self); 842 final long lim = (limit == UNDEFINED) ? JSType.MAX_UINT : JSType.toUint32(limit); 843 844 if (separator == UNDEFINED) { 845 return lim == 0 ? new NativeArray() : new NativeArray(new Object[]{str}); 846 } 847 848 if (separator instanceof NativeRegExp) { 849 return ((NativeRegExp) separator).split(str, lim); 850 } 851 852 // when separator is a string, it is treated as a literal search string to be used for splitting. 853 return splitString(str, JSType.toString(separator), lim); 854 } 855 856 private static Object splitString(String str, String separator, long limit) { 857 if (separator.isEmpty()) { 858 final int length = (int) Math.min(str.length(), limit); 859 final Object[] array = new Object[length]; 860 for (int i = 0; i < length; i++) { 861 array[i] = String.valueOf(str.charAt(i)); 862 } 863 return new NativeArray(array); 864 } 865 866 final List<String> elements = new LinkedList<>(); 867 final int strLength = str.length(); 868 final int sepLength = separator.length(); 869 int pos = 0; 870 int n = 0; 871 872 while (pos < strLength && n < limit) { 873 int found = str.indexOf(separator, pos); 874 if (found == -1) { 875 break; 876 } 877 elements.add(str.substring(pos, found)); 878 n++; 879 pos = found + sepLength; 880 } 881 if (pos <= strLength && n < limit) { 882 elements.add(str.substring(pos)); 883 } 884 885 return new NativeArray(elements.toArray()); 886 } 887 888 /** 889 * ECMA B.2.3 String.prototype.substr (start, length) 890 * 891 * @param self self reference 892 * @param start start position 893 * @param length length of section 894 * @return substring given start and length of section 895 */ 896 @Function(attributes = Attribute.NOT_ENUMERABLE) 897 public static Object substr(final Object self, final Object start, final Object length) { 898 final String str = JSType.toString(self); 899 final int strLength = str.length(); 900 901 int intStart = JSType.toInteger(start); 902 if (intStart < 0) { 903 intStart = Math.max(intStart + strLength, 0); 904 } 905 906 final int intLen = Math.min(Math.max((length == UNDEFINED) ? Integer.MAX_VALUE : JSType.toInteger(length), 0), strLength - intStart); 907 908 return intLen <= 0 ? "" : str.substring(intStart, intStart + intLen); 909 } 910 911 /** 912 * ECMA 15.5.4.15 String.prototype.substring (start, end) 913 * 914 * @param self self reference 915 * @param start start position of substring 916 * @param end end position of substring 917 * @return substring given start and end indexes 918 */ 919 @Function(attributes = Attribute.NOT_ENUMERABLE) 920 public static Object substring(final Object self, final Object start, final Object end) { 921 922 final String str = checkObjectToString(self); 923 if (end == UNDEFINED) { 924 return substring(str, JSType.toInteger(start)); 925 } 926 return substring(str, JSType.toInteger(start), JSType.toInteger(end)); 927 } 928 929 /** 930 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start parameter 931 * 932 * @param self self reference 933 * @param start start position of substring 934 * @return substring given start and end indexes 935 */ 936 @SpecializedFunction 937 public static String substring(final Object self, final int start) { 938 final String str = checkObjectToString(self); 939 if (start < 0) { 940 return str; 941 } else if (start >= str.length()) { 942 return ""; 943 } else { 944 return str.substring(start); 945 } 946 } 947 948 /** 949 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start parameter 950 * 951 * @param self self reference 952 * @param start start position of substring 953 * @return substring given start and end indexes 954 */ 955 @SpecializedFunction 956 public static String substring(final Object self, final double start) { 957 return substring(self, (int)start); 958 } 959 960 /** 961 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start and end parameters 962 * 963 * @param self self reference 964 * @param start start position of substring 965 * @param end end position of substring 966 * @return substring given start and end indexes 967 */ 968 @SpecializedFunction 969 public static String substring(final Object self, final int start, final int end) { 970 final String str = checkObjectToString(self); 971 final int len = str.length(); 972 final int validStart = start < 0 ? 0 : (start > len ? len : start); 973 final int validEnd = end < 0 ? 0 : (end > len ? len : end); 974 975 if (validStart < validEnd) { 976 return str.substring(validStart, validEnd); 977 } 978 return str.substring(validEnd, validStart); 979 } 980 981 /** 982 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start and end parameters 983 * 984 * @param self self reference 985 * @param start start position of substring 986 * @param end end position of substring 987 * @return substring given start and end indexes 988 */ 989 @SpecializedFunction 990 public static String substring(final Object self, final double start, final double end) { 991 return substring(self, (int)start, (int)end); 992 } 993 994 /** 995 * ECMA 15.5.4.16 String.prototype.toLowerCase ( ) 996 * @param self self reference 997 * @return string to lower case 998 */ 999 @Function(attributes = Attribute.NOT_ENUMERABLE) 1000 public static Object toLowerCase(final Object self) { 1001 return checkObjectToString(self).toLowerCase(Locale.ROOT); 1002 } 1003 1004 /** 1005 * ECMA 15.5.4.17 String.prototype.toLocaleLowerCase ( ) 1006 * @param self self reference 1007 * @return string to locale sensitive lower case 1008 */ 1009 @Function(attributes = Attribute.NOT_ENUMERABLE) 1010 public static Object toLocaleLowerCase(final Object self) { 1011 return checkObjectToString(self).toLowerCase(Global.getEnv()._locale); 1012 } 1013 1014 /** 1015 * ECMA 15.5.4.18 String.prototype.toUpperCase ( ) 1016 * @param self self reference 1017 * @return string to upper case 1018 */ 1019 @Function(attributes = Attribute.NOT_ENUMERABLE) 1020 public static Object toUpperCase(final Object self) { 1021 return checkObjectToString(self).toUpperCase(Locale.ROOT); 1022 } 1023 1024 /** 1025 * ECMA 15.5.4.19 String.prototype.toLocaleUpperCase ( ) 1026 * @param self self reference 1027 * @return string to locale sensitive upper case 1028 */ 1029 @Function(attributes = Attribute.NOT_ENUMERABLE) 1030 public static Object toLocaleUpperCase(final Object self) { 1031 return checkObjectToString(self).toUpperCase(Global.getEnv()._locale); 1032 } 1033 1034 /** 1035 * ECMA 15.5.4.20 String.prototype.trim ( ) 1036 * @param self self reference 1037 * @return string trimmed from whitespace 1038 */ 1039 @Function(attributes = Attribute.NOT_ENUMERABLE) 1040 public static Object trim(final Object self) { 1041 1042 final String str = checkObjectToString(self); 1043 final int len = str.length(); 1044 int start = 0; 1045 int end = len - 1; 1046 1047 while (start <= end && ScriptRuntime.isJSWhitespace(str.charAt(start))) { 1048 start++; 1049 } 1050 while (end > start && ScriptRuntime.isJSWhitespace(str.charAt(end))) { 1051 end--; 1052 } 1053 1054 return start == 0 && end + 1 == len ? str : str.substring(start, end + 1); 1055 } 1056 1057 private static Object newObj(final Object self, final CharSequence str) { 1058 if (self instanceof ScriptObject) { 1059 return new NativeString(str, ((ScriptObject)self).getProto()); 1060 } 1061 return new NativeString(str, Global.instance().getStringPrototype()); 1062 } 1063 1064 /** 1065 * ECMA 15.5.2.1 new String ( [ value ] ) 1066 * 1067 * Constructor 1068 * 1069 * @param newObj is this constructor invoked with the new operator 1070 * @param self self reference 1071 * @param args arguments (a value) 1072 * 1073 * @return new NativeString, empty string if no args, extraneous args ignored 1074 */ 1075 @Constructor(arity = 1) 1076 public static Object constructor(final boolean newObj, final Object self, final Object... args) { 1077 final CharSequence str = (args.length > 0) ? JSType.toCharSequence(args[0]) : ""; 1078 return newObj ? newObj(self, str) : str.toString(); 1079 } 1080 1081 /** 1082 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with no args 1083 * 1084 * Constructor 1085 * 1086 * @param newObj is this constructor invoked with the new operator 1087 * @param self self reference 1088 * 1089 * @return new NativeString ("") 1090 */ 1091 @SpecializedConstructor 1092 public static Object constructor(final boolean newObj, final Object self) { 1093 return newObj ? newObj(self, "") : ""; 1094 } 1095 1096 /** 1097 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with one arg 1098 * 1099 * Constructor 1100 * 1101 * @param newObj is this constructor invoked with the new operator 1102 * @param self self reference 1103 * @param arg argument 1104 * 1105 * @return new NativeString (arg) 1106 */ 1107 @SpecializedConstructor 1108 public static Object constructor(final boolean newObj, final Object self, final Object arg) { 1109 final CharSequence str = JSType.toCharSequence(arg); 1110 return newObj ? newObj(self, str) : str.toString(); 1111 } 1112 1113 /** 1114 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg 1115 * 1116 * Constructor 1117 * 1118 * @param newObj is this constructor invoked with the new operator 1119 * @param self self reference 1120 * @param arg the arg 1121 * 1122 * @return new NativeString containing the string representation of the arg 1123 */ 1124 @SpecializedConstructor 1125 public static Object constructor(final boolean newObj, final Object self, final int arg) { 1126 final String str = JSType.toString(arg); 1127 return newObj ? newObj(self, str) : str; 1128 } 1129 1130 /** 1131 * Lookup the appropriate method for an invoke dynamic call. 1132 * 1133 * @param request the link request 1134 * @param receiver receiver of call 1135 * @return Link to be invoked at call site. 1136 */ 1137 public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) { 1138 final MethodHandle guard = NashornGuards.getInstanceOf2Guard(String.class, ConsString.class); 1139 return PrimitiveLookup.lookupPrimitive(request, guard, new NativeString((CharSequence)receiver), WRAPFILTER); 1140 } 1141 1142 @SuppressWarnings("unused") 1143 private static NativeString wrapFilter(final Object receiver) { 1144 return new NativeString((CharSequence)receiver); 1145 } 1146 1147 private static CharSequence getCharSequence(final Object self) { 1148 if (self instanceof String || self instanceof ConsString) { 1149 return (CharSequence)self; 1150 } else if (self instanceof NativeString) { 1151 return ((NativeString)self).getValue(); 1152 } else if (self != null && self == Global.instance().getStringPrototype()) { 1153 return ""; 1154 } else { 1155 throw typeError("not.a.string", ScriptRuntime.safeToString(self)); 1156 } 1157 } 1158 1159 private static String getString(final Object self) { 1160 if (self instanceof String) { 1161 return (String)self; 1162 } else if (self instanceof ConsString) { 1163 return self.toString(); 1164 } else if (self instanceof NativeString) { 1165 return ((NativeString)self).getStringValue(); 1166 } else if (self != null && self == Global.instance().getStringPrototype()) { 1167 return ""; 1168 } else { 1169 throw typeError( "not.a.string", ScriptRuntime.safeToString(self)); 1170 } 1171 } 1172 1173 /** 1174 * Combines ECMA 9.10 CheckObjectCoercible and ECMA 9.8 ToString with a shortcut for strings. 1175 * 1176 * @param self the object 1177 * @return the object as string 1178 */ 1179 private static String checkObjectToString(final Object self) { 1180 if (self instanceof String) { 1181 return (String)self; 1182 } else if (self instanceof ConsString) { 1183 return self.toString(); 1184 } else { 1185 Global.checkObjectCoercible(self); 1186 return JSType.toString(self); 1187 } 1188 } 1189 1190 private boolean isValid(final int key) { 1191 return key >= 0 && key < value.length(); 1192 } 1193 1194 private static MethodHandle findWrapFilter() { 1195 try { 1196 return MethodHandles.lookup().findStatic(NativeString.class, "wrapFilter", MH.type(NativeString.class, Object.class)); 1197 } catch (final NoSuchMethodException | IllegalAccessException e) { 1198 throw new MethodHandleFactory.LookupException(e); 1199 } 1200 } 1201 }