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