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