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); } } |