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