• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package std.core;
17
18const MAX_CODE_POINT = 0x10FFFF
19const MAX_CODE_UNIT = 0xFFFF
20
21type StringOrRegExp = String | RegExp
22
23/**
24 * Unicode string
25 */
26export class String extends Object implements Comparable<String>, JSONable<String> {
27
28    // The constructors below are implemented via initobj instruction
29    /**
30     * Constructs an empty String
31     */
32    public constructor() {}
33
34    /**
35     * Constructs String from chars array initializer
36     *
37     * @param data initializer
38     */
39    public constructor(data: char[]) {}
40
41    /**
42     * Constructs String from another String
43     *
44     * @param otherStr initializer
45     */
46    public constructor(otherStr: String) {}
47
48    /**
49     * Checks equality of this string and another Object as String
50     *
51     * @param to another object to compare
52     *
53     * @returns true if strings are equal and false otherwise
54     *
55     * @remarks
56     * Implemented as native function,  @see `equals()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L596).
57     */
58    public native override equals(to: NullishType): boolean;
59
60    /**
61     * Length of this string
62     *
63     * @returns length of this string
64     *
65     * @remarks
66     * Implemented as native function,  @see `getLength()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L563).
67     */
68    public native getLength(): int;
69
70    /**
71     * Length of this string
72     *
73     * @returns length of this string
74     */
75    public get length(): number {
76        return this.getLength() as number
77    }
78
79    /**
80     * Getter for char at some index
81     *
82     * @param index index in char array inside String
83     *
84     * @returns char value at index
85     *
86     * @throws StringIndexOutOfBoundsException if index is negative or >= length
87     *
88     * @remarks
89     * Implemented as native function,  @see `charAt()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L585).
90     */
91    public charAt(index: number): String {
92        let c = new char[1];
93        c[0] = this.charAt(index as int)
94        return new String(c)
95    }
96
97    /**
98     * Getter for char at some index
99     *
100     * @param index index in char array inside String
101     *
102     * @returns char value at index
103     *
104     * @throws StringIndexOutOfBoundsException if index is negative or >= length
105     *
106     * @remarks
107     * Implemented as native function,  @see `charAt()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L585).
108     */
109    public native charAt(index: int): char;
110
111    /**
112     * Checks if this string is empty
113     *
114     * @returns true if empty and false otherwise
115     * @remarks
116     * Implemented as native function,  @see `length()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L574).
117     */
118    public native isEmpty(): boolean;
119
120    /**
121     * Gets the codepoint at the specified index in this string.
122     * Is similar to charAt(index), but if the character at the
123     * index is the beginning of a surrogate pair, the int
124     * representation for this codepoint is returned. Otherwise,
125     * the character at the index is returned.
126     *
127     * @param index index of the potential surrogate pair
128     *
129     * @throws StringIndexOutOfBoundsException if index is negative or >= length
130     *
131     * @returns the codepoint at the specified index
132     */
133    public codePointAt(index: number): Number | undefined {
134        if (index < 0 || index >= this.getLength()) {
135            return undefined
136        }
137        return this.codePointAt(index as int) as number
138    }
139
140    /**
141     * Gets the codepoint at the specified index in this string.
142     * Is similar to charAt(index), but if the character at the
143     * index is the beginning of a surrogate pair, the int
144     * representation for this codepoint is returned. Otherwise,
145     * the character at the index is returned.
146     *
147     * @param index index of the potential surrogate pair
148     *
149     * @throws StringIndexOutOfBoundsException if index is negative or >= length
150     *
151     * @returns the codepoint at the specified index
152     */
153    public codePointAt(index: int): int {
154        assert index >= 0 && index < this.getLength(): "Index is out of bound";
155        let highValue: char = this.charAt(index);
156        if (!Char.isHighSurrogate(highValue) || ++index == this.getLength()) {
157            return highValue as int;
158        }
159        let lowValue: char = this.charAt(index);
160        if (!Char.isLowSurrogate(lowValue)) {
161            return highValue as int;
162        }
163        return Char.charsToCodePoint(highValue, lowValue);
164    }
165
166    /**
167     * Returns the amount of full codepoints between begin and end
168     * indexes. Characters outside the range are not counted, even if
169     * the range ends in the middle of a surrogate pair.
170     *
171     * @param begin index to start from
172     * @param end past the ending index
173     *
174     * @throws StringIndexOutOfBoundsException if begin is negative or >= length
175     * @throws StringIndexOutOfBoundsException if end >= length
176     * @throws AssertionError if begin > end
177     *
178     * @returns the amount of completed codepoints
179     */
180    public codePointCount(begin: number, end: number): number {
181        return this.codePointCount(begin as int, end as int)
182    }
183
184    /**
185     * Returns the amount of full codepoints between begin and end
186     * indexes. Characters outside the range are not counted, even if
187     * the range ends in the middle of a surrogate pair.
188     *
189     * @param begin index to start from
190     * @param end past the ending index
191     *
192     * @throws StringIndexOutOfBoundsException if begin is negative or >= length
193     * @throws StringIndexOutOfBoundsException if end >= length
194     * @throws AssertionError if begin > end
195     *
196     * @returns the amount of completed codepoints
197     */
198    public codePointCount(begin: int, end: int): int {
199        assert begin <= end: "Begin idx must be less then end idx";
200        let counter: int = 0;
201        for (let i: int = begin; i < end; i++) {
202            ++counter;
203            if (Char.isHighSurrogate(this.charAt(i)) &&
204                (i + 1 < end) &&
205                Char.isLowSurrogate(this.charAt(i + 1))) {
206                ++i;
207            }
208        }
209        return counter;
210    }
211
212    /**
213     * Gets the full char sequence that is representing
214     * this string.
215     *
216     * @returns char[] array
217     */
218    public getChars(): char[] {
219        return this.getChars(0, this.getLength());
220    }
221
222    /**
223     * Gets the char sequence that is representing the part of
224     * this string between begin and end indexes. The range is
225     * a half-interview [begin, end).
226     *
227     * @param begin index to start from
228     * @param end past the ending index
229     *
230     * @throws StringIndexOutOfBoundsException if begin > end
231     * @throws StringIndexOutOfBoundsException if begin is negative or >= length
232     * @throws StringIndexOutOfBoundsException if end > length
233     *
234     * @returns char[] array
235     *
236     * @remark
237     * Implemented as native function,  @see `getChars()` intrinsic [declaration](https://gitee.com/openharmony-sig/arkcompiler_runtime_core/blob/master/plugins/ets/runtime/ets_libbase_runtime.yaml#L231).
238     */
239    public native getChars(begin: int, end: int): char[];
240
241    /**
242     * Gets the byte sequence that is representing the part of
243     * this string between begin and end indexes. The range is
244     * a half-interview [begin, end).
245     *
246     * @param begin index to start from
247     * @param end past the ending index
248     *
249     * @throws StringIndexOutOfBoundsException if begin is negative or >= length
250     * @throws StringIndexOutOfBoundsException if end >= length
251     * @throws AssertionError if begin > end
252     *
253     * @returns char[] array
254     */
255    public getBytes(begin: int, end: int): byte[] {
256        assert begin <= end: "Begin idx must be less then end idx";
257        let resChars: char[] = this.getChars(begin, end);
258        let resBytes: byte[] = new byte[resChars.length];
259        for (let i: int = 0; i < resChars.length; i++) {
260            resBytes[i] = resChars[i] as byte;
261        }
262        return resBytes;
263    }
264
265    /**
266     * Compares the given StringBuilder to this String. The
267     * result is true if the StringBuilder has the same content
268     * as this String at this moment and false otherwise.
269     *
270     * @param sb StringBuilder to compare to
271     *
272     * @throws NullPointerException if sb param is null
273     *
274     * @returns true if StringBuilder has the same String
275     */
276    public contentEquals(sb: StringBuilder): boolean {
277        return this.equals(sb.toString());
278    }
279
280    /**
281     * Lexicographical comparison between this String and another one.
282     * The result is less than 0 if this string sorts before the other,
283     * 0 if they are equal, and greater than 0 otherwise.
284     *
285     * @param other String to compare with
286     *
287     * @throws NullPointerException if to param is null
288     *
289     * @returns the comparison result
290     */
291    public override compareTo(other: String): int {
292        let to: String = other as String;
293        let len: int = min(this.getLength(), to.getLength()) as int;
294        for (let i: int = 0; i < len; i++) {
295            let lhsCh: Char = new Char(this.charAt(i));
296            let rhsCh: Char = new Char(to.charAt(i));
297            let tmpRes: int = lhsCh.compareTo(rhsCh);
298            if (tmpRes != 0) {
299                return tmpRes;
300            }
301        }
302        return this.getLength() - to.getLength();
303    }
304
305    /**
306     * Comparison between this String and another one based on locale.
307     * The result is -1 if this string sorts before the another string,
308     * 0 if they are equal, and 1 otherwise.
309     *
310     * @param another String to compare with
311     * @param locale String representing the BCP47 language tag
312     *
313     * @throws RangeError if the locale tag is invalid or not found
314     * @throws NullPointerException if another or locale is null
315     *
316     * @returns the comparison result
317     */
318    public native localeCompare(another: String, locale: String | null): short;
319
320    /**
321     * Comparison between this String and another one based on default
322     * host locale. The result is -1 if this string sorts before the
323     * another string, 0 if they are equal, and 1 otherwise.
324     *
325     * @param another String to compare with
326     *
327     * @throws RangeError if the locale tag is invalid or not found
328     * @throws NullPointerException if another param is null
329     *
330     * @returns the comparison result
331     */
332    public localeCompare(another: String): short {
333        return this.localeCompare(another, null);
334    }
335
336    /**
337     * Checks that the substring of this string that starts from
338     * the specified index starts with the specified prefix.
339     * Negative `fromIndex` is treated as 0. Result is always true
340     * if `prefix` is empty.
341     *
342     * @param prefix prefix string
343     * @param fromIndex index to start from
344     *
345     * @throws NullPointerException if prefix param is null
346     *
347     * @returns true if the substring begins with prefix
348     */
349    public startsWith(prefix: String, fromIndex?: Number): boolean {
350        if (fromIndex == undefined) {
351            return this.startsWith(prefix, 0)
352        }
353        return this.startsWith(prefix, fromIndex!.intValue())
354    }
355
356    /**
357     * Checks that the substring of this string that starts from
358     * the specified index starts with the specified prefix.
359     *
360     * @param prefix prefix string
361     * @param fromIndex index to start from
362     *
363     * @throws NullPointerException if prefix param is null
364     * @throws StringIndexOutOfBoundsException if fromIndex param is negative or >= length
365     *
366     * @returns true if the substring begins with prefix
367     */
368    public startsWith(prefix: String, fromIndex: int): boolean {
369        let prefixLen = prefix.getLength()
370        if (fromIndex < 0) {
371            fromIndex = 0;
372        }
373        if (fromIndex + prefixLen > this.getLength() && !prefix.isEmpty()) {
374            return false;
375        }
376        for (let i: int = 0; i < prefixLen; i++) {
377            if (this.charAt(fromIndex + i) != prefix.charAt(i)) {
378                return false;
379            }
380        }
381        return true;
382    }
383
384    /**
385     * Checks that this string ends with the specified suffix.
386     * Result is always true if `suffix` is empty.
387     *
388     * @param suffix suffix string
389     *
390     * @param endPosition at which suffix is expected to be found. Defaults to str.length.
391     *
392     * @throws NullPointerException if suffix param is null
393     *
394     * @returns true if this string ends with suffix
395     */
396    public endsWith(suffix: String, endPosition?: Number): boolean {
397        let ep = this.getLength()
398        if (endPosition != undefined) {
399            ep = endPosition!.unboxed() as int
400        }
401        let suffixLen = suffix.getLength()
402        let fromIndex: int = ep - suffixLen;
403        if (fromIndex < 0 && !suffix.isEmpty()) {
404            return false;
405        }
406        for (let i: int = 0; i < suffixLen; i++) {
407            if (this.charAt(fromIndex + i) != suffix.charAt(i)) {
408                return false;
409            }
410        }
411        return true;
412    }
413
414    /**
415     * Computes the hashcode for this String.
416     *
417     * @returns hashcode value of this String
418     */
419    public native override $_hashCode(): int;
420
421    /**
422     * Finds the first occurrence of a character in this String.
423     *
424     * @param ch to find
425     *
426     * @returns index of the character from the beginning of this string, or -1 if not found
427     */
428    public native indexOf(ch: char): int;
429
430    /**
431     * Finds the first occurrence of a character in this String at position >= fromIndex.
432     * Negative fromIndex is equivalent to 0, and fromIndex >= length implies no match.
433     *
434     * @param ch to find
435     * @param fromIndex to start searching from
436     *
437     * @returns index of the character from the beginning of this string, or -1 if not found
438     */
439    public native indexOf(ch: char, fromIndex: int): int;
440
441    /**
442     * Finds the first occurrence of another String in this String
443     *
444     * @param str to find
445     *
446     * @param fromIndex to start searching from
447     *
448     * @returns index of the str from the beginning of this string, or -1 if not found
449     */
450    public indexOf(str: String, fromIndex?: Number): number {
451        if (fromIndex == undefined) {
452            return this.indexOf(str, 0)
453        }
454        return this.indexOf(str, fromIndex!.intValue())
455    }
456
457    /**
458     * Finds the first occurrence of another String in this String at position >= fromIndex.
459     * Negative fromIndex is equivalent to fromIndex = 0, and fromIndex >= length implies no match.
460     *
461     * @param str to find
462     * @param fromIndex to start searching from
463     *
464     * @returns index of the str from the beginning of this string, or -1 if not found
465     */
466    public native indexOf(str: String, fromIndex: int): int;
467
468    /**
469     * Finds the last occurrence of a character in this String.
470     *
471     * @param ch to find
472     *
473     * @returns index of the character from the beginning of this string, or -1 if not found
474     */
475    public lastIndexOf(ch: char): int {
476        return this.lastIndexOf(ch, this.getLength());
477    }
478
479    /**
480     * Finds the last occurrence of a character in this String at position <= fromIndex.
481     * All values of fromIndex >= length are equivalent, and negative fromIndex implies no match.
482     *
483     * @param ch to find
484     * @param fromIndex to start searching from
485     *
486     * @returns index of the character from the beginning of this string, or -1 if not found
487     */
488    public lastIndexOf(ch: char, fromIndex: int): int {
489        if (fromIndex >= this.getLength()) {
490            fromIndex = this.getLength() - 1;
491        }
492        for (let i: int = fromIndex; i >= 0; i--) {
493            if (this.charAt(i) == ch) {
494                return i;
495            }
496        }
497        return -1;
498    }
499
500    /**
501     * Finds the last occurrence of another String in this String.
502     *
503     * @param str to find
504     *
505     * @param fromIndex to start searching from
506    *
507     * @throws NullPointerException if str param is null
508     *
509     * @returns index of the str from the beginning of this string, or -1 if not found
510     */
511    public lastIndexOf(str: String, fromIndex?: Number): number {
512        if (fromIndex == undefined) {
513            return this.lastIndexOf(str, Int.MAX_VALUE as int)
514        }
515        return this.lastIndexOf(str, fromIndex!.intValue())
516    }
517
518    /**
519     * Finds the last occurrence of another String in this String at position <= fromIndex.
520     * All values of fromIndex >= length are equivalent, and negative fromIndex implies no match.
521     *
522     * @param str to find
523     * @param fromIndex to start searching from
524     *
525     * @returns index of the str from the beginning of this string, or -1 if not found
526     */
527    public native lastIndexOf(str: String, fromIndex: int): int;
528
529    /**
530     * Selects a substring of this String, starting at a specified index
531     * and ending at the end of this String.
532     *
533     * @param begin to start substring
534     *
535     * @returns new String which is a substring of this String
536     */
537    public substring(begin: int): String {
538        return this.substring(begin, this.getLength());
539    }
540
541    /**
542     * Selects a substring of this String, starting at a specified index
543     * and ending at index before another specified index.
544     *
545     * @param begin to start substring
546     * @param end to end before at
547     *
548     * @returns new String which is a substring of this String
549     */
550    public substring(begin: number, end?: Number): String {
551        if (end == undefined) {
552            return this.substring(begin as int)
553        }
554        return this.substring(begin as int, end!.intValue())
555    }
556
557    /**
558     * Selects a substring of this String, starting at a specified index
559     * and ending at index before another specified index.
560     *
561     * If 'begin' < 0, then 'begin' is assumed to be equal to zero.
562     * If 'begin' > this.length, then 'begin' is assumed to be equal to the this.length.
563     * If 'end' < 0, then 'end' is assumed to be equal to zero.
564     * If 'end' > this.length, then 'end' is assumed to be equal to this.length.
565     * If 'begin' > 'end', then these are swapped.
566     * If 'begin' == 'end', then an empty string is returned.
567     *
568     * @param begin to start substring
569     * @param end to end before at
570     *
571     * @returns new String which is a substring of this String
572     */
573    public native substring(begin: int, end: int): String;
574
575    /**
576     * Concatenation of this and array of strings.
577     *
578     * @param strings strings to concat with
579     *
580     * @throws NullPointerException if strings param is null
581     *
582     * @returns new String which is a concatenation of this + strings[0] + ... + strings[string.length - 1]
583     */
584    public concat(...strings: String[]): String {
585        let length = strings.length
586        if (length == 0) {
587            return this
588        }
589        if (length == 1) {
590            return StringBuilder.concatStrings(this, strings[0])
591        }
592        let res = new StringBuilder(this);
593        for (let i = 0; i < strings.length; i++) {
594            res = res.append(strings[i])
595        }
596        return res.toString();
597    }
598
599    /*
600     * Creates a String using substitutions
601     *
602     * @param d a template
603     *
604     * @param substs strings to be substituted
605     *
606     * @returns string with respected data being substituted
607     */
608    // TODO(ivan-tyulyandin): uncomment when #12735 will be fixed
609    // public static raw(d: String, substs: ...String): String {
610    //     throw new Error("String.raw: not implemented")
611    // }
612
613    /**
614     * Replaces all occurrences of the specified character
615     * with another specified character. If no specified
616     * character in this string is found then the original
617     * string will be returned
618     *
619     * @param oldCh character which occurrences will be replaced
620     * @param newCh character to replace
621     *
622     * @returns new String with replaced characters
623     */
624    public replaceChar(oldCh: char, newCh: char): String {
625        let newChars: char[] = this.getChars();
626        for (let i: int = 0; i < newChars.length; i++) {
627            if (newChars[i] == oldCh) {
628                newChars[i] = newCh;
629            }
630        }
631        return new String(newChars);
632    }
633
634    /**
635     * Checks if this String contains the specified string.
636     * The search starts from specified index (negative fromIndex is equivalent to fromIndex = 0, and fromIndex >= length implies no match).
637     *
638     * @param str string to search
639     * @param fromIndex index to start search from
640     *
641     * @throws NullPointerException if str param is null
642     *
643     * @returns true if this String contains str and false otherwise
644     */
645    public contains(str: String, fromIndex: number): boolean {
646        return this.contains(str, fromIndex as int);
647    }
648
649    /**
650     * Checks if this String contains the specified string.
651     * The search starts from specified index.
652     *
653     * @param str string to search
654     * @param fromIndex index to start search from
655     *
656     * @throws NullPointerException if str param is null
657     * @throws StringIndexOutOfBoundsException if fromIndex param is negative or >= length
658     *
659     * @returns true if this String contains str and false otherwise
660     */
661    public contains(str: String, fromIndex: int): boolean {
662        return this.indexOf(str, fromIndex) != -1;
663    }
664
665    private static splitMatch(s: String, q: int, r: String): int {
666        let rLength = r.getLength();
667        if (q + rLength > s.getLength()) {
668            return -1;
669        } else {
670            for (let i = 0; i < rLength; ++i) {
671                if (s.codePointAt(q + i) != r.codePointAt(i)) {
672                    return -1;
673                }
674            }
675            return q + rLength;
676        }
677    }
678
679    /**
680     * Splits this String by pattern and returns ordered array of substrings.
681     * The order of the resulted array corresponds to the order of the
682     * passage of this String from beginning to end. The pattern is
683     * excluded from substrings. The array is limited by some specified value.
684     *
685     * @param pattern String or RegExp to split by
686     * @param limit max length of the returned array. If it's negative then there is no limit.
687     *
688     * @throws NullPointerException if pattern param is null
689     *
690     * @returns string array contains substrings from this String
691     */
692    public split(separator: StringOrRegExp, limit?: Number): String[] {
693        if (separator instanceof RegExp) {
694            return (separator as RegExp).split(this, limit)
695        }
696        return this.split(separator as String, limit)
697    }
698
699    public split(separator: RegExp, limit?: Number): String[] {
700        return separator.split(this, limit)
701    }
702
703    public split(separator: String, limit?: Number): String[] {
704        let lim : long
705        if (limit == undefined) {
706            lim = (1 << 32) - 1
707        } else if ((limit!) == 0) {
708            return new String[0]
709        } else {
710            lim = limit!.unboxed() as long
711        }
712        let s = this.getLength()
713        if (s == 0) {
714            let z = String.splitMatch(this, 0, separator )
715            if (z != -1) {
716                return new String[0]
717            }
718            return [(this)]
719        }
720        let splittedStrings = new ArrayAsListString()
721        let lastStart = 0
722        for (let lastEnd = 0; lastEnd < s;) {
723            let separatorRight = String.splitMatch(this, lastEnd, separator)
724            assert(separatorRight <= s)
725            if (separatorRight != -1 && separatorRight != lastStart) {
726                let substr = this.substring(lastStart, lastEnd)
727                splittedStrings.pushBack(substr)
728                if (splittedStrings.size() == lim) {
729                    return splittedStrings.toArray()
730                }
731                lastStart = separatorRight
732                lastEnd = lastStart
733            } else {
734                ++lastEnd
735            }
736        }
737        let substr = this.substring(lastStart, s)
738        splittedStrings.pushBack(substr)
739        return splittedStrings.toArray()
740    }
741
742    /**
743     * Concatenates the specified string array by inserting
744     * the specified separator between all elements.
745     *
746     * @param strings string array
747     * @param delim separator between all elements
748     *
749     * @throws NullPointerException if strings param is null
750     * @throws NullPointerException if delim param is null
751     *
752     * @returns newly created string from string array and delimiter
753     */
754    public static join(strings: String[], delim: String): String {
755        return String.join(strings, delim, "", "");
756    }
757
758    /**
759     * Concatenates the specified string array by inserting
760     * the specified prefix before each element, the specified
761     * suffix after each element, and the specified separator
762     * between all elements.
763     *
764     * @param strings string array
765     * @param delim separator between all elements
766     * @param prefix prefix before each element
767     * @param suffix suffix after each element
768     *
769     * @throws NullPointerException if strings param is null
770     * @throws NullPointerException if delim param is null
771     * @throws NullPointerException if prefix param is null
772     * @throws NullPointerException if suffix param is null
773     *
774     * @returns newly created string from string array, prefix, suffix and delimiter
775     */
776    public static join(strings: String[], delim: String, prefix: String, suffix: String): String {
777        let resStr: String = "";
778        for (let i: int = 0; i < strings.length; i++) {
779            resStr += prefix + strings[i] + suffix;
780            if (i != strings.length - 1) {
781                resStr += delim;
782            }
783        }
784        return resStr;
785    }
786
787    /**
788     * Creates new string similar to this String but with
789     * all characters in lower case.
790     *
791     * @returns new string with all characters in lower case
792     */
793    public native toLowerCase(): String;
794
795    /**
796     * Creates new string similar to this String but with
797     * all characters in upper case.
798     *
799     * @returns new string with all characters in upper case
800     */
801    public native toUpperCase(): String;
802
803    /**
804     * Trims all whitespaces from the beginning and end of this String.
805     *
806     * @returns new trimmed string
807     */
808    public trim(): String {
809        return this.trimLeft().trimRight();
810    }
811
812    /**
813     * Trims all whitespaces from the beginning of this String.
814     *
815     * @returns new left trimmed string
816     */
817    public trimLeft(): String {
818        let firstNotWhiteSpaceIdx: int = this.getLength();
819        for (let i: int = 0; i < this.getLength(); i++) {
820            if (!Char.isWhiteSpace(this.charAt(i))) {
821                firstNotWhiteSpaceIdx = i;
822                break;
823            }
824        }
825        return this.substring(firstNotWhiteSpaceIdx, this.getLength());
826    }
827
828    /**
829     * Trims all whitespaces from the end of this String.
830     *
831     * @returns new right trimmed string
832     */
833    public trimRight(): String {
834        let lastNotWhiteSpaceIdx: int = -1;
835        for (let i: int = this.getLength() - 1; i >= 0; i--) {
836            if (!Char.isWhiteSpace(this.charAt(i))) {
837                lastNotWhiteSpaceIdx = i;
838                break;
839            }
840        }
841        return this.substring(0, lastNotWhiteSpaceIdx + 1);
842    }
843
844    /**
845     * Checks whether the specified char is in the specified
846     * char array or not.
847     *
848     * @param ch character to search
849     * @param data char array to search in
850     *
851     * @throws NullPointerException if data param is null
852     *
853     * @returns true if ch is in the data and false otherwise
854     */
855    private static isCharOneOf(ch: char, data: char[]): boolean {
856        for (let i: int = 0; i < data.length; i++) {
857            if (ch == data[i]) {
858                return true;
859            }
860        }
861        return false;
862    }
863
864    /**
865     * Trims all specified characters from the beginning and
866     * end of this String.
867     *
868     * @param remove that contains the characters to trim
869     *
870     * @throws NullPointerException if remove param is null
871     *
872     * @returns new trimmed string
873     */
874    public trim(remove: char[]): String {
875        return this.trimLeft(remove).trimRight(remove);
876    }
877
878    /**
879     * Trims all specified characters from the beginning this String.
880     *
881     * @param remove that contains the characters to trim
882     *
883     * @throws NullPointerException if remove param is null
884     *
885     * @returns new left trimmed string
886     */
887    public trimLeft(remove: char[]): String {
888        let firstNotSpecCharIdx: int = 0;
889        for (let i: int = 0; i < this.getLength(); i++) {
890            if (!String.isCharOneOf(this.charAt(i), remove)) {
891                firstNotSpecCharIdx = i;
892                break;
893            }
894        }
895        return new String(this.getChars(firstNotSpecCharIdx, this.getLength()));
896    }
897
898    /**
899     * Trims all specified characters from the end of this String.
900     *
901     * @param remove that contains the characters to trim
902     *
903     * @throws NullPointerException if remove param is null
904     *
905     * @returns new right trimmed string
906     */
907    public trimRight(remove: char[]): String {
908        let lastNotSpecCharIdx: int = 0;
909        for (let i: int = this.getLength() - 1; i >= 0; i--) {
910            if (!String.isCharOneOf(this.charAt(i), remove)) {
911                lastNotSpecCharIdx = i;
912                break;
913            }
914        }
915        return new String(this.getChars(0, lastNotSpecCharIdx + 1));
916    }
917
918    /**
919     * Creates a new string of a specified length in which
920     * the beginning of this String is padded with a
921     * specified character. `padStart` is an alias of this method,
922     * except the parameter order.
923     *
924     * @param pad to repeat
925     * @param count of characters in the resulting string
926     *
927     * @returns new string with padding at the beginning
928     */
929    public padLeft(pad: char, count: int): String {
930        return this.padStart(count, pad)
931    }
932
933    /**
934     * Creates a new string of a specified length in which
935     * the end of this String is padded with a specified
936     * character. `padEnd` is an alias of this method,
937     * except the parameter order.
938     *
939     * @param pad to repeat
940     * @param count of characters in the resulting string
941     *
942     * @returns new string with padding at the end
943     */
944    public padRight(pad: char, count: int): String {
945        return this.padEnd(count, pad)
946    }
947
948    /**
949     * Repeats this string count times, i.e.
950     *    a = "A",
951     *    a.repeat(2) == "AA"
952     *
953     * @param count number of repetitions of this String
954     *
955     * @throws ArgumentOutOfRangeException if count < 0
956     *
957     * @returns this string that is repeated count times
958     */
959    public repeat(count: number): String throws {
960        return this.repeat(count as int)
961    }
962
963    /**
964     * Repeats this string count times, i.e.
965     *    a = "A",
966     *    a.repeat(2) == "AA"
967    *
968    * @param count number of repetitions of this String
969    *
970    * @throws ArgumentOutOfRangeException if count < 0
971    *
972    * @returns this string that is repeated count times
973    */
974    public repeat(count: int): String throws {
975        if (count == 0) {
976            return new String();
977        }
978        if (count < 0) {
979            throw new RangeError("repeat: count is negative")
980        }
981        let length: int = this.getLength();
982        let resChars: char[] = new char[length * count];
983        for (let i: int = 0; i < length; i++) {
984            resChars[i] = this.charAt(i);
985        }
986        for (let repeat = 1; repeat < count; repeat++) {
987            copyTo(resChars, resChars, repeat * length, 0, length);
988        }
989        return new String(resChars);
990    }
991
992    /**
993     * The `toString()` method returns the string representation of the given String
994     * in the form of a copy of the original object.
995     *
996     * @returns a copy of the original String
997     */
998    public override toString(): String {
999        return new String(this);
1000    }
1001
1002    /**
1003     * The at() method takes an integer value and returns a new String consisting of the single UTF-16 code unit located
1004     * at the specified offset. This method allows for positive and negative integers. Negative integers count back from the last string character.
1005     *
1006     * @returns A String consisting of the single UTF-16 code unit located at the specified position.
1007     * Returns undefined if the given index can not be found.
1008     */
1009    public at(index: number): String throws {
1010        throw new Error("not implemented")
1011    }
1012
1013    /**
1014     * The at() method takes an integer value and returns a new String consisting of the single UTF-16 code unit located
1015     * at the specified offset. This method allows for positive and negative integers. Negative integers count back from the last string character.
1016     *
1017     * @returns A String consisting of the single UTF-16 code unit located at the specified position.
1018     * Returns undefined if the given index can not be found.
1019     */
1020    public at(index: int): Char throws {
1021        let n = this.getLength();
1022        if(index < 0 && -index <= n) {
1023            index += n
1024            return this.charAt(index)
1025        } else if (index >= 0 && index < n) {
1026            return this.charAt(index)
1027        }
1028        throw new ArgumentOutOfRangeException("undefined");
1029    }
1030
1031    private CreateHTMLString(tag: String, param: String): String{
1032        return "<" + tag + param + ">" + new String(this) + "</" + tag + ">"
1033    }
1034
1035    /**
1036     * The anchor() method creates a string that embeds a string in an <a> element with a name (<a name="...">str</a>)
1037     *
1038     * @returns A string beginning with an <a name="name"> start tag (double quotes in name are replaced with &quot;),
1039     * then the text str, and then an </a> end tag.
1040     */
1041    public anchor(name: String): String {
1042        return this.CreateHTMLString("a", " name=\"" + name + "\"")
1043    }
1044
1045    /*
1046     * The big() method creates a string that embeds a string in a <big> element (<big>str</big>), which causes a string to be displayed in a big font.
1047     */
1048    public big(): String{
1049        return this.CreateHTMLString("big", "")
1050    }
1051
1052
1053    /*
1054     * The small() method creates a string that embeds a string in a <small> element (<small>str</small>), which causes a string to be displayed in a big font.
1055     */
1056    public small(): String{
1057        return this.CreateHTMLString("small", "")
1058    }
1059
1060    /*
1061     * The blink() method creates a string that embeds a string in a <blink> element (<blink>str</blink>), which causes a string to be displayed in a big font.
1062     */
1063    public blink(): String{
1064        return this.CreateHTMLString("blink", "")
1065    }
1066
1067    /*
1068     * The bold() method creates a string that embeds a string in a <bold> element (<b>str</b>), which causes a string to be displayed in a big font.
1069     */
1070    public bold(): String{
1071        return this.CreateHTMLString("bold", "")
1072    }
1073
1074    /*
1075     * The italics() method creates a string that embeds a string in a <i> element (<i>str</i>), which causes a string to be displayed in a big font.
1076     */
1077    public italics(): String{
1078        return this.CreateHTMLString("i", "")
1079    }
1080
1081    /*
1082     * The strike() method creates a string that embeds a string in a <strike> element (<strike>str</strike>), which causes a string to be displayed in a big font.
1083     */
1084    public strike(): String{
1085        return this.CreateHTMLString("strike", "")
1086    }
1087
1088    /*
1089     * The sub() method creates a string that embeds a string in a <sub> element (<sub>str</sub>), which causes a string to be displayed in a big font.
1090     */
1091    public sub(): String{
1092        return this.CreateHTMLString("sub", "")
1093    }
1094
1095    /*
1096     * The sup() method creates a string that embeds a string in a <sup> element (<sup>str</sup>), which causes a string to be displayed in a big font.
1097     */
1098    public sup(): String{
1099        return this.CreateHTMLString("sup", "")
1100    }
1101
1102    /*
1103     * The fixed() method creates a string that embeds a string in a <tt> element (<tt>str</tt>), which causes a string to be displayed in a big font.
1104     */
1105    public fixed(): String{
1106        return this.CreateHTMLString("tt", "")
1107    }
1108
1109    /*
1110     * The fontcolor() method creates a string that embeds a string in a <font> element (<font color="...">str</font>), which causes a string to be displayed in the specified font color.
1111     */
1112    public fontcolor(color: String): String{
1113        return this.CreateHTMLString("font", " color=\"" + color + "\"")
1114    }
1115
1116    /*
1117     * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size.
1118     */
1119    public fontsize(size: number): String {
1120        return this.CreateHTMLString("font", " size=\"" + size + "\"")
1121    }
1122
1123    /*
1124     * The fontsize() method creates a string that embeds a string in a <font> element (<font size="...">str</font>), which causes a string to be displayed in the specified font size.
1125     */
1126    public fontsize(size: int): String{
1127        return this.CreateHTMLString("font", " size=\"" + size + "\"")
1128    }
1129
1130    /*
1131     * The link() method creates a string that embeds a string in an <a> element (<a href="...">str</a>), to be used as a hypertext link to another URL.
1132     */
1133    public link(link: String): String{
1134        return this.CreateHTMLString("a", " href=\"" + link + "\"")
1135    }
1136
1137    /*
1138     * The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.
1139     */
1140    public charCodeAt(index: number): number {
1141        return this.charAt(index as int);
1142    }
1143
1144    /*
1145     * The charCodeAt() method returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.
1146     */
1147    public charCodeAt(index: int): char {
1148        return this.charAt(index) as char;
1149    }
1150
1151    /**
1152     * The String.fromCharCode() static method returns a string created from the specified sequence of UTF-16 code units
1153     *
1154     * @param codes are numbers between 0 and 65535 (0xFFFF) representing a UTF-16 code unit
1155     *
1156     * @throws RangeError if codes[i] is less than 0, or is greater than 0xFFFF
1157     *
1158     * @returns string consisting of specified UTF-16 code units.
1159     *
1160     */
1161    public static fromCharCode(...codes: number[]): String {
1162        let res = new StringBuilder();
1163        for (const cu of codes) {
1164            if (cu < 0 || cu > MAX_CODE_UNIT) {
1165                throw new RangeError("Invalid code unit: " + new Number(cu).toString())
1166            }
1167            res.append(cu as char)
1168        }
1169        return res.toString()
1170    }
1171
1172    /*
1173     * The valueOf() method returns the primitive value of a String object.
1174     */
1175    public valueOf(): String {
1176        return this;
1177    }
1178
1179    /**
1180     * The includes() method performs a case-sensitive search to determine whether one string may
1181     * be found within another string, returning true or false as appropriate.
1182     *
1183     * @param searchString to be searched
1184     *
1185     * @param position within the string at which to begin searching for searchString
1186     *
1187     * @returns true if the search string is found anywhere within the given string,
1188     *  including when searchString is an empty string; otherwise, false
1189     */
1190    public includes(searchString: String, position?: Number): boolean {
1191        if (position == undefined) {
1192            return this.indexOf(searchString) == -1 ? false : true;
1193        }
1194        return this.indexOf(searchString, position!.intValue()) == -1 ? false : true;
1195    }
1196
1197    /**
1198     * The padEnd() method pads the current string with a given string (repeated, if needed)
1199     * so that the resulting string reaches a given length.
1200     * The padding is applied from the end of the current string.
1201     *
1202     * @param maxLength of the resulting string once the current str has been padded
1203     *
1204     * @param fillString to pad the current str with
1205     *
1206     * @returns string with the padString applied at the end of the current str
1207     */
1208    public padEnd(maxLength: number, fillString?: String): String {
1209        const str = (fillString == undefined) ? "\u0020" : fillString!
1210        return this.padEnd(maxLength as int, str)
1211    }
1212
1213    /*
1214     * The padEnd() method pads the current string with a given string (repeated, if needed)
1215     * so that the resulting string reaches a given length.
1216     * The padding is applied from the end of the current string.
1217     */
1218    public padEnd(end: int, ch: char): String {
1219        if (this.getLength() >= end) {
1220            return this;
1221        }
1222        let arr = new char[end]
1223        for (let i: int = 0; i < this.getLength(); i++) {
1224            arr[i] = this.charAt(i)
1225        }
1226        for(let i = this.getLength(); i < end; i++){
1227            arr[i] = ch
1228        }
1229        return new String(arr)
1230    }
1231
1232    public padEnd(end: int): String {
1233        return this.padEnd(end, ' ')
1234    }
1235
1236    public padEnd(end: int, str: String): String {
1237        if (this.getLength() >= end) {
1238            return this;
1239        }
1240        let arr = new char[end];
1241        let length = this.getLength();
1242        let strLength = str.getLength()
1243        for (let i: int = 0; i < length; i++) {
1244            arr[i] = this.charAt(i);
1245        }
1246        for (let i = 0; i < strLength && length + i < end; i++) {
1247            arr[length + i] = str.charAt(i);
1248        }
1249        for (let i = this.getLength() + strLength; i < end; i++) {
1250            arr[i] = arr[i - strLength];
1251        }
1252        return new String(arr);
1253    }
1254
1255    /**
1256     * The padStart() method pads the current string with another string (multiple times, if needed)
1257     * until the resulting string reaches the given length.
1258     * The padding is applied from the start of the resulting string.
1259     *
1260     * @param maxLength of the resulting string once the current str has been padded
1261     *
1262     * @param fillString to pad the current str with
1263     *
1264     * @returns string with the padString applied at the end of the current str
1265     */
1266    public padStart(maxLength: number, fillString?: String): String {
1267        const str = (fillString == undefined) ? "\u0020" : fillString!
1268        return this.padStart(maxLength as int, str)
1269    }
1270
1271    /*
1272     * The padStart() method pads the current string with another string (multiple times, if needed)
1273     * until the resulting string reaches the given length.
1274     * The padding is applied from the start of the current string.
1275     */
1276    public padStart(end: int, ch: char): String {
1277        let padLen = end - this.getLength();
1278        if (padLen <= 0) {
1279            return this;
1280        }
1281        let arr: char[] = new char[end]
1282        for(let i = 0; i < padLen; i++){
1283            arr[i] = ch
1284        }
1285        for (let i: int = 0; i < this.getLength(); i++) {
1286            arr[padLen + i] = this.charAt(i);
1287        }
1288        return new String(arr)
1289    }
1290
1291    public padStart(end: int): String {
1292        return this.padStart(end, ' ')
1293    }
1294
1295    public padStart(end: int, str: String): String {
1296        if (this.getLength() >= end) {
1297            return this;
1298        }
1299        let arr: char[] = new char[end];
1300        let padLen: int = end - this.getLength();
1301        for (let i: int = 0; i < this.getLength(); i++) {
1302            arr[padLen + i] = this.charAt(i);
1303        }
1304        let strLength = str.getLength()
1305        for (let i = 0; i < strLength && i < padLen; i++) {
1306            arr[i] = str.charAt(i);
1307        }
1308        for (let i = strLength; i < padLen; i++) {
1309            arr[i] = arr[i - strLength];
1310        }
1311        return new String(arr);
1312    }
1313
1314    /**
1315     * The substr() method returns a portion of the string, starting at the specified
1316     * index and extending for a given number of characters afterwards.
1317     *
1318     * @param begin is index of the first character to include in the returned substring
1319     *
1320     * @param length is number of characters to extract
1321     *
1322     * @returns new string containing the specified part of the given string
1323     */
1324    public substr(begin: number, length?: Number): String {
1325        if (length == undefined) {
1326            return this.substr(begin as int);
1327        }
1328        return this.substr(begin as int, length!.intValue());
1329    }
1330
1331    public substr(begin: number, length: number): String {
1332        return this.substr(begin as int, length as int);
1333    }
1334
1335    /*
1336     * The substr() method returns a portion of the string, starting at the specified
1337     * index and extending for a given number of characters afterwards.
1338     */
1339    public substr(begin: int): String {
1340        return this.substring(begin);
1341    }
1342
1343    public substr(begin: int, length: int): String {
1344        let end = begin + length
1345        if (end > this.getLength()) {
1346            end = this.getLength()
1347        }
1348        return this.substring(begin, end);
1349    }
1350
1351    /*
1352     * The trimEnd() method removes whitespace from the end of a string and returns a new string,
1353     * without modifying the original string. trimRight() is an alias of this method.
1354     */
1355    public trimEnd(): String {
1356        return this.trimRight()
1357    }
1358
1359    /*
1360     * The trimStart() method removes whitespace from the beginning of a string and returns a new string,
1361     * without modifying the original string. trimLeft() is an alias of this method.
1362     */
1363    public trimStart(): String {
1364        return this.trimLeft()
1365    }
1366
1367    /*
1368     * The slice() method extracts a section of a string and returns it as a new string,
1369     * without modifying the original string.
1370     */
1371    public slice(begin?: Number, end?: Number): String {
1372        const b = (begin == undefined) ? 0 : begin!
1373        const e = (end == undefined) ? this.getLength() : end!
1374        return this.slice(b as int, e as int)
1375    }
1376
1377    /*
1378     * The slice() method extracts a section of a string and returns it as a new string,
1379     * without modifying the original string.
1380     */
1381    public slice(begin: int): String {
1382        if(begin < 0) {
1383            begin += this.getLength()
1384        }
1385        return this.substring(begin)
1386    }
1387
1388    public slice(begin: number, end: number): String {
1389        return this.slice(begin as int, end as int)
1390    }
1391
1392    public slice(begin: int, end: int): String {
1393        if(begin < 0) {
1394            begin += this.getLength()
1395        }
1396        if(end < 0) {
1397            end += this.getLength()
1398        }
1399        return this.substring(begin, end)
1400    }
1401
1402    //NOTE(kirill-mitkin): Replace namedCaptures type with record type when it will be possible
1403    static getSubstitution(matched: String, str: String, position: int, captures: String[], namedCaptures: Object | undefined, replacement: String) {
1404        let matchLength = matched.getLength()
1405        let stringLength = str.getLength()
1406        assert(position >= 0 && position <= stringLength)
1407        let tailPos = position + matchLength
1408        let m = captures.length
1409        let result = ""
1410        let doubleCapture = true;
1411        for (let i: int = 0; i < replacement.getLength();) {
1412            if (i + 1 < replacement.getLength()
1413                        && replacement.charAt(i) == c'$'
1414                        && replacement.charAt(i + 1) == c'$') {
1415                result += c'$'
1416                i += 2
1417            } else if (i + 1 < replacement.getLength()
1418                        && replacement.charAt(i) == c'$'
1419                        && replacement.charAt(i + 1) == c'&') {
1420                result += matched
1421                i += 2
1422            } else if (i + 1 < replacement.getLength()
1423                        && replacement.charAt(i) == c'$'
1424                        && replacement.charAt(i + 1) == c'`') {
1425                if (position != 0) {
1426                    result += str.substring(0, position)
1427                }
1428                i += 2
1429            } else if (i + 1 < replacement.getLength()
1430                        && replacement.charAt(i) == c'$'
1431                        && replacement.charAt(i + 1) == c'\'') {
1432                if (tailPos < stringLength) {
1433                    result += str.substring(tailPos, stringLength)
1434                }
1435                i += 2
1436            } else if (i + 2 < replacement.getLength()
1437                            && doubleCapture
1438                            && replacement.charAt(i) == c'$'
1439                            && Char.isDecDigit(replacement.charAt(i + 1))
1440                            && Char.isDecDigit(replacement.charAt(i + 2))) {
1441                let firstDigit = replacement.charAt(i + 1) - c'0';
1442                let secondDigit = replacement.charAt(i + 2) - c'0';
1443                let digit = firstDigit * 10 + secondDigit;
1444                if (digit == 0 || digit > m) {
1445                    doubleCapture = false;
1446                } else {
1447                    result += captures[digit - 1];
1448                    i += 3
1449                }
1450            } else if (i + 2 < replacement.getLength()
1451                            && replacement.charAt(i) == c'$'
1452                            && Char.isDecDigit(replacement.charAt(i + 1))
1453                            && !Char.isDecDigit(replacement.charAt(i + 2))
1454                        || i + 1 < replacement.getLength()
1455                            && replacement.charAt(i) == c'$'
1456                            && Char.isDecDigit(replacement.charAt(i + 1))) {
1457                let digit = replacement.charAt(i + 1) - c'0';
1458                if (digit == 0 || digit > m) {
1459                    result += replacement.substring(i, i + 2)
1460                } else {
1461                    result += captures[digit - 1];
1462                }
1463                doubleCapture = true;
1464                i += 2
1465            } else if (replacement.charAt(i) == c'$' && replacement.charAt(i + 1) == c'<') {
1466                if (namedCaptures == undefined) {
1467                    result += "$<";
1468                } else {
1469                    let j = i + 2;
1470                    for (; j < replacement.getLength() && replacement.charAt(j) != c'>'; ++j) {}
1471                    if (j < replacement.getLength()) {
1472                        let groupName = replacement.substring(i + 2, j);
1473                        /*let capture = namedCaptures[groupName];
1474                        if (capture != undefined) {
1475                            result += capture
1476                        }
1477                        */
1478                        i = j;
1479                    } else {
1480                        result += "$<";
1481                        i += 2;
1482                    }
1483                }
1484            } else {
1485                result += replacement.charAt(i);
1486                i += 1;
1487            }
1488        }
1489        return result
1490    }
1491
1492    /**
1493     * Returns a new string with one, some, or all matches of a pattern replaced by a replacement
1494     *
1495     * @param searchValue is pattern which can be a String or RegExp
1496     *
1497     * @param replaceValue is replacement String
1498     *
1499     * @returns a new replaced string
1500     */
1501    public replace(searchValue: StringOrRegExp, replaceValue: String) : String {
1502        if (searchValue instanceof RegExp) {
1503            return (searchValue as RegExp).replace(this, replaceValue)
1504        }
1505        return this.replace(searchValue as String, replaceValue)
1506    }
1507
1508    public replace(searchValue: RegExp, replaceValue: String) : String {
1509        return searchValue.replace(this, replaceValue)
1510    }
1511
1512    public replace(searchValue: String, replaceValue: String) : String {
1513        let searchLength = searchValue.getLength()
1514        let position = this.indexOf(searchValue)
1515        if (position == -1) {
1516            return this
1517        }
1518        let preceding = this.substring(0, position)
1519        let following = this.substring(position + searchLength)
1520        let captures = new ArrayAsListString()
1521        let replacement = String.getSubstitution(searchValue, this, position as int, captures.toArray(), undefined, replaceValue)
1522        return preceding + replacement + following
1523    }
1524
1525    /**
1526     * Returns a new string with one, some, or all matches of a pattern replaced by a replacement
1527     *
1528     * @param searchValue is pattern which can be a String or RegExp
1529     *
1530     * @param replacer is replacement function
1531     *
1532     * @returns a new replaced string
1533     */
1534    public replace(searchValue: StringOrRegExp, replacer: (substr: String, args: Object[]) => String): String {
1535        if (searchValue instanceof RegExp) {
1536            return (searchValue as RegExp).replace(this, replacer)
1537        }
1538        return this.replace(searchValue as String, replacer)
1539    }
1540
1541    public replace(searchValue: RegExp, replacer: (substr: String, args: Object[]) => String): String {
1542        return searchValue.replace(this, replacer)
1543    }
1544
1545    public replace(searchValue: String, replacer: (substr: String, args: Object[]) => String): String {
1546        let searchLength = searchValue.getLength()
1547        let position = this.indexOf(searchValue)
1548        if (position == -1) {
1549            return this
1550        }
1551        let preceding = this.substring(0, position)
1552        let following = this.substring(position + searchLength)
1553        let replacement = replacer(searchValue, [new Double(position as double) as Object, this as Object] as Object[])
1554        return preceding + replacement + following
1555    }
1556
1557     /**
1558     * Returns a new string with all matches of a pattern replaced by a replacement
1559     *
1560     * @param searchValue is pattern which can be a String or RegExp
1561     *
1562     * @param replaceValue is replacement String
1563     *
1564     * @returns a new replaced string
1565     */
1566    public replaceAll(searchValue: StringOrRegExp, replaceValue: String): String {
1567        if (searchValue instanceof RegExp) {
1568            const re = searchValue as RegExp
1569            if (!re.global) {
1570                throw new Error("Global flag expected for regexp")
1571            }
1572            return re.replace(this, replaceValue)
1573        }
1574        return this.replaceAll(searchValue as String, replaceValue)
1575    }
1576
1577    public replaceAll(searchValue: RegExp, replaceValue: String): String {
1578        if (!searchValue.global) {
1579            throw new Error("Global flag expected for regexp")
1580        }
1581        return searchValue.replace(this, replaceValue)
1582    }
1583
1584    public replaceAll(searchValue: String, replaceValue: String): String {
1585        let searchLength = searchValue.getLength()
1586        let advanceBy = max(1, searchLength)
1587        let matchPositions = new ArrayAsListInt();
1588        let position = this.indexOf(searchValue, 0)
1589        while (position != -1) {
1590            matchPositions.pushBack(position)
1591            position = this.indexOf(searchValue, position + advanceBy)
1592        }
1593        let endOfLastMatch = 0
1594        let result = ""
1595        let arrayMatchPositions = matchPositions.toArray()
1596        for (let i = 0; i < arrayMatchPositions.length; ++i) {
1597            let p = arrayMatchPositions[i].unboxed()
1598            let preserved = this.substring(endOfLastMatch, p)
1599            let captures = new ArrayAsListString()
1600            let replacement = String.getSubstitution(searchValue, this, p, captures.toArray(), undefined, replaceValue)
1601            result = result + preserved + replacement
1602            endOfLastMatch = p + searchLength
1603        }
1604        if (endOfLastMatch < this.getLength()) {
1605            result += this.substring(endOfLastMatch)
1606        }
1607        return result
1608    }
1609
1610    /**
1611     * Returns a new string with all matches of a pattern replaced by a replacement
1612     *
1613     * @param searchValue is pattern which can be a String or RegExp
1614     *
1615     * @param replacer is replacement function
1616     *
1617     * @returns a new replaced string
1618     */
1619    public replaceAll(searchValue: StringOrRegExp, replacer: (substr: String, args: Object[]) => String): String {
1620        if (searchValue instanceof RegExp) {
1621            const re = searchValue as RegExp
1622            if (!re.global) {
1623                throw new Error("Global flag expected for regexp")
1624            }
1625            return re.replace(this, replacer)
1626        }
1627        return this.replaceAll(searchValue as String, replacer)
1628    }
1629
1630    public replaceAll(searchValue: RegExp, replacer: (substr: String, args: Object[]) => String): String {
1631        if (!searchValue.global) {
1632            throw new Error("Global flag expected for regexp")
1633        }
1634        return searchValue.replace(this, replacer)
1635    }
1636
1637    public replaceAll(searchValue: String, replacer: (substr: String, args: Object[]) => String): String {
1638        let searchLength = searchValue.getLength()
1639        let advanceBy = max(1, searchLength)
1640        let matchPositions = new ArrayAsListInt();
1641        let position = this.indexOf(searchValue, 0)
1642        while (position != -1) {
1643            matchPositions.pushBack(position)
1644            position = this.indexOf(searchValue, position + advanceBy)
1645        }
1646        let endOfLastMatch = 0
1647        let result = ""
1648        let arrayMatchPositions = matchPositions.toArray()
1649        for (let i = 0; i < arrayMatchPositions.length; ++i) {
1650            let p = arrayMatchPositions[i].unboxed()
1651            let preserved = this.substring(endOfLastMatch, p)
1652            let args = new ArrayAsListObject()
1653            args.pushBack(Double.valueOf(p))
1654            args.pushBack(this)
1655            let replacement = replacer(searchValue, args.toArray())
1656            result = result + preserved + replacement
1657            endOfLastMatch = p + searchLength
1658        }
1659        if (endOfLastMatch < this.getLength()) {
1660            result += this.substring(endOfLastMatch)
1661        }
1662        return result
1663    }
1664
1665      /**
1666     * Executes a search for a match between a regular expression and this String object.
1667     *
1668     * @param regexp a regular expression object or implicit regular expression
1669     *
1670     * @returns the index of the first match between the regular expression and the given string,
1671     * or -1 if no match was found.
1672     */
1673    public search(regexp: StringOrRegExp): number {
1674        if (regexp instanceof String) {
1675            return new RegExp(regexp as String).search(this)
1676        }
1677        return (regexp as RegExp).search(this)
1678    }
1679
1680    public search(implicitRegExp: String): number {
1681        return new RegExp(implicitRegExp).search(this)
1682    }
1683
1684    public search(regexp: RegExp): number {
1685        return regexp.search(this)
1686    }
1687
1688    /*
1689     * The toLocaleLowerCase() method returns the calling string value converted to lower case,
1690     * according to any locale-specific case mappings.
1691     */
1692    public native toLocaleLowerCase(locale: String): String;
1693
1694    public toLocaleLowerCase(): String {
1695        return this.toLocaleLowerCase("");
1696    }
1697
1698    /*
1699     * The toLocaleUpperCase() method returns the calling string value converted to upper case,
1700     * according to any locale-specific case mappings.
1701     */
1702    public native toLocaleUpperCase(locale: String): String;
1703
1704    public toLocaleUpperCase(): String {
1705        return this.toLocaleUpperCase("");
1706    }
1707    /**
1708     * Retrieves the result of matching a string against a regular expression
1709     *
1710     * @param regexp a regular expression object
1711     *
1712     * @returns
1713     * If the regexp.global is true, all results matching the complete regular expression will be returned,
1714     * but capturing groups are not included
1715     * Otherwise, only the first complete match and its related capturing groups are returned
1716     */
1717    public match(regexp: StringOrRegExp): RegExpMatchArray | null {
1718        if (regexp instanceof String) {
1719            return new RegExp(regexp as String).match(this)
1720        }
1721        return (regexp as RegExp).match(this)
1722    }
1723
1724    /**
1725     * Returns an iterator of all results matching a string against a regular expression,
1726     * including capturing groups
1727     *
1728     * @param regexp a regular expression object
1729     */
1730    public matchAll(reg: RegExp): IterableIterator<RegExpMatchArray> {
1731        let flags = reg.flags;
1732        if (!reg.global) {
1733            throw new Error("matchAll must be called with a global RegExp")
1734        }
1735        return reg.matchAll(this)
1736    }
1737
1738    public native normalizeNFC(): String;
1739
1740    public native normalizeNFD(): String;
1741
1742    public native normalizeNFKC(): String;
1743
1744    public native normalizeNFKD(): String;
1745
1746    /**
1747     * The normalize() method of String values returns the Unicode Normalization Form of this string
1748     *
1749     * @param form is "NFC" or "NFD" or "NFKC" or "NFKD"
1750     *
1751     * @throws RangeError if form is not "NFC" or "NFD" or "NFKC" or "NFKD"
1752     *
1753     * @returns the Unicode Normalization Form of the string
1754     */
1755    public normalize(form?: String): String {
1756        const f = (form == undefined) ? "NFC" : form!
1757        switch (f) {
1758        case "NFC":
1759            return this.normalizeNFC()
1760        case "NFD":
1761            return this.normalizeNFD()
1762        case "NFKC":
1763            return this.normalizeNFKC()
1764        case "NFKD":
1765            return this.normalizeNFKD()
1766        default:
1767            throw new RangeError("The normalization form should be one of NFC, NFD, NFKC, NFKD.")
1768        }
1769    }
1770
1771    /*
1772     * The toWellFormed() method of String values returns a string where all lone surrogates of
1773     * this string are replaced with the Unicode replacement character U+FFFD.
1774     */
1775    public toWellFormed(): String {
1776        return this.normalize()
1777    }
1778
1779    private static native codePointToChar(cp: int): char
1780
1781    /**
1782     * The String.fromCodePoint() static method returns a string created by using the specified sequence of code points
1783     *
1784     * @param codePoints are integers between 0 and 0x10FFFF (inclusive) representing a Unicode code point
1785     *
1786     * @throws RangeError if codePoints[i] is less than 0, or is greater than 0x10FFFF
1787     *
1788     * @returns string created by using the specified sequence of code points
1789     */
1790    public static fromCodePoint(...codePoints: number[]): String {
1791        let res = new StringBuilder();
1792        for (const cp of codePoints) {
1793            if (cp < 0 || cp > MAX_CODE_POINT) {
1794                throw new RangeError("Invalid code point: " + new Number(cp).toString())
1795            }
1796            res.append(String.codePointToChar(cp as int))
1797        }
1798        return res.toString()
1799    }
1800
1801    /*
1802     * The isWellFormed() method of String values returns a boolean indicating whether this string contains any lone surrogates.
1803     */
1804    public native isWellFormed(): boolean;
1805
1806    /**
1807     * Creates a String instance based on JSONValue
1808     *
1809     * @param json: JSONValue - a JSON representation
1810     *
1811     * @throws JSONTypeError if json does not encode a valid String
1812     *
1813     * @returns String - string value decoded from JSON
1814     */
1815    static createFromJSONValue(json: JSONValue): String {
1816        if (json instanceof JSONString) {
1817            return (json as JSONString).value
1818        }
1819        throw new JSONTypeError("Cannot create String from JSON", json)
1820    }
1821
1822    /**
1823     * Check if a string is compressed
1824     *
1825     * @param s string to be checked
1826     *
1827     * @returns true - if s is compressed, false - otherwise
1828     */
1829    public native isCompressed(): boolean;
1830
1831}
1832