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
241
242
243
244
245
246
247
package Torello.Java;

import Torello.Java.Verbosity;
import Torello.Java.IOExceptionHandler;
import Torello.Java.FileNode;
import Torello.Java.FileRW;
import Torello.Java.StrPrint;
import Torello.Java.StrIndexOf;
import Torello.Java.Q;

import Torello.Java.Additional.AppendableSafe;
import Torello.Java.Additional.BiAppendable;

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

import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.io.IOException;

class SingleLineStrMatch
{
    // Re-Use the Pointer, I guess
    private static final String I4 = Helper.I4;

    static void CHECK_STRS(final String matchStr, final String replaceStr)
    {
        if (matchStr.indexOf('\n') > -1) throw new IllegalArgumentException(
            "The String which was passed to parameter 'matchStr' contains a newline character " +
            "('\\n'), at String-Index " + matchStr.indexOf('\n') + ".\n" +
            "Neither multi-line Match-Strings nor Multi-Line Replace-Strings may be processed " +
            "SED-Method 'singleLine'"
        );

        if (replaceStr.indexOf('\n') > -1) throw new IllegalArgumentException(
            "The String which was passed to parameter 'replaceStr' contains a newline character " +
            "('\\n'), at String-Index " + replaceStr.indexOf('\n') + ".\n" +
            "Neither multi-line Match-Strings nor Multi-Line Replace-Strings may be processed " +
            "SED-Method 'singleLine'"
        );

        if (matchStr.equals(replaceStr)) throw new IllegalArgumentException(
            "The String passed to parameter 'matcheStr' is identical to the String passed to " +
            "parameter 'replaceStr'.  There is nothing for SED to do."
        );
    }

