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