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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package Torello.Java;

import java.util.regex.MatchResult;
import java.util.stream.Stream;
import java.util.function.Function;

import static Torello.Java.C.BGREEN;
import static Torello.Java.C.BRED;
import static Torello.Java.C.RESET;

class SingleLineRegExMatch_ONE_LINE 
{
    static class LoopHelper 
    {
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // This is a "Printing Stream" which saves PrintingRecSingleLine instances.
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // 
        // This allows the spacing to properly computed before actually writing text-information to
        // System.out
        // 
        // REMEMBER:    Because there is the possibility (more common than not) that the user has
        //              opted for UNIX-Terminal Color-Coded Output.  This means that when a line of
        //              text has been changed based on the user's request, the actual updated 
        //              String which is printed to the Terminal will differ significantly from the
        //              String which is saved to disk.
        // 
        // Hopefully, if you think about it, you will realize that the UNIX Color-Codes are only a
        // "Visual Helper" that are used when printing to screen, and cannot be include in the 
        // output text which is wrtten to disk.

        final Stream.Builder<PrintingRecSingleLine> PRSLB = Stream.builder();


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // 3 of the 4 StringBuilder's below are "re-used" on each invocation to avoid G.C.
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        int matchCount = 0;

        final StringBuilder newFileSB                   = new StringBuilder();
        final StringBuilder updatedLineSB               = new StringBuilder();
        final StringBuilder unModifiedPrintingLineSB    = new StringBuilder();
        final StringBuilder updatedPrintingLineSB       = new StringBuilder();

        final CONFIG_RECORD userConfig;

        LoopHelper(final CONFIG_RECORD userConfig)
        { this.userConfig = userConfig; }


        // These three StringBuilder's are re-used each time a line is modified / processed
        // This likely saves the GC / Memory-Manager a ton of extra, pointless work.

        private void reset()
        {
            updatedLineSB.setLength(0);
            unModifiedPrintingLineSB.setLength(0);
            updatedPrintingLineSB.setLength(0);
        }
    }

    static void handleOneLine(
            final String        line,
            final int           curLineNum,
            final LoopHelper    loopHelper
        )
    {
        // It is POSSIBLE for there to be more than one match on a single line
        // Retrieve the starting String-index of each and every match in the line

        final MatchResult[] matchResultsLine =
            StringParse.getAllMatches(line, loopHelper.userConfig.regEx, false);


        // If this lines of text doesn't have any Reg-Ex matches, then just append the original 
        // line of text into the Output-File StringBuilder.  Remember that the Input-Parameter 
        // 'line' has left off the ending '\n', so make sure to append that as well.

        if (matchResultsLine.length == 0)
        {
            loopHelper.newFileSB.append(line).append('\n');
            return;
        }

        loopHelper.reset();
        int modifiedCount = allThreeVariants(line, matchResultsLine, loopHelper);

        // It is possible for the MatchResult-Lambda to output the same line, and when that
        // happens, it should just return the original line..

        if (modifiedCount == 0)
        {
            loopHelper.newFileSB.append(line).append('\n');
            return;
        }

        loopHelper.matchCount += modifiedCount;
 
        // This is the modified line...
        final String updated = loopHelper.updatedLineSB.toString();

        final String original_TO_SCREEN = loopHelper.userConfig.useUNIXColors
            ? loopHelper.unModifiedPrintingLineSB.toString()
            : line;

        final String updated_TO_SCREEN  = loopHelper.userConfig.useUNIXColors
            ? loopHelper.updatedPrintingLineSB.toString()
            : updated;

        loopHelper.newFileSB.append(updated).append('\n');


        // The purpose of this Record is to save output-data into a record, so that the spacing may
        // be computed **BEFORE** printing to System.out.  This allows for making sure that all
        // String-Matches are **EVENLY-SPACED** when printed to System.out

        final PrintingRecSingleLine prslRec = new PrintingRecSingleLine();

        prslRec.saveOriginalLine(original_TO_SCREEN);
        prslRec.saveOriginalLineLength(line.length());
        prslRec.saveNewLine(updated_TO_SCREEN);
        prslRec.saveNewLineLength(updated.length());
        prslRec.saveLineNumber(curLineNum);

        loopHelper.PRSLB.accept(prslRec);
    }

