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