    // Only Method
    static InternalSED.ResultsSED handleOneFile(
            final String        fileName,
            final FileNode      file,
            final String        fileAsStr,
            final CONFIG_RECORD userConfig
        )
        throws IOException
    {
        // Retrieve the starting String-index of each and every match in the file
        final int[] posArr = StrIndexOf.all(fileAsStr, userConfig.matchStr);

        if (posArr.length == 0) return InternalSED.NO_CHANGES_RET;


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // For-Loop: Print the Matches to System.out
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // 
        // These are used (only in one place) at the very end of this loop, for printing only.
        // They are "re-initialized" below.  The '-1' initialization is just to shut up the
        // Java-Compiler

        int prevPos = -1, prevLineNumber = -1;


        // This is a "Printing Stream" which saves PrintingRecSingleLine instances.  This allows
        // the spacing to be properly computed before actually writing the text to System.out

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

        for (int i=0; i < posArr.length; /* Loop-Increment happens inside inner for-loop */)
        {
            final int pos = posArr[i];

            // The line of text within the Text-File containing the positon of the match
            final String line = StrPrint.line(fileAsStr, pos);


            // It is POSSIBLE for there to be more than one match on a single line
            // It is NOT POSSIBLE for there to be zero matches....

            final int[] posArrLine = StrIndexOf.all(line, userConfig.matchStr);


            // 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 saverRecord = new PrintingRecSingleLine();


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Generate the Original Line... but with UNIX (Escape-Sequence) HiLiting-Colors
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            final StringBuilder sb = new StringBuilder();
            int prev = 0;


            // If the user has requested No UNIX-Colors, then this whole loop can just be skipped,
            // and the original line that was extracted may simply be printed to the terminal,
            // telling the user about the match, with no color codes.  See the Else-Statement that
            // is after this If-Branch

            if (userConfig.useUNIXColors)
            {
                // Iterate all matches on just one line of the input-text. 
                for (int j=0; j < posArrLine.length; j++)
                {
                    final int posLine = posArrLine[j];


                    // Overlapping matches need to be skipped.  The best way to determine whether
                    // there is an over-lapping match is just to check if the start of the match
                    // occurs at a String-Index that is before the end of the previous-match.  If
                    // so, just continue.
    
                    if (j > 0) if (posArr[j] < (posArr[j-1] + userConfig.MATCH_STR_LEN))
                        continue;
    
                    sb
                        .append(line.substring(prev, posLine))  // Stuff that didn't match
                        .append(BRED)
                        .append(userConfig.matchStr)            // Text that did match
                        .append(RESET);

                    prev = posLine + userConfig.MATCH_STR_LEN;
                }

                // Last-Ending Unmatched-Stuff, Don't forget to append it!
                sb.append(line.substring(prev));

                // This is the exact same "Original Line" of text, but with UNIX Color-Codes
                saverRecord.saveOriginalLine(sb.toString());

                // Reset the StringBuilder & 'prev' for the next loop
                sb.setLength(0);
                prev = 0;
            }


            // **ELSE-IF** no UNIX-Colors are used to print the message to the user, then the
            // "Original-Line" ... well ... is still the same Original-Line.  So save that.

            else saverRecord.saveOriginalLine(line);


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Generate the Updated / New Line - with Escape-Sequence Colors (or not !)
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            // Iterate all matches on just one line of the input-text. 
            for (int j=0; j < posArrLine.length; j++)
            {
                final int posLine = posArrLine[j];

                // Overlapping matches need to be skipped.  (as before, same as in code above)
                if (j > 0)
                    if (posArr[j] < (posArr[j-1] + userConfig.MATCH_STR_LEN)) 
                        continue;

                sb
                    .append(line.substring(prev, posLine))  // Stuff that didn't match
                    .append(userConfig.HILITE_START)
                    .append(userConfig.replaceStr)                     // Text that did match (updated)
                    .append(userConfig.HILITE_END);

                prev = posLine + userConfig.MATCH_STR_LEN;
            }

            // Last-Ending Unmatched-Stuff, Don't forget to append it!
            sb.append(line.substring(prev));

            // This is the exact same "Original Line" of text, but with UNIX Color-Codes
            saverRecord.saveNewLine(sb.toString());


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Generate PrintingRecSingleLine, (Actual Printing done at very end, all at once)
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            saverRecord.saveOriginalLineLength(line.length());

            saverRecord.saveNewLineLength(
                line.length() +
                (posArrLine.length * (userConfig.replaceStr.length() - userConfig.MATCH_STR_LEN))
            );


            // Only on the first iteration should we compute the line number, after the first
            // loop iteration, use the "lineNumberSince" which uses the prev-loop computations
            // This is just for efficiency purposes only.

            final int lineNumber = (i == 0)
                ? StrPrint.lineNumber(fileAsStr, pos)
                : StrPrint.lineNumberSince(fileAsStr, pos, prevLineNumber, prevPos);

            saverRecord.saveLineNumber(lineNumber);


            // Update these ... The previous line of code directly above is the only place
            // where these two are used.  They make it faster/easier to "find the line number"

            prevPos         = pos;
            prevLineNumber  = lineNumber;

            PRSLB.accept(saverRecord);


            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Don't Forget... (File-Updater Loop-Counter needs to be updated)
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            i += posArrLine.length;
        }

        // All Printing-Records as an Array
        final PrintingRecSingleLine[] prslArr =
            PRSLB.build().toArray(PrintingRecSingleLine[]::new);

        if (prslArr.length == 0) return InternalSED.NO_CHANGES_RET;

        // unless Verbosity.Silent was requested, print the matches.
        if (userConfig.verbosity.level > 0) PrintingRecSingleLine.printAll
            (prslArr, userConfig.useUNIXColors, userConfig.appendable, userConfig.verbosity);

        // Query the User about his or her feelings
        if (userConfig.askFirst) if (! Q.YN("Re-Write the Updated File to Disk?"))
            return InternalSED.NO_CHANGES_RET;

        // Remember, this single line of code does the actual replacement!
        final String newFileAsStr = fileAsStr.replace(userConfig.matchStr, userConfig.replaceStr);

        return new InternalSED.ResultsSED(newFileAsStr, prslArr.length);
    }
}