    private static int allThreeVariants(
            final String        line,
            final MatchResult[] matchResultsLine,
            final LoopHelper    loopHelper
        )
    {
        final boolean                       COLORS = loopHelper.userConfig.useUNIXColors;
        final Function<MatchResult, String> rf     = loopHelper.userConfig.replaceFunction;

        int prev = 0, modifiedCount = 0;

        // Iterate all matches on just one line of the input-text. 
        for (final MatchResult matchResultLine : matchResultsLine)
        {
            final int posStart = matchResultLine.start();


            // Overlapping matches need to be skipped.  In this for-loop, simply pretend that this
            // second, overlapping, match doesn't even exist!  (Continue the loop).

            if (posStart < prev) continue;

            final String matchStr   = matchResultLine.group();
            final String replaceStr = rf.apply(matchResultLine);
            final String prevChunk  = line.substring(prev, posStart);

            if (replaceStr != null)
            {
                // We can still print the differences, but only count a change if it is an actual 
                // change in the text !!!  The UI will show lines that have not changed, with the 
                // match hi-lited - **BUT** if the entire line is identical, it will just skip this
                // entire line completely, and move on to the next line...
                // 
                // If there are ZERO NET-EFFECTIVE changes to this line, then *YES* the body of 
                // this for-loop is a wasted effort - BUT SO WHAT?

                if (! matchStr.equals(replaceStr)) modifiedCount++;

                if (replaceStr.indexOf('\n') != -1) throw new RegExException(
                    "The Function<MatchResult, String> parameter 'replaceFunction' has " +
                    "returned a Multi-Line Replacement-String (a replacement which contains " +
                    "a newline '\\n' character):\n" +
                    replaceStr + '\n' +
                    "Why not use the Multi-Line SED Method?"
                );

                // The actual updated text
                loopHelper.updatedLineSB
                    .append(prevChunk)
                    .append(replaceStr);
            }


            // When the user supplies 'null' to the MatchResult Lambda, then it simply means he 
            // would rather not exact any change to the original string at all!  So, therefore, use
            // the original match-string.

            else loopHelper.updatedLineSB
                .append(prevChunk)
                .append(matchStr);


            // If the User has requested "useUnixColors", then there are *TWO* StringBuilder's that
            // need to be updated.
            //      ** "Unmodified Printing Line"   => The line with the original match-text
            //      ** "Updated Printing Line"      => The line of text with the replacement text
            // 
            // If "useUnixColors" is false => We can just print the Actual-Original Line of Text,
            // and the Updated Line of Text (meaning those two StringBuilder's simply aren't used!)

            if (COLORS)
            {
                // This is the "original line of text", with the Strings that have matched being
                // hilited using UNIX-Terminal Color-Codes

                loopHelper.unModifiedPrintingLineSB
                    .append(prevChunk)
                    .append(BRED)
                    .append(matchStr)
                    .append(RESET);


                // This is the "updated line of text".  If the user has opted for UNIX-Terminal
                // Color-Codes, then they will be used to hi-lite the modifications.

                loopHelper.updatedPrintingLineSB
                    .append(prevChunk)
                    .append(BGREEN)
                    .append(replaceStr)
                    .append(RESET);
            }

            prev = matchResultLine.end();
        }


        // Last-Ending Unmatched-Stuff, Don't forget to append it!
        // This is always called the "Trailing Append"

        final String endingChunk = line.substring(prev);

        loopHelper.updatedLineSB.append(endingChunk);

        if (COLORS)
        {
            loopHelper.unModifiedPrintingLineSB.append(endingChunk);
            loopHelper.updatedPrintingLineSB.append(endingChunk);
        }

        return modifiedCount;
    }
}