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