001package Torello.Java; 002 003import static Torello.Java.C.*; 004 005import java.io.IOException; 006import java.io.FilenameFilter; 007import java.io.File; 008 009import java.util.List; 010import java.util.Objects; 011import java.util.regex.Pattern; 012import java.util.regex.MatchResult; 013import java.util.function.Function; 014 015import Torello.JavaDoc.Entity; 016import Torello.JavaDoc.IntoHTMLTable; 017import Torello.JavaDoc.LinkJavaSource; 018 019import static Torello.JavaDoc.IntoHTMLTable.Background.GreenDither; 020import static Torello.JavaDoc.IntoHTMLTable.Background.BlueDither; 021 022/** 023 * Based on the UNIX {@code 'SED'} Command, this class updates files based on 024 * <B STYLE='color: red;'><I>either</I></B> {@code String}-Literal matches & replacements, or 025 * <B STYLE='color: red;'><I>or</I></B> Regular-Expression matches. 026 * 027 * <EMBED CLASS='external-html' DATA-FILE-ID=SED> 028 */ 029@Torello.JavaDoc.StaticFunctional 030public class SED 031{ 032 private SED() { } 033 034 035 // ******************************************************************************************** 036 // ******************************************************************************************** 037 // Some Filters 038 // ******************************************************************************************** 039 // ******************************************************************************************** 040 041 042 /** A {@code java.io.FilenameFilter} that looks for JavaDoc Upgrader HTML-Files. */ 043 public static final FilenameFilter filterFilesHTML = (File dir, String fileName) -> 044 { 045 if (! fileName.endsWith(".html")) return false; 046 047 if (! StrCmpr.containsAND(dir.getPath(), "upgrade-files", "external-html")) return false; 048 049 return true; 050 }; 051 052 /** A {@code java.io.FilenameFilter} that looks for {@code '.java'} Files. */ 053 public static final FilenameFilter filterFilesJava = (File dir, String fileName) -> 054 { 055 if (! fileName.endsWith(".java")) return false; 056 057 return true; 058 }; 059 060 061 // ******************************************************************************************** 062 // ******************************************************************************************** 063 // Single-Line String-Match 064 // ******************************************************************************************** 065 // ******************************************************************************************** 066 067 068 /** 069 * <BR>Passed Values to Configuration-Record: 070 * <BR>: 071 * <BR>askFirst: TRUE 072 * <BR>ioeh: null 073 * <BR>useUNIXColors: TRUE 074 * <BR>verbosity: Verbosity.Normal 075 * <BR>outputSaver: null 076 */ 077 @IntoHTMLTable( 078 background=GreenDither, 079 title="Find Single-Line String-Matches, and Substitue each match with a Replacement-String" 080 ) 081 @LinkJavaSource(handle="SingleLineStrMatch") 082 @LinkJavaSource(handle="PrintingRecSingleLine") 083 public static List<FileNode> singleLine 084 (Iterable<FileNode> files, String matchStr, String replaceStr) 085 throws IOException 086 { 087 CHECK_NULL(files, matchStr, replaceStr); 088 SingleLineStrMatch.CHECK_STRS(matchStr, replaceStr); 089 090 final CONFIG_RECORD userConfig = new CONFIG_RECORD( 091 matchStr, 092 replaceStr, 093 true, // askFirst 094 null, // ioeh 095 true, // useUNIXColors 096 Verbosity.Normal, // verbosity 097 null // outputSaver 098 ); 099 100 return InternalSED.run(files, userConfig, SingleLineStrMatch::handleOneFile); 101 } 102 103 /** 104 * Makes updates to Text-Files listed in parameter {@code 'files'}. 105 * 106 * <BR /><BR />In the example below, all {@code '.java'} Source-Code Files in the Java-HTML 107 * Library are loaded into a {@code Vector<FileNode>}. Using this particular {@code 'SED'} 108 * method, each substring that looks like <B STYLE='color: blue'>{@code <B>TRUE</B>}</B> (in 109 * all {@code '.java'} Source-Files) is converted to look like 110 * <B STYLE='color: blue'><CODE>{@code TRUE}</CODE></B> 111 * 112 * <DIV CLASS=EXAMPLE>{@code 113 * StringBuilder log = new StringBuilder(); 114 * 115 * // Load all Java-HTML Source-Files (in all packages) into a FileNode-Vector 116 * Vector<FileNode> files = FileNode 117 * .createRoot("Torello/") 118 * .loadTree(-1, SED.filterFilesJava, null) 119 * .flattenJustFiles(RTC.VECTOR()); 120 * 121 * // Invoke this method 122 * singleLine(files, "<B>TRUE</B>", "{@code TRUE}", true, null, true, Verbosity.Normal, log); 123 * 124 * // Save the Changes that were made to a log 125 * FileRW.writeFile(log, "ChangesMade.log.txt"); 126 * }</DIV> 127 * 128 * @param matchStr Any Java {@code String}-Literal. It is required that this {@code String} 129 * not contain any new-line ({@code '\n'}) characters, or an {@code IllegalArgumentException} 130 * will throw. 131 * 132 * @param replaceStr For all {@code 'files'}, each instance of the {@code String}-Literal 133 * parameter {@code 'matchStr'} found in any file will be replaced by {@code 'replaceStr'}. 134 * 135 * <BR /><BR />As with parameter {@code 'matchStr'}, if this {@code String} contains any 136 * new-line ({@code '\n'}) characters, an {@code IllegalArgumentException} will throw. 137 * 138 * @return A list of {@link FileNode} instances whose File-Contents were modified / updated 139 * by this method. 140 * 141 * @throws IllegalArgumentException If either of the {@code String} parameters 142 * {@code 'matchStr'} or {@code 'replaceStr'} contain any newline ({@code '\n'}) characters. 143 * 144 * <BR /><BR />If a {@code 'SED'} replacement needs to be done using a {@code String}-Match 145 * that spans more than one line, use the Multi-Line method provided in this class. 146 * 147 * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=SED_APPEND_ERROR> 148 * @throws IOException On File-System read / write error. 149 */ 150 @LinkJavaSource(handle="SingleLineStrMatch") 151 @LinkJavaSource(handle="PrintingRecSingleLine") 152 public static List<FileNode> singleLine( 153 Iterable<FileNode> files, 154 String matchStr, 155 String replaceStr, 156 boolean askFirst, 157 IOExceptionHandler ioeh, 158 boolean useUNIXColors, 159 Verbosity verbosity, 160 Appendable outputSaver 161 ) 162 throws IOException 163 { 164 CHECK_NULL(files, matchStr, replaceStr); 165 SingleLineStrMatch.CHECK_STRS(matchStr, replaceStr); 166 167 final CONFIG_RECORD userConfig = new CONFIG_RECORD 168 (matchStr, replaceStr, askFirst, ioeh, useUNIXColors, verbosity, outputSaver); 169 170 return InternalSED.run(files, userConfig, SingleLineStrMatch::handleOneFile); 171 } 172 173 174 // ******************************************************************************************** 175 // ******************************************************************************************** 176 // Single-Line Regular-Expression-Match 177 // ******************************************************************************************** 178 // ******************************************************************************************** 179 180 181 /** 182 * <BR>Passed Values to Configuration-Record: 183 * <BR>: 184 * <BR>askFirst: TRUE 185 * <BR>ioeh: null 186 * <BR>useUNIXColors: TRUE 187 * <BR>verbosity: Verbosity.Normal 188 * <BR>outputSaver: null 189 */ 190 @IntoHTMLTable( 191 background=BlueDither, 192 title="Find Reg-Ex Matches on a Line-By-Line Basis, and Apply a Replacement-Function" 193 ) 194 @LinkJavaSource(handle="SingleLineRegExMatch_ONE_FILE") 195 @LinkJavaSource(handle="SingleLineRegExMatch_ONE_LINE") 196 @LinkJavaSource(handle="PrintingRecSingleLine") 197 public static List<FileNode> singleLine 198 (Iterable<FileNode> files, Pattern regEx, Function<MatchResult, String> replaceFunction) 199 throws IOException 200 { 201 CHECK_NULL(files, regEx, replaceFunction); 202 203 final CONFIG_RECORD userConfig = new CONFIG_RECORD( 204 regEx, 205 replaceFunction, 206 true, // askFirst 207 null, // ioeh 208 true, // useUNIXColors 209 Verbosity.Normal, // verbosity 210 null // outputSaver 211 ); 212 213 return InternalSED.run(files, userConfig, SingleLineRegExMatch_ONE_FILE::handleOneFile); 214 } 215 216 /** 217 * Makes updates to Text-Files listed in parameter {@code 'files'}. Matches that are eligible 218 * for replacement are identified using the Regular-Expression parameter {@code 'regEx'}. 219 * 220 * <BR /><BR />Replacement-{@code String's} are obtained by invoking the 221 * {@code 'replaceFunction'} parameter. 222 * 223 * <BR /><BR />This method will, first, break up the input text into separate lines of textual 224 * data, demarcated by the {@code '\n'} new-line character, and then apply the provided 225 * {@code 'regEx'} parameter to each of these lines of text. In essence, the standard Java 226 * {@code 'String.split("\n")'} is first invoked, and aftewards a loop iterates each line of 227 * text against the supplied Regular-Expression. 228 * 229 * <BR /><BR /><DIV CLASS=JDHint> 230 * <B STYLE='color:red;'>Note:</B> The caret ({@code `^`}) and dollar-sign ({@code `$`}) 231 * characters - <B STYLE='color:red;'><I>also known as line anchors</I></B> - do not require 232 * the use of the {@code Pattern.MULTILINE} flag because, as was just mentioned, each line of 233 * text is split, manually (with {@code String.split("\n")}), before matching begins. 234 * 235 * <BR /><BR />When applied to individual lines of {@code String}-data, {@code '^'} matches the 236 * start of the line, and {@code '$'} matches just before the newline, even without the 237 * {@code MULTILINE} flag. 238 * </DIV> 239 * 240 * <BR /><BR />In the example below, all Java HTML Library Source-Files are loaded into a 241 * {@code Vector<FileNode>}, and then iterated by this method. In each file, any instances of 242 * an {@code EMBED}-Tag that contains a {@code FILE-ID} Data-Attribute whose value is wrapped 243 * in Double Quotation-Marks has that value extracted and re-inserted without any 244 * Quotation-Marks. 245 * 246 * <DIV CLASS=EXAMPLE>{@code 247 * StringBuilder log = new StringBuilder(); 248 * 249 * // Load all Java-HTML Source-Files (in all packages) into a FileNode-Vector 250 * Vector<FileNode> files = FileNode 251 * .createRoot("Torello/") 252 * .loadTree(-1, filterFilesJava, null) 253 * .flattenJustFiles(RTC.VECTOR()); 254 * 255 * // Matches an <EMBED> Tag's FILE-ID Data-Attribute. There are parenthesis placed around the 256 * // actual ID-Name. The contents of the parenthesis are "Reg-Ex Match-Group #1" 257 * 258 * Pattern regEx = Pattern.compile("DATA-FILE-ID=\"(\\w+)\""); 259 * 260 * // This Function extracts Reg-Ex Group-1, and re-inserts it **WITHOUT** the Double 261 * // Quotation-Marks that were placed around it. Look Closely at Previous Line of Code, the 262 * // 'replacer' function does not include the Double-Quotes. 263 * 264 * Function<MatchResult, String> replacer = (MatchResult mr) -> "DATA-FILE-ID=" + mr.group(1); 265 * 266 * // Invoke this method 267 * singleLine(files, regEx, replacer, true, null, true, Verbosity.Normal, log); 268 * 269 * // Write the changes that have been made to a log-file 270 * FileRW.writeFile(log, "ChangesMade.log.txt"); 271 * }</DIV> 272 * 273 * @param regEx Any Java Regular-Expression. This will be used to match against the text 274 * inside each file returned by the {@code Iterable} parameter {@code 'files'}. 275 * 276 * @param replaceFunction Any Lambda-Expression or Function-Pointer that accepts a 277 * {@code java.util.regex.MatchResult}, and returns a Replacement-{@code String}. 278 * 279 * <BR /><BR /><DIV CLASS=JDHint> 280 * This function may return 'null' - and if or when it does, whatever pending Match-Text is 281 * being processed shall be ignored, and left unchanged. The original Match-Text shall remain 282 * in the output file for any match in which {@code 'replaceFunction'} returns null. 283 * </DIV> 284 * 285 * <BR /><DIV CLASS=JDHint2> 286 * If this Replace-Function returns a match that contains any newline {@code '\n'} 287 * characters, this method will throw a {@link RegExException}. 288 * </DIV> 289 * 290 * @return A list of {@link FileNode} instances whose File-Contents were modified / updated 291 * by this method. 292 * 293 * @throws RegExExpression Throws if the Lambda-Target / Function-Pointer parameter 294 * {@code 'replaceFunction'} ever returns a {@code String} that spans multiple lines. 295 * 296 * <BR /><BR />The logic simply checks for the presence of a newline {@code '\n'} character. 297 * 298 * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=SED_APPEND_ERROR> 299 * @throws IOException On File-System read / write error. 300 */ 301 @LinkJavaSource(handle="SingleLineRegExMatch_ONE_FILE") 302 @LinkJavaSource(handle="SingleLineRegExMatch_ONE_LINE") 303 @LinkJavaSource(handle="PrintingRecSingleLine") 304 public static List<FileNode> singleLine( 305 Iterable<FileNode> files, 306 Pattern regEx, 307 Function<MatchResult, String> replaceFunction, 308 boolean askFirst, 309 IOExceptionHandler ioeh, 310 boolean useUNIXColors, 311 Verbosity verbosity, 312 Appendable outputSaver 313 ) 314 throws IOException 315 { 316 CHECK_NULL(files, regEx, replaceFunction); 317 318 final CONFIG_RECORD userConfig = new CONFIG_RECORD 319 (regEx, replaceFunction, askFirst, ioeh, useUNIXColors, verbosity, outputSaver); 320 321 return InternalSED.run(files, userConfig, SingleLineRegExMatch_ONE_FILE::handleOneFile); 322 } 323 324 325 // ******************************************************************************************** 326 // ******************************************************************************************** 327 // Multi-Line String-Match 328 // ******************************************************************************************** 329 // ******************************************************************************************** 330 331 332 /** 333 * <BR>Passed Values to Configuration-Record: 334 * <BR>: 335 * <BR>askFirst: TRUE 336 * <BR>ioeh: null 337 * <BR>useUNIXColors: TRUE 338 * <BR>verbosity: Verbosity.Normal 339 * <BR>outputSaver: null 340 */ 341 @IntoHTMLTable( 342 background=GreenDither, 343 title="Find Multi-Line String-Matches, and Substitue each match with a Replacement-String" 344 ) 345 @LinkJavaSource(handle="MultiLineStrMatch") 346 @LinkJavaSource(handle="PrintingRecMultiLine") 347 public static List<FileNode> multiLine 348 (Iterable<FileNode> files, String matchStr, String replaceStr) 349 throws IOException 350 { 351 CHECK_NULL(files, matchStr, replaceStr); 352 MultiLineStrMatch.CHECK_STRS(matchStr, replaceStr); 353 354 final CONFIG_RECORD userConfig = new CONFIG_RECORD( 355 matchStr, 356 replaceStr, 357 true, // askFirst 358 null, // ioeh 359 true, // useUNIXColors 360 Verbosity.Normal, // verbosity 361 null // outputSaver 362 ); 363 364 return InternalSED.run(files, userConfig, MultiLineStrMatch::handleOneFile); 365 } 366 367 368 /** 369 * Makes updates to Text-Files listed in parameter {@code 'files'}. 370 * 371 * <BR /><BR /><B CLASS=JDDescLabel>Single-Line & Multi-Line:</B> 372 * 373 * <BR />If either parameters {@code 'matchStr'} or {@code 'replaceStr'} receive a 374 * {@code String} that does not actually contain any new-line characters ({@code '\n'}), 375 * <I>this method will not throw any exceptions. All User-Requested Text-Updates will be 376 * processed to completion!</I> 377 * 378 * <BR /><BR />This somewhat in contrast to method {@code 'singleLine'} which will, indeed, 379 * throw an {@code IllegalArgumentException} if either {@code 'matchStr'} or 380 * {@code 'replaceStr'} contain newline characters. 381 * 382 * @param matchStr Any Java {@code String}-Literal. 383 * 384 * @param replaceStr For all {@code 'files'}, each instance of the {@code String}-Literal 385 * parameter {@code 'matchStr'} found in any file will be replaced by {@code 'replaceStr'}. 386 * 387 * @return A list of {@link FileNode} instances whose File-Contents were modified / updated 388 * by this method. 389 * 390 * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=SED_APPEND_ERROR> 391 * @throws IOException On File-System read / write error. 392 */ 393 @LinkJavaSource(handle="MultiLineStrMatch") 394 @LinkJavaSource(handle="PrintingRecMultiLine") 395 public static List<FileNode> multiLine( 396 Iterable<FileNode> files, 397 String matchStr, 398 String replaceStr, 399 boolean askFirst, 400 IOExceptionHandler ioeh, 401 boolean useUNIXColors, 402 Verbosity verbosity, 403 Appendable outputSaver 404 ) 405 throws IOException 406 { 407 CHECK_NULL(files, matchStr, replaceStr); 408 409 final CONFIG_RECORD userConfig = new CONFIG_RECORD 410 (matchStr, replaceStr, askFirst, ioeh, useUNIXColors, verbosity, outputSaver); 411 412 return InternalSED.run(files, userConfig, MultiLineStrMatch::handleOneFile); 413 } 414 415 416 // ******************************************************************************************** 417 // ******************************************************************************************** 418 // Multi-Line Regular-Expression Match 419 // ******************************************************************************************** 420 // ******************************************************************************************** 421 422 423 /** 424 * <BR>Passed Values to Configuration-Record: 425 * <BR>: 426 * <BR>askFirst: TRUE 427 * <BR>ioeh: null 428 * <BR>useUNIXColors: TRUE 429 * <BR>verbosity: Verbosity.Normal 430 * <BR>outputSaver: null 431 */ 432 @IntoHTMLTable( 433 background=BlueDither, 434 title="Find Multi-Line Reg-Ex-Matches, and Apply a Replacement-Function" 435 ) 436 @LinkJavaSource(handle="MultiLineRegExMatch") 437 @LinkJavaSource(handle="PrintingRecMultiLine") 438 public static List<FileNode> multiLine 439 (Iterable<FileNode> files, Pattern regEx, Function<MatchResult, String> replaceFunction) 440 throws IOException 441 { 442 CHECK_NULL(files, regEx, replaceFunction); 443 444 final CONFIG_RECORD userConfig = new CONFIG_RECORD( 445 regEx, 446 replaceFunction, 447 true, // askFirst 448 null, // ioeh 449 true, // useUNIXColors 450 Verbosity.Normal, // verbosity 451 null // outputSaver 452 ); 453 454 return InternalSED.run(files, userConfig, MultiLineRegExMatch::handleOneFile); 455 } 456 457 /** 458 * Makes updates to Text-Files listed in parameter {@code 'files'}. 459 * 460 * <BR /><BR /><B CLASS=JDDescLabel>Single-Line & Multi-Line:</B> 461 * 462 * <BR />When unsure whether to use the {@code 'singleLine'} versus the {@code 'multiLine'} 463 * variants class {@coce 'SED'}, given a {@code 'regEx'} that might match either one or many 464 * lines of text - <I>always use the Multi-Line Version (this method) to avoid exception 465 * throws!</I> 466 * 467 * <BR /><BR />The main differences between methods {@code 'singleLine'} and 468 * {@code 'multiLine'} lays with the formatting of the User-Output Text that is printed by this 469 * method as matches are found. 470 * 471 * <BR /><BR />This method can easily handle falling back to printing only one line of 472 * Matching-Text. However, {@code 'singleLine'} is simply incapable of printing a Multi-Line 473 * Regular-Expression Match, and will instead throw an exception if such a match is identified. 474 * 475 * @param regEx Any Java Regular-Expression. This will be used to match against the text 476 * inside each file returned by the {@code Iterable} parameter {@code 'files'}. The 477 * Regular-Expression passed may match any number of characters or lines in the Source-File. 478 * If just one line of the original file is matched by {@code 'regEx'}, this is perfectly fine 479 * and will not produce any exception throws. 480 * 481 * <BR /><BR />This method's User Output-Text Printing-Mechanism is written to handle 482 * {@code 'regEx'} Match-Text of any size. 483 * 484 * @param replaceFunction Any Lambda-Expression or Function-Pointer that accepts a 485 * {@code java.util.regex.MatchResult}, and returns a Replacement-{@code String}. 486 * 487 * <BR /><BR /><DIV CLASS=JDHint> 488 * This function may return 'null' - and if or when it does, whatever pending Match-Text is 489 * being processed shall be ignored, and left unchanged. The original Match-Text shall remain 490 * in the output file for any match in which {@code 'replaceFunction'} returns null. 491 * </DIV> 492 * 493 * @return A list of {@link FileNode} instances whose File-Contents were modified / updated 494 * by this method. 495 * 496 * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=SED_APPEND_ERROR> 497 * @throws IOException On File-System read / write error. 498 */ 499 @LinkJavaSource(handle="MultiLineRegExMatch") 500 @LinkJavaSource(handle="PrintingRecMultiLine") 501 public static List<FileNode> multiLine( 502 Iterable<FileNode> files, 503 Pattern regEx, 504 Function<MatchResult, String> replaceFunction, 505 boolean askFirst, 506 IOExceptionHandler ioeh, 507 boolean useUNIXColors, 508 Verbosity verbosity, 509 Appendable outputSaver 510 ) 511 throws IOException 512 { 513 CHECK_NULL(files, regEx, replaceFunction); 514 515 final CONFIG_RECORD userConfig = new CONFIG_RECORD 516 (regEx, replaceFunction, askFirst, ioeh, useUNIXColors, verbosity, outputSaver); 517 518 return InternalSED.run(files, userConfig, MultiLineRegExMatch::handleOneFile); 519 } 520 521 522 // ******************************************************************************************** 523 // ******************************************************************************************** 524 // ******************************************************************************************** 525 // ******************************************************************************************** 526 527 528 private static void CHECK_NULL( 529 final Iterable<FileNode> files, 530 final Pattern regEx, 531 final Function<MatchResult, String> replaceFunction 532 ) 533 { 534 Objects.requireNonNull(files, "You have passed 'null' to Parameter 'files'"); 535 Objects.requireNonNull(regEx, "You have passed 'null' to Parameter 'regEx'"); 536 537 Objects.requireNonNull 538 (replaceFunction, "You have passed 'null' to Parameter 'replaceFunction'"); 539 } 540 541 private static void CHECK_NULL( 542 final Iterable<FileNode> files, 543 final String matchStr, 544 final String replaceStr 545 ) 546 { 547 Objects.requireNonNull(files, "You have passed 'null' to Parameter 'files'"); 548 Objects.requireNonNull(matchStr, "You have passed 'null' to Parameter 'matchStr'"); 549 Objects.requireNonNull(replaceStr, "You have passed 'null' to Parameter 'replaceStr'"); 550 } 551}