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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
package Torello.Java;

import Torello.HTML.NodeSearch.TCCompareStrException;
import Torello.HTML.NodeSearch.TextComparitor;

import java.util.*;
import java.util.regex.*;
import java.io.IOException;

import java.util.function.Predicate;
import java.util.stream.Stream;
import java.io.Serializable;

/**
 * A simple functional-interface that provides many 'builder' methods for creating standard
 * Java <CODE>Predicate's</CODE> that operate on an Object's <CODE>'toString'</CODE> method.
 * 
 * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER>
 */
@FunctionalInterface
public interface StrFilter extends Serializable, Predicate<Object>
{
    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUIDFI>  */
    public static final long serialVersionUID = 1;


    // ********************************************************************************************
    // ********************************************************************************************
    // Functional-Interface Method
    // ********************************************************************************************
    // ********************************************************************************************


    /** 
     * <EMBED CLASS='external-html' DATA-FILE-ID=FUNC_INTER_METH>
     *
     * <BR /><BR />This method will receive a {@code java.lang.Object}.  This {@code Object} must
     * have implemented its {@code toString()} method  The purpose of this method is to provide an
     * easy means to filter certain {@code String's} (automatically).
     *
     * <BR /><BR /><B CLASS=JDDescLabel>Standard Filter Behavior:</B>
     * 
     * <BR />This method should return {@code FALSE} if the passed {@code String} <I><B>should be
     * skipped</B></I>.  A return value of {@code TRUE} implies that the {@code String} is not to
     * be ignored or passed over, but rather 'kept.'
     *
     * <BR /><BR />This behavior is consisten with the Java Stream's method
     * {@code Stream.filter(Predicate)}.
     *
     * @param o This is the {@code Object} that will be tested.  If it passes the test, then this
     * method must return {@code TRUE} which would imply that the object shall not be filtered.
     * If the object fails the test, this method should return {@code FALSE}, and the object shall
     * not be included in the result set as it has failed the test.
     *
     * @return When implementing this method, returning {@code TRUE} must mean that the
     * {@code Object} has passed the filter's test-requirements (and will subsequently be retained
     * by whatever function is carrying out the filter operation).
     */
    public boolean test(Object o);

