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.runtime.ECMAErrors.typeError;
  29 import static jdk.nashorn.internal.runtime.JSType.isRepresentableAsInt;
  30 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  31 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndexNoThrow;
  32 import static jdk.nashorn.internal.runtime.linker.Lookup.MH;
  33 
  34 import java.lang.invoke.MethodHandle;
  35 import java.lang.invoke.MethodHandles;
  36 import java.text.Collator;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.List;

  40 import jdk.nashorn.internal.objects.annotations.Attribute;
  41 import jdk.nashorn.internal.objects.annotations.Constructor;
  42 import jdk.nashorn.internal.objects.annotations.Function;
  43 import jdk.nashorn.internal.objects.annotations.Getter;
  44 import jdk.nashorn.internal.objects.annotations.ScriptClass;
  45 import jdk.nashorn.internal.objects.annotations.SpecializedConstructor;
  46 import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
  47 import jdk.nashorn.internal.objects.annotations.Where;
  48 import jdk.nashorn.internal.parser.Lexer;
  49 import jdk.nashorn.internal.runtime.ConsString;
  50 import jdk.nashorn.internal.runtime.JSType;
  51 import jdk.nashorn.internal.runtime.ScriptFunction;
  52 import jdk.nashorn.internal.runtime.ScriptObject;
  53 import jdk.nashorn.internal.runtime.ScriptRuntime;
  54 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
  55 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  56 import jdk.nashorn.internal.runtime.linker.NashornGuards;
  57 import jdk.nashorn.internal.runtime.linker.PrimitiveLookup;
  58 import org.dynalang.dynalink.linker.GuardedInvocation;
  59 
  60 
  61 /**
  62  * ECMA 15.5 String Objects.
  63  */
  64 @ScriptClass("String")
  65 public final class NativeString extends ScriptObject {
  66 
  67     private final CharSequence value;
  68 
  69     private static final MethodHandle WRAPFILTER = findWrapFilter();
  70 
  71     NativeString(final CharSequence value) {
  72         this(value, Global.instance().getStringPrototype());
  73     }
  74 
  75     private NativeString(final CharSequence value, final ScriptObject proto) {
  76         assert value instanceof String || value instanceof ConsString;
  77         this.value = value;
  78         this.setProto(proto);
  79     }
  80 
  81     @Override
  82     public String safeToString() {
  83         return "[String " + toString() + "]";
  84     }
  85 
  86     @Override
  87     public String toString() {
  88         return getStringValue();
  89     }
  90 
  91     @Override
  92     public boolean equals(final Object other) {
  93         if (other instanceof NativeString) {
  94             return getStringValue().equals(((NativeString) other).getStringValue());
  95         }
  96 
  97         return false;
  98     }
  99 
 100     @Override
 101     public int hashCode() {
 102         return getStringValue().hashCode();
 103     }
 104 
 105     private String getStringValue() {
 106         return value instanceof String ? (String) value : value.toString();
 107     }
 108 
 109     private CharSequence getValue() {
 110         return value;
 111     }
 112 
 113     @Override
 114     public String getClassName() {
 115         return "String";
 116     }
 117 
 118     @Override
 119     public Object getLength() {
 120         return value.length();
 121     }
 122 
 123     // String characters can be accessed with array-like indexing..
 124     @Override
 125     public Object get(final Object key) {
 126         final int index = getArrayIndexNoThrow(key);
 127         if (index >= 0 && index < value.length()) {
 128             return String.valueOf(value.charAt(index));
 129         }
 130         return super.get(key);
 131     }
 132 
 133     @Override
 134     public Object get(final double key) {
 135         if (isRepresentableAsInt(key)) {
 136             return get((int)key);
 137         }
 138         return super.get(key);
 139     }
 140 
 141     @Override
 142     public Object get(final long key) {
 143         if (key >= 0 && key < value.length()) {
 144             return String.valueOf(value.charAt((int)key));
 145         }
 146         return super.get(key);
 147     }
 148 
 149     @Override
 150     public Object get(final int key) {
 151         if (key >= 0 && key < value.length()) {
 152             return String.valueOf(value.charAt(key));
 153         }
 154         return super.get(key);
 155     }
 156 
 157     @Override
 158     public int getInt(final Object key) {
 159         return JSType.toInt32(get(key));
 160     }
 161 
 162     @Override
 163     public int getInt(final double key) {
 164         return JSType.toInt32(get(key));
 165     }
 166 
 167     @Override
 168     public int getInt(final long key) {
 169         return JSType.toInt32(get(key));
 170     }
 171 
 172     @Override
 173     public int getInt(final int key) {
 174         return JSType.toInt32(get(key));
 175     }
 176 
 177     @Override
 178     public long getLong(final Object key) {
 179         return JSType.toUint32(get(key));
 180     }
 181 
 182     @Override
 183     public long getLong(final double key) {
 184         return JSType.toUint32(get(key));
 185     }
 186 
 187     @Override
 188     public long getLong(final long key) {
 189         return JSType.toUint32(get(key));
 190     }
 191 
 192     @Override
 193     public long getLong(final int key) {
 194         return JSType.toUint32(get(key));
 195     }
 196 
 197     @Override
 198     public double getDouble(final Object key) {
 199         return JSType.toNumber(get(key));
 200     }
 201 
 202     @Override
 203     public double getDouble(final double key) {
 204         return JSType.toNumber(get(key));
 205     }
 206 
 207     @Override
 208     public double getDouble(final long key) {
 209         return JSType.toNumber(get(key));
 210     }
 211 
 212     @Override
 213     public double getDouble(final int key) {
 214         return JSType.toNumber(get(key));
 215     }
 216 
 217     @Override
 218     public boolean has(final Object key) {
 219         final int index = getArrayIndexNoThrow(key);
 220         return isValid(index) || super.has(key);
 221     }
 222 
 223     @Override
 224     public boolean has(final int key) {
 225         return isValid(key) || super.has(key);
 226     }
 227 
 228     @Override
 229     public boolean has(final long key) {
 230         final int index = getArrayIndexNoThrow(key);
 231         return isValid(index) || super.has(key);
 232     }
 233 
 234     @Override
 235     public boolean has(final double key) {
 236         final int index = getArrayIndexNoThrow(key);
 237         return isValid(index) || super.has(key);
 238     }
 239 
 240     @Override
 241     public boolean hasOwnProperty(final Object key) {
 242         final int index = getArrayIndexNoThrow(key);
 243         return isValid(index) || super.hasOwnProperty(key);
 244     }
 245 
 246     @Override
 247     public boolean hasOwnProperty(final int key) {
 248         return isValid(key) || super.hasOwnProperty(key);
 249     }
 250 
 251     @Override
 252     public boolean hasOwnProperty(final long key) {
 253         final int index = getArrayIndexNoThrow(key);
 254         return isValid(index) || super.hasOwnProperty(key);
 255     }
 256 
 257     @Override
 258     public boolean hasOwnProperty(final double key) {
 259         final int index = getArrayIndexNoThrow(key);
 260         return isValid(index) || super.hasOwnProperty(key);
 261     }
 262 
 263     @Override
 264     public boolean delete(final int key, final boolean strict) {
 265         return checkDeleteIndex(key, strict)? false : super.delete(key, strict);
 266     }
 267 
 268     @Override
 269     public boolean delete(final long key, final boolean strict) {
 270         final int index = getArrayIndexNoThrow(key);
 271         return checkDeleteIndex(index, strict)? false : super.delete(key, strict);
 272     }
 273 
 274     @Override
 275     public boolean delete(final double key, final boolean strict) {
 276         final int index = getArrayIndexNoThrow(key);
 277         return checkDeleteIndex(index, strict)? false : super.delete(key, strict);
 278     }
 279 
 280     @Override
 281     public boolean delete(final Object key, final boolean strict) {
 282         final int index = getArrayIndexNoThrow(key);
 283         return checkDeleteIndex(index, strict)? false : super.delete(key, strict);
 284     }
 285 
 286     private boolean checkDeleteIndex(final int index, final boolean strict) {
 287         if (isValid(index)) {
 288             if (strict) {
 289                 typeError(Global.instance(), "cant.delete.property", Integer.toString(index), ScriptRuntime.safeToString(this));
 290             }
 291             return true;
 292         }
 293 
 294         return false;
 295     }
 296 
 297     @Override
 298     public Object getOwnPropertyDescriptor(final String key) {
 299         final int index = ArrayIndex.getArrayIndexNoThrow(key);
 300         if (index >= 0 && index < value.length()) {
 301             final Global global = Global.instance();
 302             return global.newDataDescriptor(String.valueOf(value.charAt(index)), false, true, false);
 303         }
 304 
 305         return super.getOwnPropertyDescriptor(key);
 306     }
 307 
 308     /**
 309      * return a List of own keys associated with the object.
 310      * @param all True if to include non-enumerable keys.
 311      * @return Array of keys.
 312      */
 313     @Override
 314     public String[] getOwnKeys(final boolean all) {
 315         final List<Object> keys = new ArrayList<>();
 316 
 317         // add string index keys
 318         for (int i = 0; i < value.length(); i++) {
 319             keys.add(JSType.toString(i));
 320         }
 321 
 322         // add super class properties
 323         keys.addAll(Arrays.asList(super.getOwnKeys(all)));
 324         return keys.toArray(new String[keys.size()]);
 325     }
 326 
 327     /**
 328      * ECMA 15.5.3 String.length
 329      * @param self self reference
 330      * @return     value of length property for string
 331      */
 332     @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_WRITABLE | Attribute.NOT_CONFIGURABLE)
 333     public static Object length(final Object self) {
 334         return getCharSequence(self).length();
 335     }
 336 
 337     /**
 338      * ECMA 15.5.3.2 String.fromCharCode ( [ char0 [ , char1 [ , ... ] ] ] )
 339      * @param self  self reference
 340      * @param args  array of arguments to be interpreted as char
 341      * @return string with arguments translated to charcodes
 342      */
 343     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1, where = Where.CONSTRUCTOR)
 344     public static Object fromCharCode(final Object self, final Object... args) {
 345         final char[] buf = new char[args.length];
 346         int index = 0;
 347         for (final Object arg : args) {
 348             buf[index++] = (char)JSType.toUint16(arg);
 349         }
 350         return new String(buf);
 351     }
 352 
 353     /**
 354      * ECMA 15.5.3.2 - specialization for one char
 355      * @param self  self reference
 356      * @param value one argument to be interpreted as char
 357      * @return string with one charcode
 358      */
 359     @SpecializedFunction
 360     public static Object fromCharCode(final Object self, final Object value) {
 361         try {
 362             return "" + (char)JSType.toUint16(((Number)value).doubleValue());
 363         } catch (final ClassCastException e) {
 364             return fromCharCode(self, new Object[] { value });
 365         }
 366     }
 367 
 368     /**
 369      * ECMA 15.5.3.2 - specialization for one char of int type
 370      * @param self  self reference
 371      * @param value one argument to be interpreted as char
 372      * @return string with one charcode
 373      */
 374     @SpecializedFunction
 375     public static Object fromCharCode(final Object self, final int value) {
 376         return "" + (char)(value & 0xffff);
 377     }
 378 
 379     /**
 380      * ECMA 15.5.3.2 - specialization for one char of long type
 381      * @param self  self reference
 382      * @param value one argument to be interpreted as char
 383      * @return string with one charcode
 384      */
 385     @SpecializedFunction
 386     public static Object fromCharCode(final Object self, final long value) {
 387         return "" + (char)((int)value & 0xffff);
 388     }
 389 
 390     /**
 391      * ECMA 15.5.3.2 - specialization for one char of double type
 392      * @param self  self reference
 393      * @param value one argument to be interpreted as char
 394      * @return string with one charcode
 395      */
 396     @SpecializedFunction
 397     public static Object fromCharCode(final Object self, final double value) {
 398         return "" + (char)JSType.toUint16(value);
 399     }
 400 
 401     /**
 402      * ECMA 15.5.4.2 String.prototype.toString ( )
 403      * @param self self reference
 404      * @return self as string
 405      */
 406     @Function(attributes = Attribute.NOT_ENUMERABLE)
 407     public static Object toString(final Object self) {
 408         return getString(self);
 409     }
 410 
 411     /**
 412      * ECMA 15.5.4.3 String.prototype.valueOf ( )
 413      * @param self self reference
 414      * @return self as string
 415      */
 416     @Function(attributes = Attribute.NOT_ENUMERABLE)
 417     public static Object valueOf(final Object self) {
 418         return getString(self);
 419     }
 420 
 421     /**
 422      * ECMA 15.5.4.4 String.prototype.charAt (pos)
 423      * @param self self reference
 424      * @param pos  position in string
 425      * @return string representing the char at the given position
 426      */
 427     @Function(attributes = Attribute.NOT_ENUMERABLE)
 428     public static Object charAt(final Object self, final Object pos) {
 429         try {
 430             return String.valueOf(((String)self).charAt(((Number)pos).intValue()));
 431         } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
 432             Global.checkObjectCoercible(self);
 433             final String str = JSType.toString(self);
 434             final int    at  = JSType.toInteger(pos);
 435             if (at < 0 || at >= str.length()) {
 436                 return "";
 437             }
 438             return String.valueOf(str.charAt(at));
 439         }
 440     }
 441 
 442     /**
 443      * ECMA 15.5.4.5 String.prototype.charCodeAt (pos)
 444      * @param self self reference
 445      * @param pos  position in string
 446      * @return number representing charcode at position
 447      */
 448     @Function(attributes = Attribute.NOT_ENUMERABLE)
 449     public static Object charCodeAt(final Object self, final Object pos) {
 450         try {
 451             return (int)((String)self).charAt(((Number)pos).intValue());
 452         } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
 453             Global.checkObjectCoercible(self);
 454             final String str = JSType.toString(self);
 455             final int at     = JSType.toInteger(pos);
 456             if (at < 0 || at >= str.length()) {
 457                 return Double.NaN;
 458             }
 459 
 460             return JSType.toObject(str.charAt(at));
 461         }
 462     }
 463 
 464     /**
 465      * ECMA 15.5.4.6 String.prototype.concat ( [ string1 [ , string2 [ , ... ] ] ] )
 466      * @param self self reference
 467      * @param args list of string to concatenate
 468      * @return concatenated string
 469      */
 470     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 471     public static Object concat(final Object self, final Object... args) {
 472         Global.checkObjectCoercible(self);
 473         final StringBuilder sb = new StringBuilder(JSType.toString(self));
 474         if (args != null) {
 475             for (final Object obj : args) {
 476                 sb.append(JSType.toString(obj));
 477             }
 478         }
 479         return sb.toString();
 480     }
 481 
 482     /**
 483      * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position)
 484      * @param self   self reference
 485      * @param search string to search for
 486      * @param pos    position to start search
 487      * @return position of first match or -1
 488      */
 489     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 490     public static Object indexOf(final Object self, final Object search, final Object pos) {
 491         try {
 492             return ((String)self).indexOf((String)search, ((Number)pos).intValue()); //assuming that the conversions really mean "toInteger" and not "toInt32" this is ok.
 493         } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
 494             Global.checkObjectCoercible(self);
 495             return JSType.toString(self).indexOf(JSType.toString(search), JSType.toInteger(pos));
 496         }
 497     }
 498 
 499     /**
 500      * ECMA 15.5.4.8 String.prototype.lastIndexOf (searchString, position)
 501      * @param self   self reference
 502      * @param search string to search for
 503      * @param pos    position to start search
 504      * @return last position of match or -1
 505      */
 506     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
 507     public static Object lastIndexOf(final Object self, final Object search, final Object pos) {
 508         Global.checkObjectCoercible(self);
 509 
 510         final String str       = JSType.toString(self);
 511         final String searchStr = JSType.toString(search);
 512 
 513         int from;
 514 
 515         if (pos == UNDEFINED) {
 516             from = str.length();
 517         } else {
 518             final double numPos = JSType.toNumber(pos);
 519             from = !Double.isNaN(numPos) ? (int)numPos : (int)Double.POSITIVE_INFINITY;
 520         }
 521 
 522         return str.lastIndexOf(searchStr, from);
 523     }
 524 
 525     /**
 526      * ECMA 15.5.4.9 String.prototype.localeCompare (that)
 527      * @param self self reference
 528      * @param that comparison object
 529      * @return result of locale sensitive comparison operation between {@code self} and {@code that}
 530      */
 531     @Function(attributes = Attribute.NOT_ENUMERABLE)
 532     public static Object localeCompare(final Object self, final Object that) {
 533         Global.checkObjectCoercible(self);
 534 
 535         final String   str      = JSType.toString(self);
 536         final Collator collator = Collator.getInstance(Global.getThisContext().getLocale());
 537 
 538         collator.setStrength(Collator.IDENTICAL);
 539         collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
 540 
 541         return (double)collator.compare(str, JSType.toString(that));
 542     }
 543 
 544     /**
 545      * ECMA 15.5.4.10 String.prototype.match (regexp)
 546      * @param self   self reference
 547      * @param regexp regexp expression
 548      * @return array of regexp matches
 549      */
 550     @Function(attributes = Attribute.NOT_ENUMERABLE)
 551     public static Object match(final Object self, final Object regexp) {
 552         Global.checkObjectCoercible(self);
 553 
 554         final String str = JSType.toString(self);
 555 
 556         NativeRegExp nativeRegExp;
 557         if (regexp == UNDEFINED) {
 558             nativeRegExp = new NativeRegExp("");
 559         } else {
 560             nativeRegExp = Global.toRegExp(regexp);
 561         }
 562 
 563         if (!nativeRegExp.getGlobal()) {
 564             return nativeRegExp.exec(str);
 565         }
 566 
 567         nativeRegExp.setLastIndex(0);
 568 
 569         int previousLastIndex = 0;
 570         final List<Object> matches = new ArrayList<>();
 571 
 572         Object result;
 573         while ((result = nativeRegExp.exec(str)) != null) {
 574             final int thisIndex = nativeRegExp.getLastIndex();
 575             if (thisIndex == previousLastIndex) {
 576                 nativeRegExp.setLastIndex(thisIndex + 1);
 577                 previousLastIndex = thisIndex + 1;
 578             } else {
 579                 previousLastIndex = thisIndex;
 580             }
 581             matches.add(((ScriptObject)result).get(0));
 582         }
 583 
 584         if (matches.isEmpty()) {
 585             return null;
 586         }
 587 
 588         return new NativeArray(matches.toArray());
 589     }
 590 
 591     /**
 592      * ECMA 15.5.4.11 String.prototype.replace (searchValue, replaceValue)
 593      * @param self        self reference
 594      * @param string      item to replace
 595      * @param replacement item to replace it with
 596      * @return string after replacement
 597      */
 598     @Function(attributes = Attribute.NOT_ENUMERABLE)
 599     public static Object replace(final Object self, final Object string, final Object replacement) {
 600         Global.checkObjectCoercible(self);
 601 
 602         final String str = JSType.toString(self);
 603 
 604         final NativeRegExp nativeRegExp;
 605         if (string instanceof NativeRegExp) {
 606             nativeRegExp = (NativeRegExp) string;
 607         } else {
 608             nativeRegExp = NativeRegExp.flatRegExp(JSType.toString(string));
 609         }
 610 
 611         if (replacement instanceof ScriptFunction) {
 612             return nativeRegExp.replace(str, "", (ScriptFunction)replacement);
 613         }
 614 
 615         return nativeRegExp.replace(str, JSType.toString(replacement), null);
 616     }
 617 
 618     /**
 619      * ECMA 15.5.4.12 String.prototype.search (regexp)
 620      *
 621      * @param self    self reference
 622      * @param string  string to search for
 623      * @return offset where match occurred
 624      */
 625     @Function(attributes = Attribute.NOT_ENUMERABLE)
 626     public static Object search(final Object self, final Object string) {
 627         Global.checkObjectCoercible(self);
 628 
 629         final String       str          = JSType.toString(self);
 630         final NativeRegExp nativeRegExp = Global.toRegExp(string == UNDEFINED ? "" : string);
 631 
 632         return nativeRegExp.search(str);
 633     }
 634 
 635     /**
 636      * ECMA 15.5.4.13 String.prototype.slice (start, end)
 637      *
 638      * @param self  self reference
 639      * @param start start position for slice
 640      * @param end   end position for slice
 641      * @return sliced out substring
 642      */
 643     @Function(attributes = Attribute.NOT_ENUMERABLE)
 644     public static Object slice(final Object self, final Object start, final Object end) {
 645         Global.checkObjectCoercible(self);
 646 
 647         final String str      = JSType.toString(self);
 648         final int    len      = str.length();
 649         final int    intStart = JSType.toInteger(start);
 650         final int    intEnd   = (end == UNDEFINED) ? len : JSType.toInteger(end);
 651 
 652         int from;
 653 
 654         if (intStart < 0) {
 655             from = Math.max(len + intStart, 0);
 656         } else {
 657             from = Math.min(intStart, len);
 658         }
 659 
 660         int to;
 661 
 662         if (intEnd < 0) {
 663             to = Math.max(len + intEnd,0);
 664         } else {
 665             to = Math.min(intEnd, len);
 666         }
 667 
 668         return str.substring(Math.min(from,  to), to);
 669     }
 670 
 671     /**
 672      * ECMA 15.5.4.14 String.prototype.split (separator, limit)
 673      *
 674      * @param self      self reference
 675      * @param separator separator for split
 676      * @param limit     limit for splits
 677      * @return array object in which splits have been placed
 678      */
 679     @Function(attributes = Attribute.NOT_ENUMERABLE)
 680     public static Object split(final Object self, final Object separator, final Object limit) {
 681         Global.checkObjectCoercible(self);
 682 
 683         final String str = JSType.toString(self);
 684 
 685         if (separator == UNDEFINED) {
 686             return new NativeArray(new Object[]{str});
 687         }
 688 
 689         final long lim = (limit == UNDEFINED) ? JSType.MAX_UINT : JSType.toUint32(limit);
 690 
 691         if (separator instanceof NativeRegExp) {
 692             return ((NativeRegExp) separator).split(str, lim);
 693         }
 694 
 695         // when separator is a string, it has to be treated as a
 696         // literal search string to be used for splitting.
 697         return NativeRegExp.flatRegExp(JSType.toString(separator)).split(str, lim);
 698     }
 699 
 700     /**
 701      * ECMA B.2.3 String.prototype.substr (start, length)
 702      *
 703      * @param self   self reference
 704      * @param start  start position
 705      * @param length length of section
 706      * @return substring given start and length of section
 707      */
 708     @Function(attributes = Attribute.NOT_ENUMERABLE)
 709     public static Object substr(final Object self, final Object start, final Object length) {
 710         final String str       = JSType.toString(self);
 711         final int    strLength = str.length();
 712 
 713         int intStart = JSType.toInteger(start);
 714         if (intStart < 0) {
 715             intStart = Math.max(intStart + strLength, 0);
 716         }
 717 
 718         final int intLen = Math.min(Math.max((length == UNDEFINED) ? Integer.MAX_VALUE : JSType.toInteger(length), 0), strLength - intStart);
 719 
 720         return intLen <= 0 ? "" : str.substring(intStart, intStart + intLen);
 721     }
 722 
 723     /**
 724      * ECMA 15.5.4.15 String.prototype.substring (start, end)
 725      *
 726      * @param self  self reference
 727      * @param start start position of substring
 728      * @param end   end position of substring
 729      * @return substring given start and end indexes
 730      */
 731     @Function(attributes = Attribute.NOT_ENUMERABLE)
 732     public static Object substring(final Object self, final Object start, final Object end) {
 733         Global.checkObjectCoercible(self);
 734 
 735         final String str        = JSType.toString(self);
 736         final int    len        = str.length();
 737         final int    intStart   = JSType.toInteger(start);
 738         final int    intEnd     = (end == UNDEFINED) ? len : JSType.toInteger(end);
 739         final int    finalStart = Math.min((intStart < 0) ? 0 : intStart, len);
 740         final int    finalEnd   = Math.min((intEnd < 0) ? 0 : intEnd, len);
 741 
 742         int from, to;
 743 
 744         if (finalStart < finalEnd) {
 745             from = finalStart;
 746             to = finalEnd;
 747         } else {
 748             from = finalEnd;
 749             to = finalStart;
 750         }
 751         return str.substring(from, to);
 752     }
 753 
 754     /**
 755      * ECMA 15.5.4.16 String.prototype.toLowerCase ( )
 756      * @param self self reference
 757      * @return string to lower case
 758      */
 759     @Function(attributes = Attribute.NOT_ENUMERABLE)
 760     public static Object toLowerCase(final Object self) {
 761         Global.checkObjectCoercible(self);
 762         return JSType.toString(self).toLowerCase();
 763     }
 764 
 765     /**
 766      * ECMA 15.5.4.17 String.prototype.toLocaleLowerCase ( )
 767      * @param self self reference
 768      * @return string to locale sensitive lower case
 769      */
 770     @Function(attributes = Attribute.NOT_ENUMERABLE)
 771     public static Object toLocaleLowerCase(final Object self) {
 772         Global.checkObjectCoercible(self);
 773         return JSType.toString(self).toLowerCase(Global.getThisContext().getLocale());
 774     }
 775 
 776     /**
 777      * ECMA 15.5.4.18 String.prototype.toUpperCase ( )
 778      * @param self self reference
 779      * @return string to upper case
 780      */
 781     @Function(attributes = Attribute.NOT_ENUMERABLE)
 782     public static Object toUpperCase(final Object self) {
 783         Global.checkObjectCoercible(self);
 784         return JSType.toString(self).toUpperCase();
 785     }
 786 
 787     /**
 788      * ECMA 15.5.4.19 String.prototype.toLocaleUpperCase ( )
 789      * @param self self reference
 790      * @return string to locale sensitive upper case
 791      */
 792     @Function(attributes = Attribute.NOT_ENUMERABLE)
 793     public static Object toLocaleUpperCase(final Object self) {
 794         Global.checkObjectCoercible(self);
 795         return JSType.toString(self).toUpperCase(Global.getThisContext().getLocale());
 796     }
 797 
 798     /**
 799      * ECMA 15.5.4.20 String.prototype.trim ( )
 800      * @param self self reference
 801      * @return string trimmed from whitespace
 802      */
 803     @Function(attributes = Attribute.NOT_ENUMERABLE)
 804     public static Object trim(final Object self) {
 805         Global.checkObjectCoercible(self);
 806 
 807         final String str = JSType.toString(self);
 808 
 809         int start = 0;
 810         int end   = str.length() - 1;
 811 
 812         while (start <= end && Lexer.isJSWhitespace(str.charAt(start))) {
 813             start++;
 814         }
 815         while (end > start && Lexer.isJSWhitespace(str.charAt(end))) {
 816             end--;
 817         }
 818 
 819         return str.substring(start, end + 1);
 820     }
 821 
 822     private static Object newObj(final Object self, final CharSequence str) {
 823         if (self instanceof ScriptObject) {
 824             return new NativeString(str, ((ScriptObject)self).getProto());
 825         }
 826         return new NativeString(str, Global.instance().getStringPrototype());
 827     }
 828 
 829     /**
 830      * ECMA 15.5.2.1 new String ( [ value ] )
 831      *
 832      * Constructor
 833      *
 834      * @param newObj is this constructor invoked with the new operator
 835      * @param self   self reference
 836      * @param args   arguments (a value)
 837      *
 838      * @return new NativeString, empty string if no args, extraneous args ignored
 839      */
 840     @Constructor(arity = 1)
 841     public static Object constructor(final boolean newObj, final Object self, final Object... args) {
 842         final CharSequence str = (args.length > 0) ? JSType.toCharSequence(args[0]) : "";
 843         return newObj ? newObj(self, str) : str.toString();
 844     }
 845 
 846     /**
 847      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with no args
 848      *
 849      * Constructor
 850      *
 851      * @param newObj is this constructor invoked with the new operator
 852      * @param self   self reference
 853      *
 854      * @return new NativeString ("")
 855      */
 856     @SpecializedConstructor
 857     public static Object constructor(final boolean newObj, final Object self) {
 858         return newObj ? newObj(self, "") : "";
 859     }
 860 
 861     //TODO - why is there no String with one String arg constructor?
 862 
 863     /**
 864      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg
 865      *
 866      * Constructor
 867      *
 868      * @param newObj is this constructor invoked with the new operator
 869      * @param self   self reference
 870      * @param arg    the arg
 871      *
 872      * @return new NativeString containing the string representation of the arg
 873      */
 874     @SpecializedConstructor
 875     public static Object constructor(final boolean newObj, final Object self, final int arg) {
 876         final CharSequence str = JSType.toCharSequence(arg);
 877         return newObj ? newObj(self, str) : str;
 878     }
 879 
 880     /**
 881      * Lookup the appropriate method for an invoke dynamic call.
 882      *
 883      * @param desc the call site descriptor
 884      * @param receiver receiver of call
 885      * @return Link to be invoked at call site.
 886      */
 887     public static GuardedInvocation lookupPrimitive(final NashornCallSiteDescriptor desc, final Object receiver) {
 888         final MethodHandle guard = NashornGuards.getInstanceOf2Guard(String.class, ConsString.class);
 889         return PrimitiveLookup.lookupPrimitive(desc, guard, new NativeString((CharSequence)receiver), WRAPFILTER);
 890     }
 891 
 892     @SuppressWarnings("unused")
 893     private static NativeString wrapFilter(final Object receiver) {
 894         return new NativeString((CharSequence)receiver);
 895     }
 896 
 897     private static CharSequence getCharSequence(final Object self) {
 898         if (self instanceof String || self instanceof ConsString) {
 899             return (CharSequence)self;
 900         } else if (self instanceof NativeString) {
 901             return ((NativeString)self).getValue();
 902         } else if (self != null && self == Global.instance().getStringPrototype()) {
 903             return "";
 904         } else {
 905             typeError(Global.instance(), "not.a.string", ScriptRuntime.safeToString(self));
 906             return null;
 907         }
 908     }
 909 
 910     private static String getString(final Object self) {
 911         if (self instanceof String) {
 912             return (String)self;
 913         } else if (self instanceof ConsString) {
 914             return self.toString();
 915         } else if (self instanceof NativeString) {
 916             return ((NativeString)self).getStringValue();
 917         } else if (self != null && self == Global.instance().getStringPrototype()) {
 918             return "";
 919         } else {
 920             typeError(Global.instance(), "not.a.string", ScriptRuntime.safeToString(self));
 921             return null;
 922         }
 923     }
 924 
 925     private boolean isValid(final int key) {
 926         return key >= 0 && key < value.length();
 927     }
 928 
 929     private static MethodHandle findWrapFilter() {
 930         try {
 931             return MethodHandles.lookup().findStatic(NativeString.class, "wrapFilter", MH.type(NativeString.class, Object.class));
 932         } catch (final NoSuchMethodException | IllegalAccessException e) {
 933             throw new AssertionError(e);
 934         }
 935     }
 936 }
--- EOF ---