1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package Torello.Java;

import Torello.Java.Function.ToCharIntTFunc;
import Torello.Java.StringParse;

import java.util.stream.IntStream;

class StrArrToCharReplFunc
{
    static String replace(
            final String                    s,
            final boolean                   ignoreCase,
            final String[]                  matchStrs,
            final ToCharIntTFunc<String>    replaceFunction
        )
    {
        // Loop simply checks for null pointers.
        for (int i=0; i < matchStrs.length; i++)

            if (matchStrs[i] == null) throw new NullPointerException(
                "The " + i + StringParse.ordinalIndicator(i) + " of array parameter " +
                "'matchStrs' is null."
            );


        // NOTE: This builder used to save the indices where matches are found.
        //
        // The IntStream that we are building will be "interleaved" in the sense that each integer
        // in an *EVEN* location in the output array shall represent the starting-index of a 
        // sub-string match, and the *ODD* integer (the one that immediately follows it) represents
        // the ending-index of a sub-string match.

        IntStream.Builder b = IntStream.builder();

        // This is used to keep track of the size-change of the output string
        int delta = 0;


        // Main Loop: Builds a replacement StringBuilder.  Looks for matches between the input
        // String 's', and the matchStrs in array String[] matchStrs.

        TOP:
        for (int i=0; i < s.length(); i++)

            for (int j=0; j < matchStrs.length; j++)

                if (s.regionMatches(ignoreCase, i, matchStrs[j], 0, matchStrs[j].length()))
                {
                    // SEE NOTE ABOVE: When a match is found, the starting-index of the 
                    // substring match is appended to the IntStream, then IMMEDIATELY AFTERWARDS
                    // the ending-index of the substring match is appended.

                    int len = matchStrs[j].length();

                    b.accept(i);
                    b.accept(i + len);


                    // The change in size of the output String is precisely the length of the
                    // match minus 1.  A substring is being replaced by a character.

                    delta += (len - 1);


                    // A Match was found, so skip to the next location.  Move past the match
                    // that was just identified.

                    i += (len -1);

                    continue TOP;
                }
        
        // Keeps track of all the locations in the original string where matches occurred.
        int[] whereArr = b.build().toArray();

        // If there were no matches, return the original String
        if (whereArr.length == 0) return s;

        // The output string will be stored here.
        char[] cArr = new char[s.length() - delta];

        // These are the array indices of both arrays, and the original string
        int i           = 0;    // Match-String Location Pointer Array
        int oldStrPos   = 0;    // Pointer to Input-String index
        int newStrPos   = 0;    // Pointer to Output Char-Array index

        // Iterate and replace the substrings.
        while (i < whereArr.length)
        {
            if (oldStrPos == whereArr[i])
            {
                // Ask the "Replace Function" for the replacement character for the matched
                // substring
                //
                // NOTE: Each *EVEN* location in the whereArr contains a start-index, and the
                //       *ODD* location immediately following contains an end-index.  These
                //       start-end pair identify the substring matches that were found.
                //
                // NOW:  Grab that substring, and pass it to the parameter-provided (user
                //       provided)  "replaceFunction" which informs this method what character
                //       to use when replacing a substring

                cArr[newStrPos++] = replaceFunction.apply
                    (whereArr[i], s.substring(whereArr[i], whereArr[i+1]));


                // Advance the "pointers" (advance the array indices)
                //
                // The pointer to the "source array" (a.k.a. the "original array") should be
                // advanced to the location of the end of the match we just found.  That end
                // was pointed at by the start-end *PAIR* whereArr[i] ... whereArr[i+1].
                //
                // NOTE: the substring match is "exclusive of" the actual character
                //       located at whereArr[i+1].  Specifically, in the original string, there
                //       was a sub-string match to replace with a single character that began
                //       at original-string index/location whereArr[i] and continuing to 
                //       index/location (whereArr[i+1]-1)
                //
                //       Java's java.lang.String.subtring(star, end) is *ALWAYS* exclusive of
                //       the character located at 'end'

                oldStrPos = whereArr[i+1];


                // Skip the "pair" (starting-index and ending-index).   Note that at the end
                // of the loop, variable 'i' shall *ALWAYS* be an even-number.

                i += 2;
            }

            else cArr[newStrPos++] = s.charAt(oldStrPos++);
        }


        // Just like in HTMLNode Parse, the trailing ("tail") of characters after the final
        // match in the String need to be appended to the output char[] array.  These are
        // "missed" or "skipped" by the above replacement loop.  (Similar to a "trailing read")

        while (newStrPos < cArr.length) cArr[newStrPos++] = s.charAt(oldStrPos++);


        // AGAIN: All of the replacements where done on a "char[] array".  Convert that array
        //        to an actual String, and return it to the user.

        return new String(cArr);
    }
}