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.runtime.ECMAErrors.typeError; 29 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt; 30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; 31 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow; 32 import static jdk.nashorn.internal.runtime.linker.Lookup.MH; 33 34 import java.lang.invoke.MethodHandle; 35 import java.lang.invoke.MethodHandles; 36 import java.text.Collator; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 import java.util.regex.Pattern; 41 import jdk.nashorn.internal.objects.annotations.Attribute; 42 import jdk.nashorn.internal.objects.annotations.Constructor; 43 import jdk.nashorn.internal.objects.annotations.Function; 44 import jdk.nashorn.internal.objects.annotations.Getter; 45 import jdk.nashorn.internal.objects.annotations.ScriptClass; 46 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor; 47 import jdk.nashorn.internal.objects.annotations.SpecializedFunction; 48 import jdk.nashorn.internal.objects.annotations.Where; 49 import jdk.nashorn.internal.parser.Lexer; 50 import jdk.nashorn.internal.runtime.ConsString; 51 import jdk.nashorn.internal.runtime.JSType; 52 import jdk.nashorn.internal.runtime.ScriptFunction; 53 import jdk.nashorn.internal.runtime.ScriptObject; 54 import jdk.nashorn.internal.runtime.ScriptRuntime; 55 import jdk.nashorn.internal.runtime.arrays.ArrayIndex; 56 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; 57 import jdk.nashorn.internal.runtime.linker.NashornGuards; 58 import jdk.nashorn.internal.runtime.linker.PrimitiveLookup; 59 import org.dynalang.dynalink.linker.GuardedInvocation; 60 61 62 /** 63 * ECMA 15.5 String Objects. 64 */ 65 @ScriptClass("String") 66 public final class NativeString extends ScriptObject { 67 68 private final CharSequence value; 69 70 private static final MethodHandle WRAPFILTER = findWrapFilter(); 71 72 NativeString(final CharSequence value) { 73 this(value, Global.instance().getStringPrototype()); 74 } 75 76 private NativeString(final CharSequence value, final ScriptObject proto) { 77 assert value instanceof String || value instanceof ConsString; 78 this.value = value; 79 this.setProto(proto); 80 } 81 82 @Override 83 public String safeToString() { 84 return "[String " + toString() + "]"; 85 } 86 87 @Override 88 public String toString() { 89 return getStringValue(); 90 } 91 92 @Override 93 public boolean equals(final Object other) { 94 if (other instanceof NativeString) { 95 return getStringValue().equals(((NativeString) other).getStringValue()); 96 } 97 98 return false; 99 } 100 101 @Override 102 public int hashCode() { 103 return getStringValue().hashCode(); 104 } 105 106 private String getStringValue() { 107 return value instanceof String ? (String) value : value.toString(); 108 } 109 110 private CharSequence getValue() { 111 return value; 112 } 113 114 @Override 115 public String getClassName() { 116 return "String"; 117 } 118 119 @Override 120 public Object getLength() { 121 return value.length(); 122 } 123 124 // String characters can be accessed with array-like indexing.. 125 @Override 126 public Object get(final Object key) { 127 final int index = getArrayIndexNoThrow(key); 128 if (index >= 0 && index < value.length()) { 129 return String.valueOf(value.charAt(index)); 130 } 131 return super.get(key); 132 } 133 134 @Override 135 public Object get(final double key) { 136 if (isRepresentableAsInt(key)) { 137 return get((int)key); 138 } 139 return super.get(key); 140 } 141 142 @Override 143 public Object get(final long key) { 144 if (key >= 0 && key < value.length()) { 145 return String.valueOf(value.charAt((int)key)); 146 } 147 return super.get(key); 148 } 149 150 @Override 151 public Object get(final int key) { 152 if (key >= 0 && key < value.length()) { 153 return String.valueOf(value.charAt(key)); 154 } 155 return super.get(key); 156 } 157 158 @Override 159 public int getInt(final Object key) { 160 return JSType.toInt32(get(key)); 161 } 162 163 @Override 164 public int getInt(final double key) { 165 return JSType.toInt32(get(key)); 166 } 167 168 @Override 169 public int getInt(final long key) { 170 return JSType.toInt32(get(key)); 171 } 172 173 @Override 174 public int getInt(final int key) { 175 return JSType.toInt32(get(key)); 176 } 177 178 @Override 179 public long getLong(final Object key) { 180 return JSType.toUint32(get(key)); 181 } 182 183 @Override 184 public long getLong(final double key) { 185 return JSType.toUint32(get(key)); 186 } 187 188 @Override 189 public long getLong(final long key) { 190 return JSType.toUint32(get(key)); 191 } 192 193 @Override 194 public long getLong(final int key) { 195 return JSType.toUint32(get(key)); 196 } 197 198 @Override 199 public double getDouble(final Object key) { 200 return JSType.toNumber(get(key)); 201 } 202 203 @Override 204 public double getDouble(final double key) { 205 return JSType.toNumber(get(key)); 206 } 207 208 @Override 209 public double getDouble(final long key) { 210 return JSType.toNumber(get(key)); 211 } 212 213 @Override 214 public double getDouble(final int key) { 215 return JSType.toNumber(get(key)); 216 } 217 218 @Override 219 public boolean has(final Object key) { 220 final int index = getArrayIndexNoThrow(key); 221 return isValid(index) || super.has(key); 222 } 223 224 @Override 225 public boolean has(final int key) { 226 return isValid(key) || super.has(key); 227 } 228 229 @Override 230 public boolean has(final long key) { 231 final int index = getArrayIndexNoThrow(key); 232 return isValid(index) || super.has(key); 233 } 234 235 @Override 236 public boolean has(final double key) { 237 final int index = getArrayIndexNoThrow(key); 238 return isValid(index) || super.has(key); 239 } 240 241 @Override 242 public boolean hasOwnProperty(final Object key) { 243 final int index = getArrayIndexNoThrow(key); 244 return isValid(index) || super.hasOwnProperty(key); 245 } 246 247 @Override 248 public boolean hasOwnProperty(final int key) { 249 return isValid(key) || super.hasOwnProperty(key); 250 } 251 252 @Override 253 public boolean hasOwnProperty(final long key) { 254 final int index = getArrayIndexNoThrow(key); 255 return isValid(index) || super.hasOwnProperty(key); 256 } 257 258 @Override 259 public boolean hasOwnProperty(final double key) { 260 final int index = getArrayIndexNoThrow(key); 261 return isValid(index) || super.hasOwnProperty(key); 262 } 263 264 @Override 265 public boolean delete(final int key, final boolean strict) { 266 return checkDeleteIndex(key, strict)? false : super.delete(key, strict); 267 } 268 269 @Override 270 public boolean delete(final long key, final boolean strict) { 271 final int index = getArrayIndexNoThrow(key); 272 return checkDeleteIndex(index, strict)? false : super.delete(key, strict); 273 } 274 275 @Override 276 public boolean delete(final double key, final boolean strict) { 277 final int index = getArrayIndexNoThrow(key); 278 return checkDeleteIndex(index, strict)? false : super.delete(key, strict); 279 } 280 281 @Override 282 public boolean delete(final Object key, final boolean strict) { 283 final int index = getArrayIndexNoThrow(key); 284 return checkDeleteIndex(index, strict)? false : super.delete(key, strict); 285 } 286 287 private boolean checkDeleteIndex(final int index, final boolean strict) { 288 if (isValid(index)) { 289 if (strict) { 290 typeError(Global.instance(), "cant.delete.property", Integer.toString(index), ScriptRuntime.safeToString(this)); 291 } 292 return true; 293 } 294 295 return false; 296 } 297 298 @Override 299 public Object getOwnPropertyDescriptor(final String key) { 300 final int index = ArrayIndex.getArrayIndexNoThrow(key); 301 if (index >= 0 && index < value.length()) { 302 final Global global = Global.instance(); 303 return global.newDataDescriptor(String.valueOf(value.charAt(index)), false, true, false); 304 } 305 306 return super.getOwnPropertyDescriptor(key); 307 } 308 309 /** 310 * return a List of own keys associated with the object. 311 * @param all True if to include non-enumerable keys. 312 * @return Array of keys. 313 */ 314 @Override 315 public String[] getOwnKeys(final boolean all) { 316 final List<Object> keys = new ArrayList<>(); 317 318 // add string index keys 319 for (int i = 0; i < value.length(); i++) { 320 keys.add(JSType.toString(i)); 321 } 322 323 // add super class properties 324 keys.addAll(Arrays.asList(super.getOwnKeys(all))); 325 return keys.toArray(new String[keys.size()]); 326 } 327 328 /** 329 * ECMA 15.5.3 String.length 330 * @param self self reference 331 * @return value of length property for string 332 */ 333 @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE) 334 public static Object length(final Object self) { 335 return getCharSequence(self).length(); 336 } 337 338 /** 339 * ECMA 15.5.3.2 String.fromCharCode ( [ char0 [ , char1 [ , ... ] ] ] ) 340 * @param self self reference 341 * @param args array of arguments to be interpreted as char 342 * @return string with arguments translated to charcodes 343 */ 344 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1, where = Where.CONSTRUCTOR) 345 public static Object fromCharCode(final Object self, final Object... args) { 346 final char[] buf = new char[args.length]; 347 int index = 0; 348 for (final Object arg : args) { 349 buf[index++] = (char)JSType.toUint16(arg); 350 } 351 return new String(buf); 352 } 353 354 /** 355 * ECMA 15.5.3.2 - specialization for one char 356 * @param self self reference 357 * @param value one argument to be interpreted as char 358 * @return string with one charcode 359 */ 360 @SpecializedFunction 361 public static Object fromCharCode(final Object self, final Object value) { 362 try { 363 return "" + (char)JSType.toUint16(((Number)value).doubleValue()); 364 } catch (final ClassCastException e) { 365 return fromCharCode(self, new Object[] { value }); 366 } 367 } 368 369 /** 370 * ECMA 15.5.3.2 - specialization for one char of int type 371 * @param self self reference 372 * @param value one argument to be interpreted as char 373 * @return string with one charcode 374 */ 375 @SpecializedFunction 376 public static Object fromCharCode(final Object self, final int value) { 377 return "" + (char)(value & 0xffff); 378 } 379 380 /** 381 * ECMA 15.5.3.2 - specialization for one char of long type 382 * @param self self reference 383 * @param value one argument to be interpreted as char 384 * @return string with one charcode 385 */ 386 @SpecializedFunction 387 public static Object fromCharCode(final Object self, final long value) { 388 return "" + (char)((int)value & 0xffff); 389 } 390 391 /** 392 * ECMA 15.5.3.2 - specialization for one char of double type 393 * @param self self reference 394 * @param value one argument to be interpreted as char 395 * @return string with one charcode 396 */ 397 @SpecializedFunction 398 public static Object fromCharCode(final Object self, final double value) { 399 return "" + (char)JSType.toUint16(value); 400 } 401 402 /** 403 * ECMA 15.5.4.2 String.prototype.toString ( ) 404 * @param self self reference 405 * @return self as string 406 */ 407 @Function(attributes = Attribute.NOT_ENUMERABLE) 408 public static Object toString(final Object self) { 409 return getString(self); 410 } 411 412 /** 413 * ECMA 15.5.4.3 String.prototype.valueOf ( ) 414 * @param self self reference 415 * @return self as string 416 */ 417 @Function(attributes = Attribute.NOT_ENUMERABLE) 418 public static Object valueOf(final Object self) { 419 return getString(self); 420 } 421 422 /** 423 * ECMA 15.5.4.4 String.prototype.charAt (pos) 424 * @param self self reference 425 * @param pos position in string 426 * @return string representing the char at the given position 427 */ 428 @Function(attributes = Attribute.NOT_ENUMERABLE) 429 public static Object charAt(final Object self, final Object pos) { 430 try { 431 return String.valueOf(((String)self).charAt(((Number)pos).intValue())); 432 } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) { 433 Global.checkObjectCoercible(self); 434 final String str = JSType.toString(self); 435 final int at = JSType.toInteger(pos); 436 if (at < 0 || at >= str.length()) { 437 return ""; 438 } 439 return String.valueOf(str.charAt(at)); 440 } 441 } 442 443 /** 444 * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) 445 * @param self self reference 446 * @param pos position in string 447 * @return number representing charcode at position 448 */ 449 @Function(attributes = Attribute.NOT_ENUMERABLE) 450 public static Object charCodeAt(final Object self, final Object pos) { 451 try { 452 return (int)((String)self).charAt(((Number)pos).intValue()); 453 } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) { 454 Global.checkObjectCoercible(self); 455 final String str = JSType.toString(self); 456 final int at = JSType.toInteger(pos); 457 if (at < 0 || at >= str.length()) { 458 return Double.NaN; 459 } 460 461 return JSType.toObject(str.charAt(at)); 462 } 463 } 464 465 /** 466 * ECMA 15.5.4.6 String.prototype.concat ( [ string1 [ , string2 [ , ... ] ] ] ) 467 * @param self self reference 468 * @param args list of string to concatenate 469 * @return concatenated string 470 */ 471 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 472 public static Object concat(final Object self, final Object... args) { 473 Global.checkObjectCoercible(self); 474 final StringBuilder sb = new StringBuilder(JSType.toString(self)); 475 if (args != null) { 476 for (final Object obj : args) { 477 sb.append(JSType.toString(obj)); 478 } 479 } 480 return sb.toString(); 481 } 482 483 /** 484 * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) 485 * @param self self reference 486 * @param search string to search for 487 * @param pos position to start search 488 * @return position of first match or -1 489 */ 490 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 491 public static Object indexOf(final Object self, final Object search, final Object pos) { 492 try { 493 return ((String)self).indexOf((String)search, ((Number)pos).intValue()); //assuming that the conversions really mean "toInteger" and not "toInt32" this is ok. 494 } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) { 495 Global.checkObjectCoercible(self); 496 return JSType.toString(self).indexOf(JSType.toString(search), JSType.toInteger(pos)); 497 } 498 } 499 500 /** 501 * ECMA 15.5.4.8 String.prototype.lastIndexOf (searchString, position) 502 * @param self self reference 503 * @param search string to search for 504 * @param pos position to start search 505 * @return last position of match or -1 506 */ 507 @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1) 508 public static Object lastIndexOf(final Object self, final Object search, final Object pos) { 509 Global.checkObjectCoercible(self); 510 511 final String str = JSType.toString(self); 512 final String searchStr = JSType.toString(search); 513 514 int from; 515 516 if (pos == UNDEFINED) { 517 from = str.length(); 518 } else { 519 final double numPos = JSType.toNumber(pos); 520 from = !Double.isNaN(numPos) ? (int)numPos : (int)Double.POSITIVE_INFINITY; 521 } 522 523 return str.lastIndexOf(searchStr, from); 524 } 525 526 /** 527 * ECMA 15.5.4.9 String.prototype.localeCompare (that) 528 * @param self self reference 529 * @param that comparison object 530 * @return result of locale sensitive comparison operation between {@code self} and {@code that} 531 */ 532 @Function(attributes = Attribute.NOT_ENUMERABLE) 533 public static Object localeCompare(final Object self, final Object that) { 534 Global.checkObjectCoercible(self); 535 536 final String str = JSType.toString(self); 537 final Collator collator = Collator.getInstance(Global.getThisContext().getLocale()); 538 539 collator.setStrength(Collator.IDENTICAL); 540 collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); 541 542 return (double)collator.compare(str, JSType.toString(that)); 543 } 544 545 /** 546 * ECMA 15.5.4.10 String.prototype.match (regexp) 547 * @param self self reference 548 * @param regexp regexp expression 549 * @return array of regexp matches 550 */ 551 @Function(attributes = Attribute.NOT_ENUMERABLE) 552 public static Object match(final Object self, final Object regexp) { 553 Global.checkObjectCoercible(self); 554 555 final String str = JSType.toString(self); 556 557 NativeRegExp nativeRegExp; 558 if (regexp == UNDEFINED) { 559 nativeRegExp = new NativeRegExp(""); 560 } else { 561 nativeRegExp = Global.toRegExp(regexp); 562 } 563 564 if (!nativeRegExp.getGlobal()) { 565 return nativeRegExp.exec(str); 566 } 567 568 nativeRegExp.setLastIndex(0); 569 570 int previousLastIndex = 0; 571 final List<Object> matches = new ArrayList<>(); 572 573 Object result; 574 while ((result = nativeRegExp.exec(str)) != null) { 575 final int thisIndex = nativeRegExp.getLastIndex(); 576 if (thisIndex == previousLastIndex) { 577 nativeRegExp.setLastIndex(thisIndex + 1); 578 previousLastIndex = thisIndex + 1; 579 } else { 580 previousLastIndex = thisIndex; 581 } 582 matches.add(((ScriptObject)result).get(0)); 583 } 584 585 if (matches.isEmpty()) { 586 return null; 587 } 588 589 return new NativeArray(matches.toArray()); 590 } 591 592 /** 593 * ECMA 15.5.4.11 String.prototype.replace (searchValue, replaceValue) 594 * @param self self reference 595 * @param string item to replace 596 * @param replacement item to replace it with 597 * @return string after replacement 598 */ 599 @Function(attributes = Attribute.NOT_ENUMERABLE) 600 public static Object replace(final Object self, final Object string, final Object replacement) { 601 Global.checkObjectCoercible(self); 602 603 final String str = JSType.toString(self); 604 605 final NativeRegExp nativeRegExp; 606 if (string instanceof NativeRegExp) { 607 nativeRegExp = (NativeRegExp) string; 608 } else { 609 nativeRegExp = new NativeRegExp(Pattern.compile(JSType.toString(string), Pattern.LITERAL)); 610 } 611 612 if (replacement instanceof ScriptFunction) { 613 return nativeRegExp.replace(str, "", (ScriptFunction)replacement); 614 } 615 616 return nativeRegExp.replace(str, JSType.toString(replacement), null); 617 } 618 619 /** 620 * ECMA 15.5.4.12 String.prototype.search (regexp) 621 * 622 * @param self self reference 623 * @param string string to search for 624 * @return offset where match occurred 625 */ 626 @Function(attributes = Attribute.NOT_ENUMERABLE) 627 public static Object search(final Object self, final Object string) { 628 Global.checkObjectCoercible(self); 629 630 final String str = JSType.toString(self); 631 final NativeRegExp nativeRegExp = Global.toRegExp(string == UNDEFINED ? "" : string); 632 633 return nativeRegExp.search(str); 634 } 635 636 /** 637 * ECMA 15.5.4.13 String.prototype.slice (start, end) 638 * 639 * @param self self reference 640 * @param start start position for slice 641 * @param end end position for slice 642 * @return sliced out substring 643 */ 644 @Function(attributes = Attribute.NOT_ENUMERABLE) 645 public static Object slice(final Object self, final Object start, final Object end) { 646 Global.checkObjectCoercible(self); 647 648 final String str = JSType.toString(self); 649 final int len = str.length(); 650 final int intStart = JSType.toInteger(start); 651 final int intEnd = (end == UNDEFINED) ? len : JSType.toInteger(end); 652 653 int from; 654 655 if (intStart < 0) { 656 from = Math.max(len + intStart, 0); 657 } else { 658 from = Math.min(intStart, len); 659 } 660 661 int to; 662 663 if (intEnd < 0) { 664 to = Math.max(len + intEnd,0); 665 } else { 666 to = Math.min(intEnd, len); 667 } 668 669 return str.substring(Math.min(from, to), to); 670 } 671 672 /** 673 * ECMA 15.5.4.14 String.prototype.split (separator, limit) 674 * 675 * @param self self reference 676 * @param separator separator for split 677 * @param limit limit for splits 678 * @return array object in which splits have been placed 679 */ 680 @Function(attributes = Attribute.NOT_ENUMERABLE) 681 public static Object split(final Object self, final Object separator, final Object limit) { 682 Global.checkObjectCoercible(self); 683 684 final String str = JSType.toString(self); 685 686 if (separator == UNDEFINED) { 687 return new NativeArray(new Object[]{str}); 688 } 689 690 final long lim = (limit == UNDEFINED) ? JSType.MAX_UINT : JSType.toUint32(limit); 691 692 if (separator instanceof NativeRegExp) { 693 return ((NativeRegExp) separator).split(str, lim); 694 } 695 696 // when separator is a string, it has to be treated as a 697 // literal search string to be used for splitting. 698 return new NativeRegExp(Pattern.compile(JSType.toString(separator), Pattern.LITERAL)).split(str, lim); 699 } 700 701 /** 702 * ECMA B.2.3 String.prototype.substr (start, length) 703 * 704 * @param self self reference 705 * @param start start position 706 * @param length length of section 707 * @return substring given start and length of section 708 */ 709 @Function(attributes = Attribute.NOT_ENUMERABLE) 710 public static Object substr(final Object self, final Object start, final Object length) { 711 final String str = JSType.toString(self); 712 final int strLength = str.length(); 713 714 int intStart = JSType.toInteger(start); 715 if (intStart < 0) { 716 intStart = Math.max(intStart + strLength, 0); 717 } 718 719 final int intLen = Math.min(Math.max((length == UNDEFINED) ? Integer.MAX_VALUE : JSType.toInteger(length), 0), strLength - intStart); 720 721 return intLen <= 0 ? "" : str.substring(intStart, intStart + intLen); 722 } 723 724 /** 725 * ECMA 15.5.4.15 String.prototype.substring (start, end) 726 * 727 * @param self self reference 728 * @param start start position of substring 729 * @param end end position of substring 730 * @return substring given start and end indexes 731 */ 732 @Function(attributes = Attribute.NOT_ENUMERABLE) 733 public static Object substring(final Object self, final Object start, final Object end) { 734 Global.checkObjectCoercible(self); 735 736 final String str = JSType.toString(self); 737 final int len = str.length(); 738 final int intStart = JSType.toInteger(start); 739 final int intEnd = (end == UNDEFINED) ? len : JSType.toInteger(end); 740 final int finalStart = Math.min((intStart < 0) ? 0 : intStart, len); 741 final int finalEnd = Math.min((intEnd < 0) ? 0 : intEnd, len); 742 743 int from, to; 744 745 if (finalStart < finalEnd) { 746 from = finalStart; 747 to = finalEnd; 748 } else { 749 from = finalEnd; 750 to = finalStart; 751 } 752 return str.substring(from, to); 753 } 754 755 /** 756 * ECMA 15.5.4.16 String.prototype.toLowerCase ( ) 757 * @param self self reference 758 * @return string to lower case 759 */ 760 @Function(attributes = Attribute.NOT_ENUMERABLE) 761 public static Object toLowerCase(final Object self) { 762 Global.checkObjectCoercible(self); 763 return JSType.toString(self).toLowerCase(); 764 } 765 766 /** 767 * ECMA 15.5.4.17 String.prototype.toLocaleLowerCase ( ) 768 * @param self self reference 769 * @return string to locale sensitive lower case 770 */ 771 @Function(attributes = Attribute.NOT_ENUMERABLE) 772 public static Object toLocaleLowerCase(final Object self) { 773 Global.checkObjectCoercible(self); 774 return JSType.toString(self).toLowerCase(Global.getThisContext().getLocale()); 775 } 776 777 /** 778 * ECMA 15.5.4.18 String.prototype.toUpperCase ( ) 779 * @param self self reference 780 * @return string to upper case 781 */ 782 @Function(attributes = Attribute.NOT_ENUMERABLE) 783 public static Object toUpperCase(final Object self) { 784 Global.checkObjectCoercible(self); 785 return JSType.toString(self).toUpperCase(); 786 } 787 788 /** 789 * ECMA 15.5.4.19 String.prototype.toLocaleUpperCase ( ) 790 * @param self self reference 791 * @return string to locale sensitive upper case 792 */ 793 @Function(attributes = Attribute.NOT_ENUMERABLE) 794 public static Object toLocaleUpperCase(final Object self) { 795 Global.checkObjectCoercible(self); 796 return JSType.toString(self).toUpperCase(Global.getThisContext().getLocale()); 797 } 798 799 /** 800 * ECMA 15.5.4.20 String.prototype.trim ( ) 801 * @param self self reference 802 * @return string trimmed from whitespace 803 */ 804 @Function(attributes = Attribute.NOT_ENUMERABLE) 805 public static Object trim(final Object self) { 806 Global.checkObjectCoercible(self); 807 808 final String str = JSType.toString(self); 809 810 int start = 0; 811 int end = str.length() - 1; 812 813 while (start <= end && Lexer.isJSWhitespace(str.charAt(start))) { 814 start++; 815 } 816 while (end > start && Lexer.isJSWhitespace(str.charAt(end))) { 817 end--; 818 } 819 820 return str.substring(start, end + 1); 821 } 822 823 private static Object newObj(final Object self, final CharSequence str) { 824 if (self instanceof ScriptObject) { 825 return new NativeString(str, ((ScriptObject)self).getProto()); 826 } 827 return new NativeString(str, Global.instance().getStringPrototype()); 828 } 829 830 /** 831 * ECMA 15.5.2.1 new String ( [ value ] ) 832 * 833 * Constructor 834 * 835 * @param newObj is this constructor invoked with the new operator 836 * @param self self reference 837 * @param args arguments (a value) 838 * 839 * @return new NativeString, empty string if no args, extraneous args ignored 840 */ 841 @Constructor(arity = 1) 842 public static Object constructor(final boolean newObj, final Object self, final Object... args) { 843 final CharSequence str = (args.length > 0) ? JSType.toCharSequence(args[0]) : ""; 844 return newObj ? newObj(self, str) : str.toString(); 845 } 846 847 /** 848 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with no args 849 * 850 * Constructor 851 * 852 * @param newObj is this constructor invoked with the new operator 853 * @param self self reference 854 * 855 * @return new NativeString ("") 856 */ 857 @SpecializedConstructor 858 public static Object constructor(final boolean newObj, final Object self) { 859 return newObj ? newObj(self, "") : ""; 860 } 861 862 //TODO - why is there no String with one String arg constructor? 863 864 /** 865 * ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg 866 * 867 * Constructor 868 * 869 * @param newObj is this constructor invoked with the new operator 870 * @param self self reference 871 * @param arg the arg 872 * 873 * @return new NativeString containing the string representation of the arg 874 */ 875 @SpecializedConstructor 876 public static Object constructor(final boolean newObj, final Object self, final int arg) { 877 final CharSequence str = JSType.toCharSequence(arg); 878 return newObj ? newObj(self, str) : str; 879 } 880 881 /** 882 * Lookup the appropriate method for an invoke dynamic call. 883 * 884 * @param desc the call site descriptor 885 * @param receiver receiver of call 886 * @return Link to be invoked at call site. 887 */ 888 public static GuardedInvocation lookupPrimitive(final NashornCallSiteDescriptor desc, final Object receiver) { 889 final MethodHandle guard = NashornGuards.getInstanceOf2Guard(String.class, ConsString.class); 890 return PrimitiveLookup.lookupPrimitive(desc, guard, new NativeString((CharSequence)receiver), WRAPFILTER); 891 } 892 893 @SuppressWarnings("unused") 894 private static NativeString wrapFilter(final Object receiver) { 895 return new NativeString((CharSequence)receiver); 896 } 897 898 private static CharSequence getCharSequence(final Object self) { 899 if (self instanceof String || self instanceof ConsString) { 900 return (CharSequence)self; 901 } else if (self instanceof NativeString) { 902 return ((NativeString)self).getValue(); 903 } else if (self != null && self == Global.instance().getStringPrototype()) { 904 return ""; 905 } else { 906 typeError(Global.instance(), "not.a.string", ScriptRuntime.safeToString(self)); 907 return null; 908 } 909 } 910 911 private static String getString(final Object self) { 912 if (self instanceof String) { 913 return (String)self; 914 } else if (self instanceof ConsString) { 915 return self.toString(); 916 } else if (self instanceof NativeString) { 917 return ((NativeString)self).getStringValue(); 918 } else if (self != null && self == Global.instance().getStringPrototype()) { 919 return ""; 920 } else { 921 typeError(Global.instance(), "not.a.string", ScriptRuntime.safeToString(self)); 922 return null; 923 } 924 } 925 926 private boolean isValid(final int key) { 927 return key >= 0 && key < value.length(); 928 } 929 930 private static MethodHandle findWrapFilter() { 931 try { 932 return MethodHandles.lookup().findStatic(NativeString.class, "wrapFilter", MH.type(NativeString.class, Object.class)); 933 } catch (final NoSuchMethodException | IllegalAccessException e) { 934 throw new AssertionError(e); 935 } 936 } 937 }