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