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