    /**
     * Java is not perfect.  This method is fine: since {@code String} inherits {@code Object},
     * and the {@code interface Predicate} does not have any write-dependencies.
     * 
     * <BR /><BR />Erasure is like this with {@code interface Predicate} - because that's how
     * it works with all of the generics.  A {@code Vector<Object>} cannot be passed to a
     * {@code Vector<String>} parameter (because a person might need to use the {@code String's}).
     * But a {@code Predicate<Object>} <B><I>can not</I></B> have problems being passed to a 
     * {@code Predicate<String>}...
     *
     * @return {@code 'this' StrFilter} cast into a {@code Predicate<String>}
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public default Predicate<String> castToStrPred()
    {
        Predicate p = this;
        return (Predicate<String>) p;
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // strList: KEEP
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
     */
    public static StrFilter strListKEEP(Iterable<String> strList, boolean ignoreCase)
    { return strListKEEP(strList.iterator(), ignoreCase); }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
     * <BR />Converts: {@code String[]} VarArgs to {@code Iterator<String>}
     */
    public static StrFilter strListKEEP(boolean ignoreCase, String... strList)
    { return strListKEEP(Arrays.asList(strList).iterator(), ignoreCase); }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
     */
    public static StrFilter strListKEEP(String strListFileName, boolean ignoreCase)
        throws IOException
    { return strListKEEP(FileRW.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListKEEP(Iterator, boolean)}
     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
     * <BR />Catches Exception, and Fails Fast - (See {@link LFEC})
     */
    public static StrFilter strListKEEP_NOIOE(String strListFileName, boolean ignoreCase)
    { return strListKEEP(LFEC.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
     *
     * <BR /><BR />Here, an input-object's {@code toString()} result is compared against the
     * {@code String's} contained in {@code 'strList'} - which would also be called an
     * input-{@code String} 'white-list.'
     *
     * @param strList A list of {@code String's} to be used as a 'comparison set' against an
     * input-{@code String} in a {@code Predicate}-test.
     *
     * @param ignoreCase When this is {@code TRUE} all equality-comparisons performed in the
     * {@code String's} tested will ignore case-sensitivity.
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to each of the {@code String's} in the input-parameter {@code 'strList'}
     */
    public static StrFilter strListKEEP(Iterator<String> strList, boolean ignoreCase)
    {
        // Build a TreeSet<String>, this will be used, internally, in the instance of 'StrFilter'
        // (a Predicate<Object>) that is returned by this factory-builder method.
        // 
        // Experienced Java users: TreeSet is a very convenient and easy-to-use "sorted list"
        // implementation that stores strings, in sorted order, in a binary-tree.  This makes
        // looking them up as efficient as possible.

        final TreeSet<String> ts = new TreeSet<>();

        // When building this sorted-list (TreeSet), if we are ignoring case (and-only-if), then
        // we must convert the strings contained by the input parameter string-list 'strList' to
        // lower-case first.

        if (ignoreCase) while (strList.hasNext()) ts.add(strList.next().toLowerCase());
        else            while (strList.hasNext()) ts.add(strList.next());

        // The returned Predicate<Object> must call '.toString()' on it's object-input before
        // checking the "Sorted-List" (TreeSet<String>) to see if it contains the string.  If a
        // case-insensitive predicate was requested, then we must build a Predicate that invokes
        // BOTH 'toString()' AND THEN invokes 'toLowerCase()' on the input Object.

        if (ignoreCase) return (Object o) -> ts.contains(o.toString().toLowerCase());
        else            return (Object o) -> ts.contains(o.toString());
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // strList REJECT
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
     */
    public static StrFilter strListREJECT(Iterable<String> strList, boolean ignoreCase)
    { return strListREJECT(strList.iterator(), ignoreCase); }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
     * <BR />Converts: {@code String[]} VarArgs to {@code Iterator<String>}
     */
    public static StrFilter strListREJECT(boolean ignoreCase, String... strList)
    { return strListREJECT(Arrays.asList(strList).iterator(), ignoreCase); }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
     */
    public static StrFilter strListREJECT(String strListFileName, boolean ignoreCase) throws IOException
    { return strListREJECT(FileRW.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }

    /**
     * Convenience Method.
     * <BR />Invokes: {@link #strListREJECT(Iterator, boolean)}
     * <BR />Loads: {@code 'strListFileName'} from disk to an {@code Iterator<String>}
     * <BR />Catches Exception, and Fails Fast - (See {@link LFEC})
     */
    public static StrFilter strListREJECT_NOIOE(String strListFileName, boolean ignoreCase)
    { return strListREJECT(LFEC.loadFileToVector(strListFileName, false).iterator(), ignoreCase); }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
     *
     * <BR /><BR />Here, an input-object's {@code toString()} result is compared against the
     * {@code String's} contained in {@code 'strList'} - which would also be called an
     * input-{@code String} 'black-list.'
     *
     * @param strList A list of {@code String's} to be used as a 'comparison set' against an
     * input-{@code String} in a {@code Predicate}-test.
     *
     * @param ignoreCase When this is {@code TRUE} all equality-comparisons performed in the
     * {@code String's} tested will ignore case-sensitivity.
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to each of the {@code String's} in the input-parameter {@code 'strList'}.
     */
    public static StrFilter strListREJECT(Iterator<String> strList, boolean ignoreCase)
    {
        // Build a TreeSet<String>, this will be used, internally, in the instance of 'StrFilter'
        // (a Predicate<Object>) that is returned by this factory-builder method.
        // 
        // Experienced Java users: TreeSet is a very convenient and easy-to-use "sorted list"
        // implementation that stores strings, in sorted order, in a binary-tree.  This makes
        // looking them up as efficient as possible.

        final TreeSet<String> ts = new TreeSet<>();

        // When building this sorted-list (TreeSet), if we are ignoring case (and-only-if), then
        // we must convert the strings contained by the input parameter string-list 'strList' to
        // lower-case first.

        if (ignoreCase) while (strList.hasNext()) ts.add(strList.next().toLowerCase());
        else            while (strList.hasNext()) ts.add(strList.next());

        // The returned Predicate<Object> must call '.toString()' on it's object-input before
        // checking the "Sorted-List" (TreeSet<String>) to see if it contains the string.  If a
        // case-insensitive predicate was requested, then we must build a Predicate that invokes
        // BOTH 'toString()' AND THEN invokes 'toLowerCase()' on the input Object.

        if (ignoreCase) return (Object o) -> ! ts.contains(o.toString().toLowerCase());
        else            return (Object o) -> ! ts.contains(o.toString());
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // strList Regular-Expressions: Filter Factory / Filter-Generator  static-methods
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
     *
     * <BR /><BR />Here, an input-object's {@code toString()} result is checked against Java's
     * Regular-Expression matcher.  The {@code Predicate} generated will choose to <B>KEEP</B>
     * input-{@code Object's} by returning {@code TRUE} when the Regular-Expression matches the
     * {@code Object.toString()} results.
     *
     * @param regEx A regular-expression used to compare against an input-{@code Object} (using
     * {@code Object.toString()} in a {@code Predicate}-test.
     *
     * @param regExMustMatchEntireString When this parameter is passed {@code TRUE}, then the
     * regular-expression {@code Pattern} must match the <B>entire input {@code Object.toString()}
     * </B>.   When this parameter is passed {@code FALSE}, it only need match some portion or
     * part of the input-parameter {@code String} in the {@code Predicate-test} that is being
     * generated.
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to the input-parameter {@code 'regEx'}
     */
    public static StrFilter regExKEEP(Pattern regEx, boolean regExMustMatchEntireString)
    {
        // FAIL-FAST: Perform this test BEFORE building the regex, to save the programmer
        // from later on (when using this factory generated predicate) having a
        // NullPointerException that is more difficult to isolate.

        if (regEx == null) throw new NullPointerException(
            "The Regular-Expression provided to the 'regEx' parameter of this " +
            "static-factory-builder method, 'regExKEEP,' was null."
        );

        if (regExMustMatchEntireString)
        {
            // the java.util.regex.Pattern.asPredicate() method produces a predicate that must
            // match an entire input string.  This would be identical (if it were possible) to
            // insert a '^' at the beginning of the regex, and an '$' at the end.
    
            final Predicate<String> p = regEx.asPredicate();

            return (Object o) -> p.test(o.toString());
        }

        else
            return (Object o) -> regEx.matcher(o.toString()).find();
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
     *
     * <BR /><BR />Here, an input-object's {@code toString()} result is checked against Java's
     * Regular-Expression matcher.  The {@code Predicate} generated will choose to <B>REJECT</B>
     * input-{@code Object's} by returning {@code FALSE} when the Regular-Expression matches the
     * {@code Object.toString()} results.
     *
     * @param regEx A regular-expression used to compare against an input-{@code Object} (using
     * {@code Object.toString()} in a {@code Predicate}-test.
     *
     * @param regExMustMatchEntireString When this parameter is passed {@code TRUE}, then the
     * regular-expression {@code Pattern} must match the <B>entire input {@code Object.toString()}
     * </B>.   When this parameter is passed {@code FALSE}, it only need match some portion or
     * part of the input-parameter {@code String} in the {@code Predicate-test} that is being
     * generated.
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to the input-parameter {@code 'regEx'}
     */
    public static StrFilter regExREJECT(Pattern regEx, boolean regExMustMatchEntireString)
    {
        // FAIL-FAST: Perform this test BEFORE building the regex, to save the programmer
        // from later on (when using this factory generated predicate) having a
        // NullPointerException that is more difficult to isolate.

        if (regEx == null) throw new NullPointerException(
            "The Regular-Expression provided to the 'regEx' parameter of this " +
            "static-factory-builder method, 'regExREJECT,' was null."
        );

        if (regExMustMatchEntireString)
        {
            // the java.util.regex.Pattern.asPredicate() method produces a predicate that must
            // match an entire input string.  This would be identical (if it were possible) to
            // insert a '^' at the beginning of the regex, and an '$' at the end.

            final Predicate<String> p = regEx.asPredicate().negate();
            return (Object o) -> ! p.test(o.toString());
        }
        else
            return (Object o) -> ! regEx.matcher(o.toString()).find();
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // strList Regular-Expressions: Filter Factory / Filter-Generator  static-methods
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
     *
     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
     * matches <I>each-and-every-one</I> of the provided regular-expressions.
     *
     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to the input-parameter {@code 'regExs'}
     */
    public static StrFilter regExsAND(Pattern... regExs)
    {
        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.

        for (Pattern regEx : regExs)

            if (regEx == null) throw new NullPointerException(
                "One or more of the elements passed to static-factory method 'regexsAND' are " +
                "null."
            );

        // This over-comes a minor "possible complication" - without likely causing much
        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
        // changed after building this Predicate, the Predicate's behavior would fail.
        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
        // predicate that stores a different  array pointer (because it is to a different array)
        // inside the Predicate's body.

        final Pattern[] pArr = regExs.clone();

        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.

        if (pArr.length == 0) throw new IllegalArgumentException(
            "This static-factory method 'regExsAND' has been invoked with zero-arguments to the " +
            "'regExs' var-args parameter."
        );

        return (Object o) ->
        {
            String s=o.toString();

            // Cycle the Regular-Expressions.  "AND" means if even one fails, return FALSE
            // immediately.

            for (Pattern p : pArr) if (! p.matcher(s).find()) return false;

            // None of them failed, return TRUE.
            return true;
        };
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
     *
     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
     * matches <I>any-one-of</I> the provided regular-expressions.
     * 
     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to the input-parameter {@code 'regExs'}
     */
    public static StrFilter regExsOR(Pattern... regExs)
    {
        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once the
        // lambda-predicate is invoked.

        for (Pattern regEx : regExs)

            if (regEx == null) throw new NullPointerException(
                "One or more of the elements passed to static-factory method 'regExsOR' are " +
                "null."
            );

        // This over-comes a minor "possible complication" - without likely causing much
        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
        // changed after building this Predicate, the Predicate's behavior would fail.
        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
        // predicate that stores a different  array pointer (because it is to a different array)
        // inside the Predicate's body.

        final Pattern[] pArr = regExs.clone();

        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.

        if (pArr.length == 0) throw new IllegalArgumentException(
            "This static-factory method 'regExsOR' has been invoked with zero-arguments to the" +
            "'regExs' var-args parameter."
        );

        return (Object o) ->
        {
            String s = o.toString();

            // Cycle the Regular-Expressions.  "OR" means if even one succeeds, return TRUE
            // immediately.

            for (Pattern p : pArr) if (p.matcher(s).find()) return true;

            // None succeeded, so return FALSE.
            return false;
        };
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
     *
     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
     * <I>does-not-match-any</I> of the provided regular-expressions.
     *
     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to the input-parameter {@code 'regExs'}.
     */
    public static StrFilter regExsNAND(Pattern... regExs)
    {
        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once the
        // lambda-predicate is invoked.

        for (Pattern regEx : regExs)

            if (regEx == null) throw new NullPointerException(
                "One or more of the elements passed to static-factory method 'regExsNAND' are " +
                "null."
            );

        // This over-comes a minor "possible complication" - without likely causing much
        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
        // changed after building this Predicate, the Predicate's behavior would fail.
        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
        // predicate that stores a different  array pointer (because it is to a different array)
        // inside the Predicate's body.

        final Pattern[] pArr = regExs.clone();

        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.

        if (pArr.length == 0) throw new IllegalArgumentException(
            "This static-factory method 'regExsNAND' has been invoked with zero-arguments to " +
            "the 'regExs' var-args parameter."
        );

        return (Object o) ->
        {
            String s = o.toString();

            // Cycle the Regular-Expressions.  "NAND" means if even one succeeds, return FALSE
            // immediately.

            for (Pattern p : pArr) if (p.matcher(s).find()) return false;

            // All regex's failed, so return TRUE
            return true;
        };
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE>
     *
     * The {@code Predicate} that is created by this factory-method will have a {@code test()}
     * method that returns {@code TRUE} if an input object's {@code Object.toString()} output
     * matches <I>precisely-one-of</I> the provided regular-expressions.
     *
     * @param regExs <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_REGEXS>
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to the input-parameter {@code 'regExs'}
     */
    public static StrFilter regExsXOR(Pattern... regExs)
    {
        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once the
        // lambda-predicate is invoked.

        for (Pattern regEx : regExs)

            if (regEx == null) throw new NullPointerException(
                "One or more of the elements passed to static-factory method 'regExsXOR' are " +
                "null."
            );

        // This over-comes a minor "possible complication" - without likely causing much
        // inefficiency.  If a RegEx[] were passed (array, not a '...' list) and that array were
        // changed after building this Predicate, the Predicate's behavior would fail.
        // Avoid this (unlikely, but possible) problem by copying the array, and returning a 
        // predicate that stores a different  array pointer (because it is to a different array)
        // inside the Predicate's body.

        final Pattern[] pArr = regExs.clone();

        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.

        if (pArr.length == 0) throw new IllegalArgumentException(
            "This static-factory method 'regExsXOR' has been invoked with zero-arguments to the " +
            "'regExs' var-args parameter."
        );

        return (Object o) ->
        {
            String  s       = o.toString();
            int     count   = 0;

            // Cycle the Regular-Expressions.  Because this is "XOR" - we must keep a count.
            // If that count is > 1, we have to return FALSE immediately.

            for (Pattern p : pArr) if (p.matcher(s).find()) { if (++count > 1) return false; }

            // If there was precisely one match, return TRUE, otherwise return FALSE.
            return count == 1;
        };
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Other static-factory methods
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
     * 
     * Here, the class {@code TextComparitor} is reused from the {@code 'NodeSearch'} package.
     * Please review how each of the pre-defined, {@code static}-instances of class
     * {@code TextComparitor} operate when presented with {@code String}-input.
     *
     * <DIV CLASS="EXAMPLE">{@code
     * StrFilter filter = StrFilter.comparitor
     *     (TextComparitor.DOES_NOT_START_WITH, "Today in Washington,");
     * 
     * // The above fiter would REJECT any input Object whose 'toString()' method returned a 
     * // String that began with the words "Today in Washington,"
     * 
     * StrFilter filter2 = StrFilter.comparitor
     *     (TextComparitor.CONTAINS_CASE_INSENSITIVE, "Highway 61 Revisited");
     * 
     * // This filter would KEEP / RETAIN any input Object whose 'toString()' method returned a
     * // String that contained the words "Highway 61 Revisited".  This comparison to be performed
     * // would be case-insensitive.
     * }</DIV>
     *
     * @param tc This is an instance of the {@code class TextComparitor}, which is defined in the
     * NodeSearch package.
     *
     * @param compareStrs These must be the comparison-{@code String's} used by class
     * {@code 'TextComparitor'} for performing the comparisons.
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code String's},
     * according to the input-parameters {@code 'tc'} and {@code 'compareStrs'}
     *
     * @see TextComparitor
     */
    public static StrFilter comparitor(TextComparitor tc, java.lang.String... compareStrs)
    {
        // FAIL-FAST: Check that the user-provided-parameters would not cause exceptions once
        // the lambda-predicate is invoked.

        if (tc == null) throw new NullPointerException
            ("A null TextComparitor has been passed to static-factory method 'comparitorKeep'");

        TCCompareStrException.check(compareStrs);

        // Mostly, this is over-board, but it doesn't slow anything down much, and it does force
        // users to think about Object-References and Parameter-Argument Marshaling.

        final String[] cmpStrs = compareStrs.clone();

        // Builds (and returns) a Predicate<Object> that re-uses the TextComparitor's
        // 'test(String, String...)' method.

        return (Object o) -> tc.test(o.toString(), cmpStrs);
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
     * 
     * Here, a single {@code String} is used as input.  The {@code Predicate} that is created by
     * this factory-method  will have a {@code test()} method that returns {@code TRUE} only if the
     * an input object's {@code 'toString()'} output <I>equals</I> the the input-parameter
     * {@code String 's'}.
     *
     * @param s This is a {@code String} which will be used to test for equality in the generated
     * {@code Predicate.test(Object)}
     *
     * @param ignoreCase When this is {@code TRUE}, the equality-test will ignore case when
     * performing its comparisons.
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code Object's} via the
     * {@code Object.toString()} method, and the input-parameter {@code 's'}
     */
    public static StrFilter isEqualKEEP(String s, boolean ignoreCase)
    {
        if (s == null) throw new NullPointerException(
            "A null String has been passed to parameter 's' of static-factory method " +
            "'isEqualKEEP'."
        );

        // Builds & returns a predicate based on the whether or not 'ignoreCase' is true or false.
        return ignoreCase
            ? (Object o) -> o.toString().equalsIgnoreCase(s)
            : (Object o) -> o.toString().equals(s);
    }

    /**
     * <EMBED CLASS='external-html' DATA-FILE-ID=STR_FILTER_NOTE2>
     * 
     * Here, a single {@code String} is used as input.  The {@code Predicate} that is created by
     * this factory-method will have a {@code test()} method that returns {@code TRUE} only if an
     * input {@code Object's} {@code Object.toString()} output <I>does-not-equal</I> the the
     * input-parameter {@code String 's'}.
     *
     * @param s This is a {@code String} which will be used to test for equality in the generated
     * {@code Predicate.test(Object)}.
     * 
     * @param ignoreCase When this is {@code TRUE}, the equality-test will ignore case when
     * performing its comparisons.
     *
     * @return A new {@code StrFilter} that can be used to test and filter {@code Object's} via the
     * {@code Object.toString()} method, and the input-parameter {@code 's'}
     */
    public static StrFilter isEqualREJECT(String s, boolean ignoreCase)
    {
        if (s == null) throw new NullPointerException(
            "A null String has been passed to parameter 's' of static-factory method " +
            "'isEqualREJECT'."
        );

        // Builds & returns a predicate based on the whether or not 'ignoreCase' is true or false.
        return ignoreCase
            ? (Object o) -> ! o.toString().equalsIgnoreCase(s)
            : (Object o) -> ! o.toString().equals(s);
    }
}