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
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.Pattern;
import java.util.regex.MatchResult;
import java.util.List;
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 List<FileNode> match(
            final Iterable<FileNode>            files,
            final Pattern                       regEx,
            final Function<MatchResult, String> replaceFunction,
            final boolean                       askFirst,
            final IOExceptionHandler            ioeh,
            final Appendable                    outputSaver,
            final boolean                       useUNIXColors,
            final Verbosity                     verbosity
        )
        throws IOException
    {
        Helper.CHECK(askFirst, verbosity);

        // This Stream-Builder contains the list of FileNode's that are modified.
        Stream.Builder<FileNode> ret = Stream.builder();

        Appendable appendable = (outputSaver == null)

            // If no 'outputSaver' was provided, then just send text to Standard-Out
            ? System.out

            // This just allows for printing to **BOTH** System.out **AND** the 'outputSaver'
            : new BiAppendable
                (System.out, new AppendableSafe(outputSaver, AppendableSafe.USE_APPENDABLE_ERROR));

        // A very short "Consumer" that really just prints the file-name, nothing more.
        Consumer<String> fileNamePrinter = Helper.getFileNamePrinter
            (appendable, useUNIXColors, verbosity.level);


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // MAIN-LOOP: Iterate all the FileNode's
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        for (FileNode file : files)
        {
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
            // Load the File, and then Find any / all Matches
            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

            String fileName     = file.getFullPathName();
            String fileAsStr    = null;

            // Print the file-name, if the verbosity level mandates that it be printed
            fileNamePrinter.accept(fileName);

            try
                { fileAsStr = FileRW.loadFileToString(fileName); }

            catch (IOException e)
            {
                if (ioeh != null)   ioeh.accept(file, e);
                else                throw e;

                // if ioeh ignores the exception rather than halting the program, then just continue
                // the loop on to the next match

                continue;
            }

            // Retrieve the starting String-index of each and every match in the file
            MatchResult[] matchResults = StringParse.getAllMatches(fileAsStr, regEx, true);

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


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

            // "Printing Stream" saves PrintingRecMultiLine instances.
            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 (MatchResult mr : matchResults)             

                PRMLB.accept(
                    prevSaverRecord = new PrintingRecMultiLine(
                        fileAsStr,
                        mr.start(),                 // match Starting-Position
                        mr.end(),                   // match Ending-{osition
                        replaceFunction.apply(mr),

                        // NOTE: The "value" assigned to this reference is **NOT-UPDATED** until
                        //       after it has been passed to this method's parameter.

                        prevSaverRecord,
                        useUNIXColors
                    ));

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

            if (recs.length == 0) continue;

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


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

            if (askFirst) if (! Q.YN("Re-Write the Updated File to Disk?")) continue;

            StringBuilder           newFileText = new StringBuilder();
            PrintingRecMultiLine    prevPRML    = null;

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

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

            try
                { FileRW.writeFile(newFileText, fileName); }

            catch (IOException e)
            {
                if (ioeh != null)   ioeh.accept(file, e);
                else                throw e;

                // if ioeh ignores the exception rather than halting the program, then just continue
                // the loop on to the next match

                continue;
            }

            // In "Verbosity.Verbose" mode, tell the user how many changes were updated in the file
            if (verbosity.level == 3)
                appendable.append(I4 + "Updated " + recs.length + " Matches");

            // This is a file that was updated, so put it in the returned list of "updated files"
            ret.accept(file);
        }


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Finished, so return the list of modified FileNode's
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        return ret.build().collect(Collectors.toList());
    }
}