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