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