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