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 &amp; 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>&lcub;&commat;code TRUE&rcub;</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 &amp; 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 &amp; 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}