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