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 | package Torello.Java; import Torello.Java.Verbosity; import Torello.Java.IOExceptionHandler; import Torello.Java.FileNode; import Torello.Java.FileRW; import Torello.Java.StrIndexOf; import Torello.Java.Q; import Torello.Java.Additional.AppendableSafe; import Torello.Java.Additional.BiAppendable; import java.util.List; import java.util.function.Consumer; import java.util.stream.Stream; import java.util.stream.Collectors; import java.io.IOException; class MultiLineStrMatch { // Re-Use the Pointer, I guess private static final String I4 = Helper.I4; // Only Method static List<FileNode> match( final Iterable<FileNode> files, final String matchStr, final String replaceStr, final boolean askFirst, final IOExceptionHandler ioeh, final Appendable outputSaver, final boolean useUNIXColors, final Verbosity verbosity ) throws IOException { Helper.CHECK(askFirst, verbosity); // This value is used repeatedly in this loop, so it is better left as a constant final int MATCH_STR_LEN = matchStr.length(); // 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 int[] posArr = StrIndexOf.all(fileAsStr, matchStr); if (posArr.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 (int i=0; i < posArr.length; i++) { // Loop-Variable that represents the current match, indexing the 'posArr' Array int pos = pos = posArr[i]; // Overlapping matches need to be skipped if ((i > 0) && (pos < (posArr[i-1] + MATCH_STR_LEN))) continue; else PRMLB.accept( prevSaverRecord = new PrintingRecMultiLine( fileAsStr, pos, // match Starting-Position pos + MATCH_STR_LEN, // match Ending-{osition replaceStr, // 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; // Remember, this single line of code does the actual replacement! String newFileAsStr = fileAsStr.replace(matchStr, replaceStr); try { FileRW.writeFile(newFileAsStr, 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()); } } |