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