1 /* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.objects; 27 28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; 29 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 31 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow; 32 import static jdk.nashorn.internal.lookup.Lookup.MH; 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.objects.annotations.Attribute; 45 import jdk.nashorn.internal.objects.annotations.Constructor; 46 import jdk.nashorn.internal.objects.annotations.Function; 47 import jdk.nashorn.internal.objects.annotations.Getter; 48 import jdk.nashorn.internal.objects.annotations.ScriptClass; 49 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor; 50 import jdk.nashorn.internal.objects.annotations.SpecializedFunction; 51 import jdk.nashorn.internal.objects.annotations.Where; 52 import jdk.nashorn.internal.runtime.ConsString; 53 import jdk.nashorn.internal.runtime.JSType; 54 import jdk.nashorn.internal.runtime.ScriptFunction; 55 import jdk.nashorn.internal.runtime.ScriptObject; 56 import jdk.nashorn.internal.runtime.ScriptRuntime; 57 import jdk.nashorn.internal.runtime.arrays.ArrayIndex; 58 import jdk.nashorn.internal.lookup.MethodHandleFactory; 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 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 Object[] array = new Object[str.length()]; 858 for (int i = 0; i < array.length; i++) { 859 array[i] = String.valueOf(str.charAt(i)); 860 } 861 return new NativeArray(array); 862 } 863 864 final List<String> elements = new LinkedList<>(); 865 final int strLength = str.length(); 866 final int sepLength = separator.length(); 867 int pos = 0; 868 int n = 0; 869 870 while (pos < strLength && n < limit) { 871 int found = str.indexOf(separator, pos); 872 if (found == -1) { 873 break; 874 } 875 elements.add(str.substring(pos, found)); 876 n++; 877 pos = found + sepLength; 878 } 879 if (pos <= strLength && n < limit) { 880 elements.add(str.substring(pos)); 881 } 882 883 return new NativeArray(elements.toArray()); 884 } 885 886 /** 887 * ECMA B.2.3 String.prototype.substr (start, length) 888 * 889 * @param self self reference 890 * @param start start position 891 * @param length length of section 892 * @return substring given start and length of section 893 */ 894 @Function(attributes = Attribute.NOT_ENUMERABLE) 895 public static Object substr(final Object self, final Object start, final Object length) { 896 final String str = JSType.toString(self); 897 final int strLength = str.length(); 898 899 int intStart = JSType.toInteger(start); 900 if (intStart < 0) { 901 intStart = Math.max(intStart + strLength, 0); 902 } 903 904 final int intLen = Math.min(Math.max((length == UNDEFINED) ? Integer.MAX_VALUE : JSType.toInteger(length), 0), strLength - intStart); 905 906 return intLen <= 0 ? "" : str.substring(intStart, intStart + intLen); 907 } 908 909 /** 910 * ECMA 15.5.4.15 String.prototype.substring (start, end) 911 * 912 * @param self self reference 913 * @param start start position of substring 914 * @param end end position of substring 915 * @return substring given start and end indexes 916 */ 917 @Function(attributes = Attribute.NOT_ENUMERABLE) 918 public static Object substring(final Object self, final Object start, final Object end) { 919 920 final String str = checkObjectToString(self); 921 if (end == UNDEFINED) { 922 return substring(str, JSType.toInteger(start)); 923 } 924 return substring(str, JSType.toInteger(start), JSType.toInteger(end)); 925 } 926 927 /** 928 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start parameter 929 * 930 * @param self self reference 931 * @param start start position of substring 932 * @return substring given start and end indexes 933 */ 934 @SpecializedFunction 935 public static String substring(final Object self, final int start) { 936 final String str = checkObjectToString(self); 937 if (start < 0) { 938 return str; 939 } else if (start >= str.length()) { 940 return ""; 941 } else { 942 return str.substring(start); 943 } 944 } 945 946 /** 947 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start parameter 948 * 949 * @param self self reference 950 * @param start start position of substring 951 * @return substring given start and end indexes 952 */ 953 @SpecializedFunction 954 public static String substring(final Object self, final double start) { 955 return substring(self, (int)start); 956 } 957 958 /** 959 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start and end parameters 960 * 961 * @param self self reference 962 * @param start start position of substring 963 * @param end end position of substring 964 * @return substring given start and end indexes 965 */ 966 @SpecializedFunction 967 public static String substring(final Object self, final int start, final int end) { 968 final String str = checkObjectToString(self); 969 final int len = str.length(); 970 final int validStart = start < 0 ? 0 : (start > len ? len : start); 971 final int validEnd = end < 0 ? 0 : (end > len ? len : end); 972 973 if (validStart < validEnd) { 974 return str.substring(validStart, validEnd); 975 } 976 return str.substring(validEnd, validStart); 977 } 978 979 /** 980 * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start and end parameters 981 * 982 * @param self self reference 983 * @param start start position of substring 984 * @param end end position of substring 985 * @return substring given start and end indexes 986 */ 987 @SpecializedFunction 988 public static String substring(final Object self, final double start, final double end) { 989 return substring(self, (int)start, (int)end); 990 } 991 992 /** 993 * ECMA 15.5.4.16 String.prototype.toLowerCase ( ) 994 * @param self self reference 995 * @return string to lower case 996 */ 997 @Function(attributes = Attribute.NOT_ENUMERABLE) 998 public static Object toLowerCase(final Object self) { 999 return checkObjectToString(self).toLowerCase(); 1000 } 1001 1002 /** 1003 * ECMA 15.5.4.17 String.prototype.toLocaleLowerCase ( ) 1004 * @param self self reference 1005 * @return string to locale sensitive lower case 1006 */ 1007 @Function(attributes = Attribute.NOT_ENUMERABLE) 1008 public static Object toLocaleLowerCase(final Object self) { 1009 return checkObjectToString(self).toLowerCase(Global.getEnv()._locale); 1010 } 1011 1012 /** 1013 * ECMA 15.5.4.18 String.prototype.toUpperCase ( ) 1014 * @param self self reference 1015 * @return string to upper case 1016 */ 1017 @Function(attributes = Attribute.NOT_ENUMERABLE) 1018 public static Object toUpperCase(final Object self) { 1019 return checkObjectToString(self).toUpperCase(); 1020 } 1021 1022 /** 1023 * ECMA 15.5.4.19 String.prototype.toLocaleUpperCase ( ) 1024 * @param self self reference 1025 * @return string to locale sensitive upper case 1026 */ 1027 @Function(attributes = Attribute.NOT_ENUMERABLE) 1028 public static Object toLocaleUpperCase(final Object self) { 1029 return checkObjectToString(self).toUpperCase(Global.getEnv()._locale); 1030 } 1031 1032 /** 1033 * ECMA 15.5.4.20 String.prototype.trim ( ) 1034 * @param self self reference 1035 * @return string trimmed from whitespace 1036 */ 1037 @Function(attributes = Attribute.NOT_ENUMERABLE) 1038 public static Object trim(final Object self) { 1039 1040 final String str = checkObjectToString(self); 1041 final int len = str.length(); 1042 int start = 0; 1043 int end = len - 1; 1044 1045 while (start <= end && ScriptRuntime.isJSWhitespace(str.charAt(start))) { 1046 start++; 1047 } 1048 while (end > start && ScriptRuntime.isJSWhitespace(str.charAt(end))) { 1049 end--; 1050 } 1051 1052 return start == 0 && end + 1 == len ? str : str.substring(start, end + 1); 1053 } 1054 1055 private static Object newObj(final Object self, final CharSequence str) { 1056 if (self instanceof ScriptObject) { 1057 return new NativeString(str, ((ScriptObject)self).getProto()); 1058 } 1059 return new NativeString(str, Global.instance().getStringPrototype()); 1060 } 1061 1062 /** 1063 * ECMA 15.5.2.1 new String ( [ value ] ) 1064 * 1065 * Constructor 1066 * 1067 * @param newObj is this constructor invoked with the new operator 1068 * @param self self reference 1069 * @param args arguments (a value) 1070 * 1071 * @return new NativeString, empty string if no args, extraneous args ignored 1072 */ 1073 @Constructor(arity = 1) 1074 public static Object constructor(final boolean newObj, final Object self, final Object... args) { 1075 final CharSequence str = (args.length > 0) ? JSType.toCharSequence(args[0]) : ""; 1076 return newObj ? newObj(self, str) : str.toString(); 1077 } 1078 1079 /** 1080 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with no args 1081 * 1082 * Constructor 1083 * 1084 * @param newObj is this constructor invoked with the new operator 1085 * @param self self reference 1086 * 1087 * @return new NativeString ("") 1088 */ 1089 @SpecializedConstructor 1090 public static Object constructor(final boolean newObj, final Object self) { 1091 return newObj ? newObj(self, "") : ""; 1092 } 1093 1094 /** 1095 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with one arg 1096 * 1097 * Constructor 1098 * 1099 * @param newObj is this constructor invoked with the new operator 1100 * @param self self reference 1101 * @param arg argument 1102 * 1103 * @return new NativeString (arg) 1104 */ 1105 @SpecializedConstructor 1106 public static Object constructor(final boolean newObj, final Object self, final Object arg) { 1107 final CharSequence str = JSType.toCharSequence(arg); 1108 return newObj ? newObj(self, str) : str.toString(); 1109 } 1110 1111 /** 1112 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg 1113 * 1114 * Constructor 1115 * 1116 * @param newObj is this constructor invoked with the new operator 1117 * @param self self reference 1118 * @param arg the arg 1119 * 1120 * @return new NativeString containing the string representation of the arg 1121 */ 1122 @SpecializedConstructor 1123 public static Object constructor(final boolean newObj, final Object self, final int arg) { 1124 final String str = JSType.toString(arg); 1125 return newObj ? newObj(self, str) : str; 1126 } 1127 1128 /** 1129 * Lookup the appropriate method for an invoke dynamic call. 1130 * 1131 * @param request the link request 1132 * @param receiver receiver of call 1133 * @return Link to be invoked at call site. 1134 */ 1135 public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) { 1136 final MethodHandle guard = NashornGuards.getInstanceOf2Guard(String.class, ConsString.class); 1137 return PrimitiveLookup.lookupPrimitive(request, guard, new NativeString((CharSequence)receiver), WRAPFILTER); 1138 } 1139 1140 @SuppressWarnings("unused") 1141 private static NativeString wrapFilter(final Object receiver) { 1142 return new NativeString((CharSequence)receiver); 1143 } 1144 1145 private static CharSequence getCharSequence(final Object self) { 1146 if (self instanceof String || self instanceof ConsString) { 1147 return (CharSequence)self; 1148 } else if (self instanceof NativeString) { 1149 return ((NativeString)self).getValue(); 1150 } else if (self != null && self == Global.instance().getStringPrototype()) { 1151 return ""; 1152 } else { 1153 throw typeError("not.a.string", ScriptRuntime.safeToString(self)); 1154 } 1155 } 1156 1157 private static String getString(final Object self) { 1158 if (self instanceof String) { 1159 return (String)self; 1160 } else if (self instanceof ConsString) { 1161 return self.toString(); 1162 } else if (self instanceof NativeString) { 1163 return ((NativeString)self).getStringValue(); 1164 } else if (self != null && self == Global.instance().getStringPrototype()) { 1165 return ""; 1166 } else { 1167 throw typeError( "not.a.string", ScriptRuntime.safeToString(self)); 1168 } 1169 } 1170 1171 /** 1172 * Combines ECMA 9.10 CheckObjectCoercible and ECMA 9.8 ToString with a shortcut for strings. 1173 * 1174 * @param self the object 1175 * @return the object as string 1176 */ 1177 private static String checkObjectToString(final Object self) { 1178 if (self instanceof String) { 1179 return (String)self; 1180 } else if (self instanceof ConsString) { 1181 return self.toString(); 1182 } else { 1183 Global.checkObjectCoercible(self); 1184 return JSType.toString(self); 1185 } 1186 } 1187 1188 private boolean isValid(final int key) { 1189 return key >= 0 && key < value.length(); 1190 } 1191 1192 private static MethodHandle findWrapFilter() { 1193 try { 1194 return MethodHandles.lookup().findStatic(NativeString.class, "wrapFilter", MH.type(NativeString.class, Object.class)); 1195 } catch (final NoSuchMethodException | IllegalAccessException e) { 1196 throw new MethodHandleFactory.LookupException(e); 1197 } 1198 } 1199 }