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