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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package Torello.Java;

import Torello.Java.ParallelArrayException;
import java.util.stream.IntStream;

class CharArrToStrArr
{
    static String replace(
            final boolean   ignoreCase,
            final String    s, 
            final char[]    matchCharsInput,
            final String[]  replaceStrs
        )
    {
        // Make sure these arrays are Parallel, and throw ParallelArrayException if not
        // If 'replaceStrs' contains null-values, throw NullPointerException

        ParallelArrayException.check
            (replaceStrs, "replaceStrs", true, matchCharsInput, "matchChars");


        // If the case of the characters is being ignored, it is easier to just set them all
        // to lower-case right now.

        final char[] matchChars;

        if (ignoreCase)
        {
            matchChars = new char[matchCharsInput.length]; // matchCharsInput.clone();
            for (int i=0; i < matchChars.length; i++)
                matchChars[i] = Character.toLowerCase(matchCharsInput[i]);
        }

        else matchChars = matchCharsInput;
    
        // Java Stream's shall keep records of *WHERE* and *WHICH* the matches occurs
        IntStream.Builder   whereB  = IntStream.builder();
        IntStream.Builder   whichB  = IntStream.builder();
        int                 delta   = 0; // string length change


        // This part of the code finds the locations of all the matches in the input string.
        // It does not build the new String, but rather, finds indexes first.  This way a
        // char[] array can be built, and then populated with the updated sub-strings.

        TOP:
        for (int i=0; i < s.length(); i++)
        {
            char c = ignoreCase ? Character.toLowerCase(s.charAt(i)) : s.charAt(i);

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

                if (c == matchChars[j])
                {
                    // Save the "original String index" of WHERE the match occurred
                    whereB.accept(i);

                    // Save the "match index" of WHICH char-match has occurred
                    whichB.accept(j);

                    // Keep a record of the 'delta' - and this is the size of the string to insert.
                    delta += replaceStrs[j].length() - 1;
                    
                    continue TOP;
                }
        }

        // List of indices into the input-String for WHERE matches occurred.
        int[] whereArr = whereB.build().toArray();

        // List of indices into the match-array for WHICH matches occurred.
        int[] whichArr = whichB.build().toArray();


        // IMPORTANT: If no matches in input char[] array 'matchChars' were found or identified
        //            inside the input-string, return the original String immediately, with no
        //            changes!

        if (whereArr.length == 0) return s;


        // The new "Char Array" which will be built into a String.  The "change in size" 
        // was computed earlier

        char[]  cArr = new char[s.length() + delta];

        // These are some loop-control variables
        int oldStrPos   = 0;
        int newStrPos   = 0;
        int matchNum    = 0;


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // "Pre-Loop Priming Update" or "PRIMING READ" - populates char array with replace-strings
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        // copies the first non-matching sub-string portion to the cArr[]
        if (whereArr[0] > 0)
        {
            s.getChars(0, whereArr[0], cArr, 0);

            // Advance the pointers
            newStrPos = oldStrPos = whereArr[0]; 
        }


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Iterate through each of the matches
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        while (matchNum < whichArr.length)
        {
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Copy the next MATCHING char-substitute String from the "Replacement Strings Array"
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            String replaceStr = replaceStrs[whichArr[matchNum]];

            /*
                FROM: Java's JDK Documentation for java.lang.String

                public void getChars(   int srcBegin,
                                        int srcEnd,
                                        char[] dst,
                                        int dstBegin    )
                            
                Copies characters from this string into the destination character array.

                Parameters:
                    srcBegin    - index of the first character in the string to copy.
                    srcEnd      - index after the last character in the string to copy.
                    dst         - the destination array.
                    dstBegin    - the start offset in the destination array.

                // OLD CODE: 
                int len = replaceStr.length();
                for (int i=0; i < len; i++) cArr[newStrPos++] = replaceStr.charAt(i);

            */
            replaceStr.getChars(0, replaceStr.length(), cArr, newStrPos);

            // In the new (output) string (currently a char[]), we have added "len" characters
            newStrPos += replaceStr.length();


            // Since we are replacing a *SINGLE* character with a new (replacement) String,
            // the pointer to the source / old string is advanced by *ONLY* one.

            oldStrPos++;


            // This "index" is pointing to an array that is holding the match information.
            // Essentially, here, we are just moving on to the next match.  Therefore
            // increment by only 1.

            matchNum++;


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Copy the next NON-MATCHING PORTION sub-string from the "Old Input String"
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            // Have We copied the entire string yet?
            if (oldStrPos < s.length())
            {
                int endCopyPos = (matchNum < whichArr.length) ? whereArr[matchNum] : s.length();

                s.getChars(oldStrPos, endCopyPos, cArr, newStrPos);


                // We have just copied (end - oldStrPos) characters to the new array, from the
                // old array.  Advance the newStrPos by this many characters.

                newStrPos += (endCopyPos - oldStrPos);


                // Advance the oldStr by the same number of character.  The line of code below
                // will be identical to this line of code: oldStrpos += (endCopyPos - oldStrPos);
                // Same as oldStrPos += (endCopyPos - oldStrPos) (Obviously!)

                oldStrPos = endCopyPos; 
            }
        }

        // Convert the char[] array (cArr) into a String, and return it.
        return new String(cArr);
    }    
}