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