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

 859                 array[i] = String.valueOf(str.charAt(i));
 860             }
 861             return new NativeArray(array);
 862         }
 863 
 864         final List<String> elements = new LinkedList<>();
 865         final int strLength = str.length();
 866         final int sepLength = separator.length();
 867         int pos = 0;
 868         int n = 0;
 869 
 870         while (pos < strLength && n < limit) {
 871             int found = str.indexOf(separator, pos);
 872             if (found == -1) {
 873                 break;
 874             }
 875             elements.add(str.substring(pos, found));
 876             n++;
 877             pos = found + sepLength;
 878         }
 879         if (pos <= strLength && n < limit) {
 880             elements.add(str.substring(pos));
 881         }
 882 
 883         return new NativeArray(elements.toArray());
 884     }
 885 
 886     /**
 887      * ECMA B.2.3 String.prototype.substr (start, length)
 888      *
 889      * @param self   self reference
 890      * @param start  start position
 891      * @param length length of section
 892      * @return substring given start and length of section
 893      */
 894     @Function(attributes = Attribute.NOT_ENUMERABLE)
 895     public static Object substr(final Object self, final Object start, final Object length) {
 896         final String str       = JSType.toString(self);
 897         final int    strLength = str.length();
 898 
 899         int intStart = JSType.toInteger(start);
 900         if (intStart < 0) {
 901             intStart = Math.max(intStart + strLength, 0);
 902         }
 903 
 904         final int intLen = Math.min(Math.max((length == UNDEFINED) ? Integer.MAX_VALUE : JSType.toInteger(length), 0), strLength - intStart);
 905 
 906         return intLen <= 0 ? "" : str.substring(intStart, intStart + intLen);
 907     }
 908 
 909     /**
 910      * ECMA 15.5.4.15 String.prototype.substring (start, end)
 911      *
 912      * @param self  self reference
 913      * @param start start position of substring
 914      * @param end   end position of substring
 915      * @return substring given start and end indexes
 916      */
 917     @Function(attributes = Attribute.NOT_ENUMERABLE)
 918     public static Object substring(final Object self, final Object start, final Object end) {
 919 
 920         final String str = checkObjectToString(self);
 921         if (end == UNDEFINED) {
 922             return substring(str, JSType.toInteger(start));
 923         }
 924         return substring(str, JSType.toInteger(start), JSType.toInteger(end));
 925     }
 926 
 927     /**
 928      * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start parameter
 929      *
 930      * @param self  self reference
 931      * @param start start position of substring
 932      * @return substring given start and end indexes
 933      */
 934     @SpecializedFunction
 935     public static String substring(final Object self, final int start) {
 936         final String str = checkObjectToString(self);
 937         if (start < 0) {
 938             return str;
 939         } else if (start >= str.length()) {
 940             return "";
 941         } else {
 942             return str.substring(start);
 943         }
 944     }
 945 
 946     /**
 947      * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start parameter
 948      *
 949      * @param self  self reference
 950      * @param start start position of substring
 951      * @return substring given start and end indexes
 952      */
 953     @SpecializedFunction
 954     public static String substring(final Object self, final double start) {
 955         return substring(self, (int)start);
 956     }
 957 
 958     /**
 959      * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start and end parameters
 960      *
 961      * @param self  self reference
 962      * @param start start position of substring
 963      * @param end   end position of substring
 964      * @return substring given start and end indexes
 965      */
 966     @SpecializedFunction
 967     public static String substring(final Object self, final int start, final int end) {
 968         final String str = checkObjectToString(self);
 969         final int len = str.length();
 970         final int validStart = start < 0 ? 0 : (start > len ? len : start);
 971         final int validEnd   = end < 0 ? 0 : (end > len ? len : end);
 972 
 973         if (validStart < validEnd) {
 974             return str.substring(validStart, validEnd);
 975         }
 976         return str.substring(validEnd, validStart);
 977     }
 978 
 979     /**
 980      * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start and end parameters
 981      *
 982      * @param self  self reference
 983      * @param start start position of substring
 984      * @param end   end position of substring
 985      * @return substring given start and end indexes
 986      */
 987     @SpecializedFunction
 988     public static String substring(final Object self, final double start, final double end) {
 989         return substring(self, (int)start, (int)end);
 990     }
 991 
 992     /**
 993      * ECMA 15.5.4.16 String.prototype.toLowerCase ( )
 994      * @param self self reference
 995      * @return string to lower case
 996      */
 997     @Function(attributes = Attribute.NOT_ENUMERABLE)
 998     public static Object toLowerCase(final Object self) {
 999         return checkObjectToString(self).toLowerCase();
1000     }
1001 
1002     /**
1003      * ECMA 15.5.4.17 String.prototype.toLocaleLowerCase ( )
1004      * @param self self reference
1005      * @return string to locale sensitive lower case
1006      */
1007     @Function(attributes = Attribute.NOT_ENUMERABLE)
1008     public static Object toLocaleLowerCase(final Object self) {
1009         return checkObjectToString(self).toLowerCase(Global.getEnv()._locale);
1010     }
1011 
1012     /**
1013      * ECMA 15.5.4.18 String.prototype.toUpperCase ( )
1014      * @param self self reference
1015      * @return string to upper case
1016      */
1017     @Function(attributes = Attribute.NOT_ENUMERABLE)
1018     public static Object toUpperCase(final Object self) {
1019         return checkObjectToString(self).toUpperCase();
1020     }
1021 
1022     /**
1023      * ECMA 15.5.4.19 String.prototype.toLocaleUpperCase ( )
1024      * @param self self reference
1025      * @return string to locale sensitive upper case
1026      */
1027     @Function(attributes = Attribute.NOT_ENUMERABLE)
1028     public static Object toLocaleUpperCase(final Object self) {
1029         return checkObjectToString(self).toUpperCase(Global.getEnv()._locale);
1030     }
1031 
1032     /**
1033      * ECMA 15.5.4.20 String.prototype.trim ( )
1034      * @param self self reference
1035      * @return string trimmed from whitespace
1036      */
1037     @Function(attributes = Attribute.NOT_ENUMERABLE)
1038     public static Object trim(final Object self) {
1039 
1040         final String str = checkObjectToString(self);
1041         final int len = str.length();
1042         int start = 0;
1043         int end   = len - 1;
1044 
1045         while (start <= end && ScriptRuntime.isJSWhitespace(str.charAt(start))) {
1046             start++;
1047         }
1048         while (end > start && ScriptRuntime.isJSWhitespace(str.charAt(end))) {
1049             end--;
1050         }
1051 
1052         return start == 0 && end + 1 == len ? str : str.substring(start, end + 1);
1053     }
1054 
1055     private static Object newObj(final Object self, final CharSequence str) {
1056         if (self instanceof ScriptObject) {
1057             return new NativeString(str, ((ScriptObject)self).getProto());
1058         }
1059         return new NativeString(str, Global.instance().getStringPrototype());
1060     }
1061 
1062     /**
1063      * ECMA 15.5.2.1 new String ( [ value ] )
1064      *
1065      * Constructor
1066      *
1067      * @param newObj is this constructor invoked with the new operator
1068      * @param self   self reference
1069      * @param args   arguments (a value)
1070      *
1071      * @return new NativeString, empty string if no args, extraneous args ignored
1072      */
1073     @Constructor(arity = 1)
1074     public static Object constructor(final boolean newObj, final Object self, final Object... args) {
1075         final CharSequence str = (args.length > 0) ? JSType.toCharSequence(args[0]) : "";
1076         return newObj ? newObj(self, str) : str.toString();
1077     }
1078 
1079     /**
1080      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with no args
1081      *
1082      * Constructor
1083      *
1084      * @param newObj is this constructor invoked with the new operator
1085      * @param self   self reference
1086      *
1087      * @return new NativeString ("")
1088      */
1089     @SpecializedConstructor
1090     public static Object constructor(final boolean newObj, final Object self) {
1091         return newObj ? newObj(self, "") : "";
1092     }
1093 
1094     /**
1095      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with one arg
1096      *
1097      * Constructor
1098      *
1099      * @param newObj is this constructor invoked with the new operator
1100      * @param self   self reference
1101      * @param arg    argument
1102      *
1103      * @return new NativeString (arg)
1104      */
1105     @SpecializedConstructor
1106     public static Object constructor(final boolean newObj, final Object self, final Object arg) {
1107         final CharSequence str = JSType.toCharSequence(arg);
1108         return newObj ? newObj(self, str) : str.toString();
1109     }
1110 
1111     /**
1112      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg
1113      *
1114      * Constructor
1115      *
1116      * @param newObj is this constructor invoked with the new operator
1117      * @param self   self reference
1118      * @param arg    the arg
1119      *
1120      * @return new NativeString containing the string representation of the arg
1121      */
1122     @SpecializedConstructor
1123     public static Object constructor(final boolean newObj, final Object self, final int arg) {
1124         final String str = JSType.toString(arg);
1125         return newObj ? newObj(self, str) : str;
1126     }
1127 
1128     /**
1129      * Lookup the appropriate method for an invoke dynamic call.
1130      *
1131      * @param request  the link request
1132      * @param receiver receiver of call
1133      * @return Link to be invoked at call site.
1134      */
1135     public static GuardedInvocation lookupPrimitive(final LinkRequest request, final Object receiver) {
1136         final MethodHandle guard = NashornGuards.getInstanceOf2Guard(String.class, ConsString.class);
1137         return PrimitiveLookup.lookupPrimitive(request, guard, new NativeString((CharSequence)receiver), WRAPFILTER);
1138     }
1139 
1140     @SuppressWarnings("unused")
1141     private static NativeString wrapFilter(final Object receiver) {
1142         return new NativeString((CharSequence)receiver);
1143     }
1144 
1145     private static CharSequence getCharSequence(final Object self) {
1146         if (self instanceof String || self instanceof ConsString) {
1147             return (CharSequence)self;
1148         } else if (self instanceof NativeString) {
1149             return ((NativeString)self).getValue();
1150         } else if (self != null && self == Global.instance().getStringPrototype()) {
1151             return "";
1152         } else {
1153             throw typeError("not.a.string", ScriptRuntime.safeToString(self));
1154         }
1155     }
1156 
1157     private static String getString(final Object self) {
1158         if (self instanceof String) {
1159             return (String)self;
1160         } else if (self instanceof ConsString) {
1161             return self.toString();
1162         } else if (self instanceof NativeString) {
1163             return ((NativeString)self).getStringValue();
1164         } else if (self != null && self == Global.instance().getStringPrototype()) {
1165             return "";
1166         } else {
1167             throw typeError( "not.a.string", ScriptRuntime.safeToString(self));
1168         }
1169     }
1170 
1171     /**
1172      * Combines ECMA 9.10 CheckObjectCoercible and ECMA 9.8 ToString with a shortcut for strings.
1173      *
1174      * @param self the object
1175      * @return the object as string
1176      */
1177     private static String checkObjectToString(final Object self) {
1178         if (self instanceof String) {
1179             return (String)self;
1180         } else if (self instanceof ConsString) {
1181             return self.toString();
1182         } else {
1183             Global.checkObjectCoercible(self);
1184             return JSType.toString(self);
1185         }
1186     }
1187 
1188     private boolean isValid(final int key) {
1189         return key >= 0 && key < value.length();
1190     }
1191 
1192     private static MethodHandle findWrapFilter() {
1193         try {
1194             return MethodHandles.lookup().findStatic(NativeString.class, "wrapFilter", MH.type(NativeString.class, Object.class));
1195         } catch (final NoSuchMethodException | IllegalAccessException e) {
1196             throw new MethodHandleFactory.LookupException(e);
1197         }
1198     }
1199 }
--- EOF ---