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