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