001package Torello.Java;
002
003import Torello.HTML.NodeSearch.TCCompareStrException;
004import Torello.HTML.NodeSearch.TextComparitor;
005
006import java.util.*;
007import java.util.regex.*;
008import java.io.IOException;
009
010import java.util.function.Predicate;
011import java.util.stream.Stream;
012import java.io.Serializable;
013
014/**
015 * A simple functional-interface that provides many 'builder' methods for creating standard
016 * Java <CODE>Predicate's</CODE> that operate on an Object's <CODE>'toString'</CODE> method.
017 * 
018 * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER>
019 */
020@FunctionalInterface
021public interface StrFilter extends Serializable, Predicate<Object>
022{
023    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDFI>  */
024    public static final long serialVersionUID = 1;
025
026
027    // ********************************************************************************************
028    // ********************************************************************************************
029    // Functional-Interface Method
030    // ********************************************************************************************
031    // ********************************************************************************************
032
033
034    /** 
035     * <EMBED CLASS='external-html' DATA-FILE-ID=FUNC_INTER_METH>
036     *
037     * <BR /><BR />This method will receive a {@code java.lang.Object}.  This {@code Object} must
038     * have implemented its {@code toString()} method  The purpose of this method is to provide an
039     * easy means to filter certain {@code String's} (automatically).
040     *
041     * <BR /><BR /><B CLASS=JDDescLabel>Standard Filter Behavior:</B>
042     * 
043     * <BR />This method should return {@code FALSE} if the passed {@code String} <I><B>should be
044     * skipped</B></I>.  A return value of {@code TRUE} implies that the {@code String} is not to
045     * be ignored or passed over, but rather 'kept.'
046     *
047     * <BR /><BR />This behavior is consisten with the Java Stream's method
048     * {@code Stream.filter(Predicate)}.
049     *
050     * @param o This is the {@code Object} that will be tested.  If it passes the test, then this
051     * method must return {@code TRUE} which would imply that the object shall not be filtered.
052     * If the object fails the test, this method should return {@code FALSE}, and the object shall
053     * not be included in the result set as it has failed the test.
054     *
055     * @return When implementing this method, returning {@code TRUE} must mean that the
056     * {@code Object} has passed the filter's test-requirements (and will subsequently be retained
057     * by whatever function is carrying out the filter operation).
058     */
059    public boolean test(Object o);
060
061    /**
062     * Java is not perfect.  This method is fine: since {@code String} inherits {@code Object},
063     * and the {@code interface Predicate} does not have any write-dependencies.
064     * 
065     * <BR /><BR />Erasure is like this with {@code interface Predicate} - because that's how
066     * it works with all of the generics.  A {@code Vector<Object>} cannot be passed to a
067     * {@code Vector<String>} parameter (because a person might need to use the {@code String's}).
068     * But a {@code Predicate<Object>} <B><I>can not</I></B> have problems being passed to a 
069     * {@code Predicate<String>}...
070     *
071     * @return {@code 'this' StrFilter} cast into a {@code Predicate<String>}
072     */
073    @SuppressWarnings({"rawtypes", "unchecked"})
074    public default Predicate<String> castToStrPred()
075    {
076        Predicate p = this;
077        return (Predicate<String>) p;
078    }
079
080
081    // ********************************************************************************************
082    // ********************************************************************************************
083    // strList: KEEP
084    // ********************************************************************************************
085    // ********************************************************************************************
086
087
088    /**
089     * Convenience Method.
090     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
091     */
092    public static StrFilter strListKEEP(Iterable<String> strList, boolean ignoreCase)
093    { return strListKEEP(strList.iterator(), ignoreCase); }
094
095    /**
096     * Convenience Method.
097     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
098     * <BR />Converts: {@code String[]} VarArgs to {@code Iterator<String>}
099     */
100    public static StrFilter strListKEEP(boolean ignoreCase, String... strList)
101    { return strListKEEP(Arrays.asList(strList).iterator(), ignoreCase); }
102
103    /**
104     * Convenience Method.
105     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
106     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
107     */
108    public static StrFilter strListKEEP(String strListFileName, boolean ignoreCase)
109        throws IOException
110    { return strListKEEP(FileRW.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }
111
112    /**
113     * Convenience Method.
114     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
115     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
116     * <BR />Catches Exception, and Fails Fast - (See {@link LFEC})
117     */
118    public static StrFilter strListKEEP_NOIOE(String strListFileName, boolean ignoreCase)
119    { return strListKEEP(LFEC.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }
120
121    /**
122     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
123     *
124     * <BR /><BR />Here, an input-object's {@code toString()} result is compared against the
125     * {@code String's} contained in {@code 'strList'} - which would also be called an
126     * input-{@code String} 'white-list.'
127     *
128     * @param strList A list of {@code String's} to be used as a 'comparison set' against an
129     * input-{@code String} in a {@code Predicate}-test.
130     *
131     * @param ignoreCase When this is {@code TRUE} all equality-comparisons performed in the
132     * {@code String's} tested will ignore case-sensitivity.
133     *
134     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
135     * according to each of the {@code String's} in the input-parameter {@code 'strList'}
136     */
137    public static StrFilter strListKEEP(Iterator<String> strList, boolean ignoreCase)
138    {
139        // Build a TreeSet<String>, this will be used, internally, in the instance of 'StrFilter'
140        // (a Predicate<Object>) that is returned by this factory-builder method.
141        // 
142        // Experienced Java users: TreeSet is a very convenient and easy-to-use "sorted list"
143        // implementation that stores strings, in sorted order, in a binary-tree.  This makes
144        // looking them up as efficient as possible.
145
146        final TreeSet<String> ts = new TreeSet<>();
147
148        // When building this sorted-list (TreeSet), if we are ignoring case (and-only-if), then
149        // we must convert the strings contained by the input parameter string-list 'strList' to
150        // lower-case first.
151
152        if (ignoreCase) while (strList.hasNext()) ts.add(strList.next().toLowerCase());
153        else            while (strList.hasNext()) ts.add(strList.next());
154
155        // The returned Predicate<Object> must call '.toString()' on it's object-input before
156        // checking the "Sorted-List" (TreeSet<String>) to see if it contains the string.  If a
157        // case-insensitive predicate was requested, then we must build a Predicate that invokes
158        // BOTH 'toString()' AND THEN invokes 'toLowerCase()' on the input Object.
159
160        if (ignoreCase) return (Object o) -> ts.contains(o.toString().toLowerCase());
161        else            return (Object o) -> ts.contains(o.toString());
162    }
163
164
165    // ********************************************************************************************
166    // ********************************************************************************************
167    // strList REJECT
168    // ********************************************************************************************
169    // ********************************************************************************************
170
171
172    /**
173     * Convenience Method.
174     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
175     */
176    public static StrFilter strListREJECT(Iterable<String> strList, boolean ignoreCase)
177    { return strListREJECT(strList.iterator(), ignoreCase); }
178
179    /**
180     * Convenience Method.
181     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
182     * <BR />Converts: {@code String[]} VarArgs to {@code Iterator<String>}
183     */
184    public static StrFilter strListREJECT(boolean ignoreCase, String... strList)
185    { return strListREJECT(Arrays.asList(strList).iterator(), ignoreCase); }
186
187    /**
188     * Convenience Method.
189     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
190     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
191     */
192    public static StrFilter strListREJECT(String strListFileName, boolean ignoreCase) throws IOException
193    { return strListREJECT(FileRW.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }
194
195    /**
196     * Convenience Method.
197     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
198     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
199     * <BR />Catches Exception, and Fails Fast - (See {@link LFEC})
200     */
201    public static StrFilter strListREJECT_NOIOE(String strListFileName, boolean ignoreCase)
202    { return strListREJECT(LFEC.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }
203
204    /**
205     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
206     *
207     * <BR /><BR />Here, an input-object's {@code toString()} result is compared against the
208     * {@code String's} contained in {@code 'strList'} - which would also be called an
209     * input-{@code String} 'black-list.'
210     *
211     * @param strList A list of {@code String's} to be used as a 'comparison set' against an
212     * input-{@code String} in a {@code Predicate}-test.
213     *
214     * @param ignoreCase When this is {@code TRUE} all equality-comparisons performed in the
215     * {@code String's} tested will ignore case-sensitivity.
216     *
217     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
218     * according to each of the {@code String's} in the input-parameter {@code 'strList'}.
219     */
220    public static StrFilter strListREJECT(Iterator<String> strList, boolean ignoreCase)
221    {
222        // Build a TreeSet<String>, this will be used, internally, in the instance of 'StrFilter'
223        // (a Predicate<Object>) that is returned by this factory-builder method.
224        // 
225        // Experienced Java users: TreeSet is a very convenient and easy-to-use "sorted list"
226        // implementation that stores strings, in sorted order, in a binary-tree.  This makes
227        // looking them up as efficient as possible.
228
229        final TreeSet<String> ts = new TreeSet<>();
230
231        // When building this sorted-list (TreeSet), if we are ignoring case (and-only-if), then
232        // we must convert the strings contained by the input parameter string-list 'strList' to
233        // lower-case first.
234
235        if (ignoreCase) while (strList.hasNext()) ts.add(strList.next().toLowerCase());
236        else            while (strList.hasNext()) ts.add(strList.next());
237
238        // The returned Predicate<Object> must call '.toString()' on it's object-input before
239        // checking the "Sorted-List" (TreeSet<String>) to see if it contains the string.  If a
240        // case-insensitive predicate was requested, then we must build a Predicate that invokes
241        // BOTH 'toString()' AND THEN invokes 'toLowerCase()' on the input Object.
242
243        if (ignoreCase) return (Object o) -> ! ts.contains(o.toString().toLowerCase());
244        else            return (Object o) -> ! ts.contains(o.toString());
245    }
246
247
248    // ********************************************************************************************
249    // ********************************************************************************************
250    // strList Regular-Expressions: Filter Factory / Filter-Generator  static-methods
251    // ********************************************************************************************
252    // ********************************************************************************************
253
254
255    /**
256     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
257     *
258     * <BR /><BR />Here, an input-object's {@code toString()} result is checked against Java's
259     * Regular-Expression matcher.  The {@code Predicate} generated will choose to <B>KEEP</B>
260     * input-{@code Object's} by returning {@code TRUE} when the Regular-Expression matches the
261     * {@code Object.toString()} results.
262     *
263     * @param regEx A regular-expression used to compare against an input-{@code Object} (using
264     * {@code Object.toString()} in a {@code Predicate}-test.
265     *
266     * @param regExMustMatchEntireString When this parameter is passed {@code TRUE}, then the
267     * regular-expression {@code Pattern} must match the <B>entire input {@code Object.toString()}
268     * </B>.   When this parameter is passed {@code FALSE}, it only need match some portion or
269     * part of the input-parameter {@code String} in the {@code Predicate-test} that is being
270     * generated.
271     *
272     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
273     * according to the input-parameter {@code 'regEx'}
274     */
275    public static StrFilter regExKEEP(Pattern regEx, boolean regExMustMatchEntireString)
276    {
277        // FAIL-FAST: Perform this test BEFORE building the regex, to save the programmer
278        // from later on (when using this factory generated predicate) having a
279        // NullPointerException that is more difficult to isolate.
280
281        if (regEx == null) throw new NullPointerException(
282            "The Regular-Expression provided to the 'regEx' parameter of this " +
283            "static-factory-builder method, 'regExKEEP,' was null."
284        );
285
286        if (regExMustMatchEntireString)
287        {
288            // the java.util.regex.Pattern.asPredicate() method produces a predicate that must
289            // match an entire input string.  This would be identical (if it were possible) to
290            // insert a '^' at the beginning of the regex, and an '$' at the end.
291    
292            final Predicate<String> p = regEx.asPredicate();
293
294            return (Object o) -> p.test(o.toString());
295        }
296
297        else
298            return (Object o) -> regEx.matcher(o.toString()).find();
299    }
300
301    /**
302     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
303     *
304     * <BR /><BR />Here, an input-object's {@code toString()} result is checked against Java's
305     * Regular-Expression matcher.  The {@code Predicate} generated will choose to <B>REJECT</B>
306     * input-{@code Object's} by returning {@code FALSE} when the Regular-Expression matches the
307     * {@code Object.toString()} results.
308     *
309     * @param regEx A regular-expression used to compare against an input-{@code Object} (using
310     * {@code Object.toString()} in a {@code Predicate}-test.
311     *
312     * @param regExMustMatchEntireString When this parameter is passed {@code TRUE}, then the
313     * regular-expression {@code Pattern} must match the <B>entire input {@code Object.toString()}
314     * </B>.   When this parameter is passed {@code FALSE}, it only need match some portion or
315     * part of the input-parameter {@code String} in the {@code Predicate-test} that is being
316     * generated.
317     *
318     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
319     * according to the input-parameter {@code 'regEx'}
320     */
321    public static StrFilter regExREJECT(Pattern regEx, boolean regExMustMatchEntireString)
322    {
323        // FAIL-FAST: Perform this test BEFORE building the regex, to save the programmer
324        // from later on (when using this factory generated predicate) having a
325        // NullPointerException that is more difficult to isolate.
326
327        if (regEx == null) throw new NullPointerException(
328            "The Regular-Expression provided to the 'regEx' parameter of this " +
329            "static-factory-builder method, 'regExREJECT,' was null."
330        );
331
332        if (regExMustMatchEntireString)
333        {
334            // the java.util.regex.Pattern.asPredicate() method produces a predicate that must
335            // match an entire input string.  This would be identical (if it were possible) to
336            // insert a '^' at the beginning of the regex, and an '$' at the end.
337
338            final Predicate<String> p = regEx.asPredicate().negate();
339            return (Object o) -> ! p.test(o.toString());
340        }
341        else
342            return (Object o) -> ! regEx.matcher(o.toString()).find();
343    }
344
345
346    // ********************************************************************************************
347    // ********************************************************************************************
348    // strList Regular-Expressions: Filter Factory / Filter-Generator  static-methods
349    // ********************************************************************************************
350    // ********************************************************************************************
351
352
353    /**
354     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
355     *
356     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
357     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
358     * matches <I>each-and-every-one</I> of the provided regular-expressions.
359     *
360     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
361     *
362     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
363     * according to the input-parameter {@code 'regExs'}
364     */
365    public static StrFilter regExsAND(Pattern... regExs)
366    {
367        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
368        // the lambda-predicate is invoked.
369
370        for (Pattern regEx : regExs)
371
372            if (regEx == null) throw new NullPointerException(
373                "One or more of the elements passed to static-factory method 'regexsAND' are " +
374                "null."
375            );
376
377        // This over-comes a minor "possible complication" - without likely causing much
378        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
379        // changed after building this Predicate, the Predicate's behavior would fail.
380        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
381        // predicate that stores a different  array pointer (because it is to a different array)
382        // inside the Predicate's body.
383
384        final Pattern[] pArr = regExs.clone();
385
386        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
387        // the lambda-predicate is invoked.
388
389        if (pArr.length == 0) throw new IllegalArgumentException(
390            "This static-factory method 'regExsAND' has been invoked with zero-arguments to the " +
391            "'regExs' var-args parameter."
392        );
393
394        return (Object o) ->
395        {
396            String s=o.toString();
397
398            // Cycle the Regular-Expressions.  "AND" means if even one fails, return FALSE
399            // immediately.
400
401            for (Pattern p : pArr) if (! p.matcher(s).find()) return false;
402
403            // None of them failed, return TRUE.
404            return true;
405        };
406    }
407
408    /**
409     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
410     *
411     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
412     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
413     * matches <I>any-one-of</I> the provided regular-expressions.
414     * 
415     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
416     *
417     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
418     * according to the input-parameter {@code 'regExs'}
419     */
420    public static StrFilter regExsOR(Pattern... regExs)
421    {
422        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once the
423        // lambda-predicate is invoked.
424
425        for (Pattern regEx : regExs)
426
427            if (regEx == null) throw new NullPointerException(
428                "One or more of the elements passed to static-factory method 'regExsOR' are " +
429                "null."
430            );
431
432        // This over-comes a minor "possible complication" - without likely causing much
433        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
434        // changed after building this Predicate, the Predicate's behavior would fail.
435        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
436        // predicate that stores a different  array pointer (because it is to a different array)
437        // inside the Predicate's body.
438
439        final Pattern[] pArr = regExs.clone();
440
441        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
442        // the lambda-predicate is invoked.
443
444        if (pArr.length == 0) throw new IllegalArgumentException(
445            "This static-factory method 'regExsOR' has been invoked with zero-arguments to the" +
446            "'regExs' var-args parameter."
447        );
448
449        return (Object o) ->
450        {
451            String s = o.toString();
452
453            // Cycle the Regular-Expressions.  "OR" means if even one succeeds, return TRUE
454            // immediately.
455
456            for (Pattern p : pArr) if (p.matcher(s).find()) return true;
457
458            // None succeeded, so return FALSE.
459            return false;
460        };
461    }
462
463    /**
464     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
465     *
466     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
467     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
468     * <I>does-not-match-any</I> of the provided regular-expressions.
469     *
470     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
471     *
472     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
473     * according to the input-parameter {@code 'regExs'}.
474     */
475    public static StrFilter regExsNAND(Pattern... regExs)
476    {
477        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once the
478        // lambda-predicate is invoked.
479
480        for (Pattern regEx : regExs)
481
482            if (regEx == null) throw new NullPointerException(
483                "One or more of the elements passed to static-factory method 'regExsNAND' are " +
484                "null."
485            );
486
487        // This over-comes a minor "possible complication" - without likely causing much
488        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
489        // changed after building this Predicate, the Predicate's behavior would fail.
490        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
491        // predicate that stores a different  array pointer (because it is to a different array)
492        // inside the Predicate's body.
493
494        final Pattern[] pArr = regExs.clone();
495
496        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
497        // the lambda-predicate is invoked.
498
499        if (pArr.length == 0) throw new IllegalArgumentException(
500            "This static-factory method 'regExsNAND' has been invoked with zero-arguments to " +
501            "the 'regExs' var-args parameter."
502        );
503
504        return (Object o) ->
505        {
506            String s = o.toString();
507
508            // Cycle the Regular-Expressions.  "NAND" means if even one succeeds, return FALSE
509            // immediately.
510
511            for (Pattern p : pArr) if (p.matcher(s).find()) return false;
512
513            // All regex's failed, so return TRUE
514            return true;
515        };
516    }
517
518    /**
519     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
520     *
521     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
522     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
523     * matches <I>precisely-one-of</I> the provided regular-expressions.
524     *
525     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
526     *
527     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
528     * according to the input-parameter {@code 'regExs'}
529     */
530    public static StrFilter regExsXOR(Pattern... regExs)
531    {
532        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once the
533        // lambda-predicate is invoked.
534
535        for (Pattern regEx : regExs)
536
537            if (regEx == null) throw new NullPointerException(
538                "One or more of the elements passed to static-factory method 'regExsXOR' are " +
539                "null."
540            );
541
542        // This over-comes a minor "possible complication" - without likely causing much
543        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
544        // changed after building this Predicate, the Predicate's behavior would fail.
545        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
546        // predicate that stores a different  array pointer (because it is to a different array)
547        // inside the Predicate's body.
548
549        final Pattern[] pArr = regExs.clone();
550
551        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
552        // the lambda-predicate is invoked.
553
554        if (pArr.length == 0) throw new IllegalArgumentException(
555            "This static-factory method 'regExsXOR' has been invoked with zero-arguments to the " +
556            "'regExs' var-args parameter."
557        );
558
559        return (Object o) ->
560        {
561            String  s       = o.toString();
562            int     count   = 0;
563
564            // Cycle the Regular-Expressions.  Because this is "XOR" - we must keep a count.
565            // If that count is > 1, we have to return FALSE immediately.
566
567            for (Pattern p : pArr) if (p.matcher(s).find()) { if (++count > 1) return false; }
568
569            // If there was precisely one match, return TRUE, otherwise return FALSE.
570            return count == 1;
571        };
572    }
573
574
575    // ********************************************************************************************
576    // ********************************************************************************************
577    // Other static-factory methods
578    // ********************************************************************************************
579    // ********************************************************************************************
580
581
582    /**
583     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
584     * 
585     * Here, the class {@code TextComparitor} is reused from the {@code 'NodeSearch'} package.
586     * Please review how each of the pre-defined, {@code static}-instances of class
587     * {@code TextComparitor} operate when presented with {@code String}-input.
588     *
589     * <DIV CLASS="EXAMPLE">{@code
590     * StrFilter filter = StrFilter.comparitor
591     *     (TextComparitor.DOES_NOT_START_WITH, "Today in Washington,");
592     * 
593     * // The above fiter would REJECT any input Object whose 'toString()' method returned a 
594     * // String that began with the words "Today in Washington,"
595     * 
596     * StrFilter filter2 = StrFilter.comparitor
597     *     (TextComparitor.CONTAINS_CASE_INSENSITIVE, "Highway 61 Revisited");
598     * 
599     * // This filter would KEEP / RETAIN any input Object whose 'toString()' method returned a
600     * // String that contained the words "Highway 61 Revisited".  This comparison to be performed
601     * // would be case-insensitive.
602     * }</DIV>
603     *
604     * @param tc This is an instance of the {@code class TextComparitor}, which is defined in the
605     * NodeSearch package.
606     *
607     * @param compareStrs These must be the comparison-{@code String's} used by class
608     * {@code 'TextComparitor'} for performing the comparisons.
609     *
610     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
611     * according to the input-parameters {@code 'tc'} and {@code 'compareStrs'}
612     *
613     * @see TextComparitor
614     */
615    public static StrFilter comparitor(TextComparitor tc, java.lang.String... compareStrs)
616    {
617        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
618        // the lambda-predicate is invoked.
619
620        if (tc == null) throw new NullPointerException
621            ("A null TextComparitor has been passed to static-factory method 'comparitorKeep'");
622
623        TCCompareStrException.check(compareStrs);
624
625        // Mostly, this is over-board, but it doesn't slow anything down much, and it does force
626        // users to think about Object-References and Parameter-Argument Marshaling.
627
628        final String[] cmpStrs = compareStrs.clone();
629
630        // Builds (and returns) a Predicate<Object> that re-uses the TextComparitor's
631        // 'test(String, String...)' method.
632
633        return (Object o) -> tc.test(o.toString(), cmpStrs);
634    }
635
636    /**
637     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
638     * 
639     * Here, a single {@code String} is used as input.  The {@code Predicate} that is created by
640     * this factory-method  will have a {@code test()} method that returns {@code TRUE} only if the
641     * an input object's {@code 'toString()'} output <I>equals</I> the the input-parameter
642     * {@code String 's'}.
643     *
644     * @param s This is a {@code String} which will be used to test for equality in the generated
645     * {@code Predicate.test(Object)}
646     *
647     * @param ignoreCase When this is {@code TRUE}, the equality-test will ignore case when
648     * performing its comparisons.
649     *
650     * @return A new {@code StrFilter} that can be used to test and filter {@code Object's} via the
651     * {@code Object.toString()} method, and the input-parameter {@code 's'}
652     */
653    public static StrFilter isEqualKEEP(String s, boolean ignoreCase)
654    {
655        if (s == null) throw new NullPointerException(
656            "A null String has been passed to parameter 's' of static-factory method " +
657            "'isEqualKEEP'."
658        );
659
660        // Builds & returns a predicate based on the whether or not 'ignoreCase' is true or false.
661        return ignoreCase
662            ? (Object o) -> o.toString().equalsIgnoreCase(s)
663            : (Object o) -> o.toString().equals(s);
664    }
665
666    /**
667     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
668     * 
669     * Here, a single {@code String} is used as input.  The {@code Predicate} that is created by
670     * this factory-method will have a {@code test()} method that returns {@code TRUE} only if an
671     * input {@code Object's} {@code Object.toString()} output <I>does-not-equal</I> the the
672     * input-parameter {@code String 's'}.
673     *
674     * @param s This is a {@code String} which will be used to test for equality in the generated
675     * {@code Predicate.test(Object)}.
676     * 
677     * @param ignoreCase When this is {@code TRUE}, the equality-test will ignore case when
678     * performing its comparisons.
679     *
680     * @return A new {@code StrFilter} that can be used to test and filter {@code Object's} via the
681     * {@code Object.toString()} method, and the input-parameter {@code 's'}
682     */
683    public static StrFilter isEqualREJECT(String s, boolean ignoreCase)
684    {
685        if (s == null) throw new NullPointerException(
686            "A null String has been passed to parameter 's' of static-factory method " +
687            "'isEqualREJECT'."
688        );
689
690        // Builds & returns a predicate based on the whether or not 'ignoreCase' is true or false.
691        return ignoreCase
692            ? (Object o) -> ! o.toString().equalsIgnoreCase(s)
693            : (Object o) -> ! o.toString().equals(s);
694    }
695}