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