View Javadoc
1 /* 2 * Strings.java 3 * Created on August 18, 2003 4 * 5 * The Blues Framework - A lightweight application framework 6 * Copyright (C) 2003 Lonnie Pryor 7 * http://blues.lonniepryor.com 8 * 9 * This library is free software; you can redistribute it and/or modify it under the 10 * terms of the GNU Lesser General Public License as published by the Free Software 11 * Foundation; either version 2.1 of the License, or (at your option) any later 12 * version. 13 * 14 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 16 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public License along 19 * with this library; if not, write to: 20 * 21 * The Free Software Foundation, Inc. 22 * 59 Temple Place, Suite 330 23 * Boston, MA 02111-1307 USA 24 * 25 */ 26 package com.lonniepryor.blues.util; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 /*** 32 * Specification interface for identifying Strings or parts of Strings. Instances 33 * of this class may be logically combined using the AND, OR, and NOT operations. 34 * Instances may also be sequentially constructed using the Builder class. Many 35 * common use-cases are supplied via static accessor methods. 36 * 37 * @author Lonnie Pryor 38 * @version $Revision: 1.1 $ 39 */ 40 public abstract class Strings { 41 /*** A specification satisfied by any non-null string. */ 42 private static final Strings any = new Strings() { 43 protected boolean evaluate (String canidate, int begin, int end) { 44 return true; 45 } 46 }; 47 /*** A specification satisfied by any non-empty string. */ 48 private static final Strings notEmpty = Strings.withAtLeastNumChars(1); 49 50 /*** 51 * Creates a new Strings object. 52 */ 53 protected Strings () { 54 } 55 56 /*** 57 * Returns a specification satisfied by any non-null string. 58 * 59 * @return A specification satisfied by any non-null string. 60 */ 61 public static Strings any () { 62 return any; 63 } 64 65 /*** 66 * Returns a specification satisfied by any non-empty string. 67 * 68 * @return A specification satisfied by any non-empty string. 69 */ 70 public static Strings notEmpty () { 71 return notEmpty; 72 } 73 74 /*** 75 * Returns a specification satisfied by strings of at least the specified length. 76 * 77 * @param minChars The minimum length of valid strings. 78 * 79 * @return A specification satisfied by strings of al least the specified length 80 */ 81 public static Strings withAtLeastNumChars (final int minChars) { 82 if (minChars < 0) 83 throw new IllegalArgumentException("minChars < 0"); 84 return new Strings() { 85 protected boolean evaluate (String canidate, int begin, int end) { 86 return ((end - begin) >= minChars); 87 } 88 }; 89 } 90 91 /*** 92 * Returns a specification satisfied by strings with a single character that 93 * satisfies the specified specification. 94 * 95 * @param specification The specification to match to. 96 * 97 * @return A specification satisfied by strings with a single character that 98 * satisfies the specified specification. 99 */ 100 public static Strings matching (final Characters specification) { 101 if (specification == null) 102 throw new NullPointerException("specification"); 103 return new Strings() { 104 protected boolean evaluate (String canidate, int begin, int end) { 105 return ((end - begin) == 1) 106 && specification.isSatisfiedBy(canidate.charAt(begin)); 107 } 108 }; 109 } 110 111 /*** 112 * Returns a specification satisfied by strings equal to the specified string. 113 * 114 * @param characters The string to match to. 115 * 116 * @return A specification satisfied by strings equal to the specified string. 117 */ 118 public static Strings matching (final String characters) { 119 if (characters == null) 120 throw new NullPointerException("characters"); 121 return new Strings() { 122 protected boolean evaluate (String canidate, int begin, int end) { 123 return ((end - begin) == characters.length()) 124 && canidate.regionMatches(begin, characters, 0, characters.length()); 125 } 126 }; 127 } 128 129 /*** 130 * Returns a Builder whose resulting Strings specification will allow any number 131 * of inital characters. 132 * 133 * @return A Builder whose resulting Strings specification will allow any number 134 * of inital characters. 135 */ 136 public static Builder startingWithAny () { 137 return startingWithAtLeastNumChars(0); 138 } 139 140 /*** 141 * Returns a Builder whose resulting Strings specification will require at least 142 * the supplied number of inital characters. 143 * 144 * @param minChars The minimum number of charactes to require. 145 * 146 * @return A Builder whose resulting Strings specification will require at least 147 * the supplied number of inital characters. 148 */ 149 public static Builder startingWithAtLeastNumChars (final int minChars) { 150 if (minChars < 0) 151 throw new IllegalArgumentException("minChars < 0"); 152 return new Builder() { 153 Strings create (Strings next) { 154 return newWildcard(minChars, next); 155 } 156 }; 157 } 158 159 /*** 160 * Returns a Builder whose resulting Strings specification will require the 161 * supplied character at the beginning of canidate strings. 162 * 163 * @param specification The specification to match to. 164 * 165 * @return A Builder whose resulting Strings specification will require the 166 * supplied character at the beginning of canidate strings. 167 */ 168 public static Builder startingWith (final Characters specification) { 169 if (specification == null) 170 throw new NullPointerException("specification"); 171 return new Builder() { 172 Strings create (Strings next) { 173 return newCharacters(specification, next); 174 } 175 }; 176 } 177 178 /*** 179 * Returns a Builder whose resulting Strings specification will require the 180 * supplied text at the beginning of canidate strings. 181 * 182 * @param characters The text required at the beginning of canidates. 183 * 184 * @return A Builder whose resulting Strings specification will require the 185 * supplied text at the beginning of canidate strings. 186 */ 187 public static Builder startingWith (final String characters) { 188 if (characters == null) 189 throw new NullPointerException("characters"); 190 return new Builder() { 191 Strings create (Strings next) { 192 return newString(characters, next); 193 } 194 }; 195 } 196 197 /*** 198 * Parses a string pattern containing the '' wildcard. Format: 199 * <pre> 200 * strings ::= wildcard | ( [ wildcard ] pattern ) 201 * pattern ::= characters [ wildcard [ pattern ] ] 202 * wildcard ::= "*" 203 * characters ::= Any sequence of characters except "*" 204 * </pre> 205 * Astriks will match zero or more characters, all other characters are matched 206 * exactly. 207 * 208 * @param stringPattern The content of the pattern. 209 * 210 * @return A Strings specification from the supplied pattern. 211 * 212 * @throws ParseException if the pattern is invalid. 213 */ 214 public static Strings parse (String stringPattern) { 215 if (stringPattern == null) 216 throw new NullPointerException("stringPattern"); 217 if (stringPattern.equals("*")) 218 return any; 219 int firstWildcard = stringPattern.indexOf('*'); 220 if (firstWildcard < 0) 221 return matching(stringPattern); 222 if (firstWildcard == 0) 223 return readCharacters(stringPattern, 1, startingWithAny()); 224 return readWildcard( 225 stringPattern, firstWildcard, 226 startingWith(stringPattern.substring(0, firstWildcard))); 227 } 228 229 /*** 230 * Reads a wildcard as the next pattern input. 231 * 232 * @param stringPattern The content of the pattern. 233 * @param index The current index. 234 * @param builder The Builder to use. 235 * 236 * @return The finished Strings specification. 237 * 238 * @throws ParseException if the pattern is invalid. 239 */ 240 private static Strings readWildcard ( 241 String stringPattern, int index, Builder builder) { 242 if (index == (stringPattern.length() - 1)) 243 return builder.endingWithAny(); 244 return readCharacters(stringPattern, index + 1, builder.followedByAny()); 245 } 246 247 /*** 248 * Reads characters as the next pattern input. 249 * 250 * @param stringPattern The content of the pattern. 251 * @param index The current index. 252 * @param builder The Builder to use. 253 * 254 * @return The finished Strings specification. 255 * 256 * @throws ParseException if the pattern is invalid. 257 */ 258 private static Strings readCharacters ( 259 String stringPattern, int index, Builder builder) { 260 if (stringPattern.charAt(index) == '*') 261 throw new ParseException( 262 "Invalid string pattern '" + stringPattern 263 + "': contains a double wildcard"); 264 int next = stringPattern.indexOf('*', index); 265 if (next < 0) 266 return builder.endingWith(stringPattern.substring(index)); 267 return readWildcard( 268 stringPattern, next, builder.followedBy(stringPattern.substring(index, next))); 269 } 270 271 /*** 272 * Parses a logical expression as described in the Expression class, using this 273 * class's parse() method for creating values. 274 * 275 * @param stringPatternExpr The content of the pattern expression. 276 * 277 * @return A Strings specification from the supplied pattern expression. 278 * 279 * @throws ParseException if the pattern expression is invalid. 280 * 281 * @see Expression 282 * @see Strings#parse(String) 283 */ 284 public static Strings parseExpression (String stringPatternExpr) { 285 if (stringPatternExpr == null) 286 throw new NullPointerException("stringPatternExpr"); 287 return (Strings)Expression.parse( 288 stringPatternExpr, 289 new Expression.Builder() { 290 public Object or (Object left, Object right) { 291 return ((Strings)left).or((Strings)right); 292 } 293 294 public Object and (Object left, Object right) { 295 return ((Strings)left).and((Strings)right); 296 } 297 298 public Object not (Object value) { 299 return ((Strings)value).not(); 300 } 301 302 public Object create (String stringPattern) { 303 return parse(stringPattern); 304 } 305 }); 306 } 307 308 /*** 309 * Returns true if the supplied String is not null and satisfies this 310 * specification. 311 * 312 * @param canidate The String to test. 313 * 314 * @return True if the supplied String is not null and satisfies this 315 * specification. 316 */ 317 public final boolean isSatisfiedBy (String canidate) { 318 return (canidate != null) && evaluate(canidate, 0, canidate.length()); 319 } 320 321 /*** 322 * Returns true if the supplied String is not null and the specified range 323 * satisfies this specification. 324 * 325 * @param canidate The String to test. 326 * @param begin The beginning of the range (inclusive). 327 * @param end The end of the range(excusive). 328 * 329 * @return True if the supplied String is not null and the specified range 330 * satisfies this specification. 331 */ 332 public final boolean isSatisfiedBy (String canidate, int begin, int end) { 333 return (canidate != null) && (begin >= 0) && (end <= canidate.length()) 334 && (begin <= end) && evaluate(canidate, begin, end); 335 } 336 337 /*** 338 * Returns true if the specified range satisfies this specification. 339 * 340 * @param canidate The String to test. 341 * @param begin The beginning of the range (inclusive). 342 * @param end The end of the range(excusive). 343 * 344 * @return True if the specified range satisfies this specification. 345 */ 346 protected abstract boolean evaluate (String canidate, int begin, int end); 347 348 /*** 349 * Returns true if all of the supplied Strings satisfy this specification. 350 * 351 * @param all The array of Strings to test. 352 * 353 * @return True if all of the supplied Strings satisfy this specification. 354 */ 355 public final boolean isSatisfiedByAll (String[] all) { 356 if (all == null) 357 throw new NullPointerException("all"); 358 for (int i = 0; i < all.length; ++i) 359 if (!isSatisfiedBy(all[i])) 360 return false; 361 return true; 362 } 363 364 /*** 365 * Returns true if any of the supplied Strings satisfy this specification. 366 * 367 * @param any The array of Strings to test. 368 * 369 * @return True if any of the supplied Strings satisfy this specification. 370 */ 371 public final boolean isSatisfiedByAny (String[] any) { 372 if (any == null) 373 throw new NullPointerException("any"); 374 for (int i = 0; i < any.length; ++i) 375 if (isSatisfiedBy(any[i])) 376 return true; 377 return false; 378 } 379 380 /*** 381 * Selects the first Strings that satisfies this specification from the supplied 382 * array. 383 * 384 * @param from The array to select from. 385 * 386 * @return The first Strings that satisfies this specification from the supplied 387 * array. 388 */ 389 public final String selectFirst (String[] from) { 390 if (from == null) 391 throw new NullPointerException("from"); 392 for (int i = 0; i < from.length; ++i) 393 if (isSatisfiedBy(from[i])) 394 return from[i]; 395 return null; 396 } 397 398 /*** 399 * Selects all the Strings that satisfy this specification from the supplied 400 * array. 401 * 402 * @param from The array to select from. 403 * 404 * @return All the Strings that satisfy this specification from the supplied 405 * array. 406 */ 407 public final String[] selectAll (String[] from) { 408 if (from == null) 409 throw new NullPointerException("from"); 410 List results = new ArrayList(from.length); 411 for (int i = 0; i < from.length; ++i) 412 if (isSatisfiedBy(from[i])) 413 results.add(from[i]); 414 return (String[])results.toArray(new String[results.size()]); 415 } 416 417 /*** 418 * Returns a specification representing a logical AND of this specification on 419 * the left and the supplied specification on the right. 420 * 421 * @param specification The specification to AND with. 422 * 423 * @return A specification representing a logical AND of this specification on 424 * the left and the supplied specification on the right. 425 */ 426 public final Strings and (final Strings specification) { 427 if (specification == null) 428 throw new NullPointerException("specification"); 429 return new Strings() { 430 public boolean evaluate (String canidate, int begin, int end) { 431 return Strings.this.evaluate(canidate, begin, end) 432 && specification.evaluate(canidate, begin, end); 433 } 434 }; 435 } 436 437 /*** 438 * Returns a specification representing a logical OR of this specification on the 439 * left and the supplied specification on the right. 440 * 441 * @param specification The specification to OR with. 442 * 443 * @return A specification representing a logical OR of this specification on the 444 * left and the supplied specification on the right. 445 */ 446 public final Strings or (final Strings specification) { 447 if (specification == null) 448 throw new NullPointerException("specification"); 449 return new Strings() { 450 public boolean evaluate (String canidate, int begin, int end) { 451 return Strings.this.evaluate(canidate, begin, end) 452 || specification.evaluate(canidate, begin, end); 453 } 454 }; 455 } 456 457 /*** 458 * Returns a specification representing a logical NOT of this specification. 459 * 460 * @return A specification representing a logical NOT of this specification. 461 */ 462 public final Strings not () { 463 return new Strings() { 464 public boolean evaluate (String canidate, int begin, int end) { 465 return !Strings.this.evaluate(canidate, begin, end); 466 } 467 }; 468 } 469 470 /*** 471 * Helper class for sequentially constructing the elements of a common Strings 472 * specification. 473 * 474 * @author Lonnie Pryor 475 * @version $Revision: 1.1 $ 476 */ 477 public abstract static class Builder { 478 /*** 479 * Returns a Builder whose resulting Strings specification will accept any 480 * number of characters at this point in canidate strings. 481 * 482 * @return A Builder whose resulting Strings specification will accept any 483 * number of characters at this point in canidate strings. 484 */ 485 public Builder followedByAny () { 486 return followedByAtLeastNumChars(0); 487 } 488 489 /*** 490 * Returns a Builder whose resulting Strings specification will require the at 491 * least the specified number of characters at this point in canidate strings. 492 * 493 * @param minChars The minimum number of charactes to require. 494 * 495 * @return A Builder whose resulting Strings specification will require the at 496 * least the specified number of characters at this point in canidate 497 * strings. 498 */ 499 public Builder followedByAtLeastNumChars (final int minChars) { 500 if (minChars < 0) 501 throw new IllegalArgumentException("minChars < 0"); 502 return new Builder() { 503 public Strings create (Strings next) { 504 return Builder.this.create(newWildcard(minChars, next)); 505 } 506 }; 507 } 508 509 /*** 510 * Returns a Builder whose resulting Strings specification will require the 511 * supplied character at this point in canidate strings. 512 * 513 * @param specification The specification to match to. 514 * 515 * @return A Builder whose resulting Strings specification will require the 516 * supplied character at this point in canidate strings. 517 */ 518 public Builder followedBy (final Characters specification) { 519 if (specification == null) 520 throw new NullPointerException("specification"); 521 return new Builder() { 522 Strings create (Strings next) { 523 return Builder.this.create(newCharacters(specification, next)); 524 } 525 }; 526 } 527 528 /*** 529 * Returns a Builder whose resulting Strings specification will require the 530 * supplied text at this point in canidate strings. 531 * 532 * @param characters The text to match. 533 * 534 * @return A Builder whose resulting Strings specification will require the 535 * supplied text at this point in canidate strings. 536 */ 537 public Builder followedBy (final String characters) { 538 if (characters == null) 539 throw new NullPointerException("characters"); 540 return new Builder() { 541 public Strings create (Strings next) { 542 return Builder.this.create(newString(characters, next)); 543 } 544 }; 545 } 546 547 /*** 548 * Returns a Strings specification that will accept any number of characters at 549 * the end of canidate strings. 550 * 551 * @return A Strings specification that will accept any number of characters at 552 * the end of canidate strings. 553 */ 554 public Strings endingWithAny () { 555 return endingWithAtLeastNumChars(0); 556 } 557 558 /*** 559 * Returns a Strings specification that will require the at least the specified 560 * number of characters at the end of canidate strings. 561 * 562 * @param minChars The minimum number of charactes to require. 563 * 564 * @return A Strings specification that will require the at least the specified 565 * number of characters at the end of canidate strings. 566 */ 567 public Strings endingWithAtLeastNumChars (final int minChars) { 568 if (minChars < 0) 569 throw new IllegalArgumentException("minChars < 0"); 570 return Builder.this.create(newWildcard(minChars, null)); 571 } 572 573 /*** 574 * Returns a Strings specification that will require the supplied character at 575 * the end of canidate strings. 576 * 577 * @param specification The specification to match to. 578 * 579 * @return A Strings specification that will require the supplied character at 580 * the end of canidate strings. 581 */ 582 public Strings endingWith (final Characters specification) { 583 if (specification == null) 584 throw new NullPointerException("specification"); 585 return Builder.this.create(newCharacters(specification, null)); 586 } 587 588 /*** 589 * Returns a Strings specification that will require the supplied text at the 590 * end of canidate strings. 591 * 592 * @param characters The text to match. 593 * 594 * @return A Strings specification that will require the supplied text at the 595 * end of canidate strings. 596 */ 597 public Strings endingWith (final String characters) { 598 if (characters == null) 599 throw new NullPointerException("characters"); 600 return Builder.this.create(newString(characters, null)); 601 } 602 603 /*** 604 * Chained method for constructing instances of Strings. 605 * 606 * @param next The next specification in the chain. 607 * 608 * @return A chained Strings object for this Builder instance. 609 */ 610 abstract Strings create (Strings next); 611 612 /*** 613 * Creates a new chained Strings object that matched a number of characters. 614 * 615 * @param minChars The minimum number of charactes to require. 616 * @param next The next specification in the chain. 617 * 618 * @return A new chained Strings object that matched a number of characters. 619 */ 620 static Strings newWildcard (final int minChars, final Strings next) { 621 return new Strings() { 622 protected boolean evaluate (String canidate, int begin, int end) { 623 if (next == null) 624 return (end - begin) >= minChars; 625 for (int i = begin + minChars; i < end; ++i) 626 if (next.evaluate(canidate, i, end)) 627 return true; 628 return false; 629 } 630 }; 631 } 632 633 /*** 634 * Creates a new chained Strings object that matched a single character. 635 * 636 * @param specification The character specification to match. 637 * @param next The next specification in the chain. 638 * 639 * @return A new chained Strings object that matched a single character. 640 */ 641 static Strings newCharacters ( 642 final Characters specification, final Strings next) { 643 return new Strings() { 644 protected boolean evaluate (String canidate, int begin, int end) { 645 if (begin >= end) 646 return false; 647 if (!specification.isSatisfiedBy(canidate.charAt(begin))) 648 return false; 649 if (next == null) 650 return (end - begin) == 1; 651 return next.evaluate(canidate, begin + 1, end); 652 } 653 }; 654 } 655 656 /*** 657 * Creates a new chained Strings object that matched a specific sequence of 658 * characters. 659 * 660 * @param characters The text to match. 661 * @param next The next specification in the chain. 662 * 663 * @return A new chained Strings object that matched a specific sequence of 664 * characters. 665 */ 666 static Strings newString (final String characters, final Strings next) { 667 return new Strings() { 668 protected boolean evaluate (String canidate, int begin, int end) { 669 if ((begin + characters.length()) > end) 670 return false; 671 if (!canidate.regionMatches(begin, characters, 0, characters.length())) 672 return false; 673 if (next == null) 674 return (begin + characters.length()) == end; 675 return next.evaluate(canidate, begin + characters.length(), end); 676 } 677 }; 678 } 679 } 680 }

This page was automatically generated by Maven