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