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
package Torello.Java;

import Torello.Java.Verbosity;
import Torello.Java.IOExceptionHandler;
import Torello.Java.FileNode;
import Torello.Java.FileRW;
import Torello.Java.StringParse;
import Torello.Java.Q;

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

import java.util.regex.MatchResult;
import java.util.function.Function;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.io.IOException;

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

    // 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 MatchResult[] matchResults =
            StringParse.getAllMatches(fileAsStr, userConfig.regEx, true);

        // If there aren't any matches, then skip to the next file.
        if (matchResults.length == 0) return InternalSED.NO_CHANGES_RET;


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // For-Loop: Print the Matches to System.out
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        // "Printing Stream" saves PrintingRecMultiLine instances.
        final Stream.Builder<PrintingRecMultiLine> PRMLB = Stream.builder();

        // Loop-Variable for retaining the previous PrintingRecMultiLine.  It has the File
        // Line-Number information for the previous match.  The Previous-Line Number info is
        // how Line-Number's are computed much more efficiently.

        PrintingRecMultiLine prevSaverRecord = null;            

        for (final MatchResult mr : matchResults)             
        {
            // Overlapping matches need to be skipped
            if (mr.start() < prevSaverRecord.matchPosEnd) continue;

            final String replStr = userConfig.replaceFunction.apply(mr);

            // if the user has supplied 'null', it is intended to be interpreted as "skip"
            if (replStr == null) continue;


            // If the user has just spat out the same exact original string, then just skip ahead
            // to the next match.  From the perspective of the UI-Interaction, this is kind of an 
            // important check.
            // 
            // NOTE: I got *BURNED ALIVE* by the case / situation where "replStr.length() == 0"
            //       In that case, "regionMaches" is actually returning TRUE - because there is 
            //       simply no comparison that is even occuring!
            // 
            // THEREFORE: I have added the initial "replStr.length() > 0" pre-condition check!

            if (replStr.length() > 0)
                if (fileAsStr.regionMatches(
                    mr.start(),         // toffset
                    replStr,            // other 
                    0,                  // ooffset
                    replStr.length()    // len
                ))
                    continue;


            // The "value" assigned to 'prevSaverRecord' is **NOT-UPDATED** until after it has been
            // passed to the constructor's parameter.

            prevSaverRecord = new PrintingRecMultiLine(
                fileAsStr,
                mr.start(), // match Starting-Position
                mr.end(),   // match Ending-Position
                replStr,
                prevSaverRecord,
                userConfig.useUNIXColors
            );

            PRMLB.accept(prevSaverRecord);
        }

        // All Printing-Records as an Array
        final PrintingRecMultiLine[] prmlArr =
            PRMLB.build().toArray(PrintingRecMultiLine[]::new);


        // Note that it is possible that there still weren't any changes, due to the check that 
        // goes on inside the for-loop

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

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


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Query the User, Write the Replacement
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        if (userConfig.askFirst) if (! Q.YN("Re-Write the Updated File to Disk?"))
            return InternalSED.NO_CHANGES_RET;

        final StringBuilder newFileText = new StringBuilder();

        PrintingRecMultiLine  prevPRML = null;

        for (final PrintingRecMultiLine prml : prmlArr)
        {
            // Doing this on a separate line so it is readable, could insert into line below
            final int prevEndIndex = (prevPRML == null) ? 0 : prevPRML.matchPosEnd;

            newFileText
                .append(fileAsStr.substring(prevEndIndex, prml.matchPosStart))
                .append((prevPRML = prml).replaceStr);
        }


        // A.I. told me to add this line.  I haven't checked it yet, but this is usually 
        // exactly what you are supposed to do after a loop like the one directly above

        newFileText.append(fileAsStr.substring(prevPRML.matchPosEnd));

        return new InternalSED.ResultsSED(newFileText.toString(), prmlArr.length);
    }
}