View Javadoc
1 /* 2 * TokenizedStrings.java 3 * Created on September 15, 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 /*** 29 * Specification interface for identifying Strings or parts of Strings that are 30 * delimeted by a paticular character. Canidate strings are parsed in the same 31 * form as by an instance of StringTokenizer set to not return tokens (leasding, 32 * trailing, and consecutive delimeters are ignored). Instances of this class may 33 * be logically combined using the AND, OR, and NOT operations. Instances may also 34 * be sequentially constructed using the Builder class. Many common use-cases are 35 * supplied via static accessor methods. 36 * 37 * @author Lonnie Pryor 38 * @version $Revision: 1.1 $ 39 */ 40 public abstract class TokenizedStrings extends Strings { 41 /*** 42 * Creates a new TokenizedStrings object. 43 */ 44 TokenizedStrings () { 45 } 46 47 /*** 48 * Returns a specification satisfied by any non-null string. 49 * 50 * @return A specification satisfied by any non-null string. 51 */ 52 public static Strings anyTokens () { 53 return Strings.any(); 54 } 55 56 /*** 57 * Returns a specification satisfied by strings with the specified minimum number 58 * of tokens. 59 * 60 * @param minTokens The minimum number of tokens in valid strings. 61 * @param delimeter The token delimeter. 62 * 63 * @return A specification satisfied by strings with the specified minimum number 64 * of tokens 65 */ 66 public static Strings withAtLeastNumTokens ( 67 final int minTokens, final char delimeter) { 68 if (minTokens < 0) 69 throw new IllegalArgumentException("minTokens < 0"); 70 if (minTokens < 2) 71 return anyTokens(); 72 return new TokenizedStrings() { 73 boolean doEvaluate (String canidate, int begin, int end) { 74 int count = 1; 75 for (int i = begin; i < end;) { 76 if (canidate.charAt(i) == delimeter) { 77 ++count; 78 if (count >= minTokens) 79 return true; 80 while ((i < end) && (canidate.charAt(i) == delimeter)) 81 ++i; 82 } else 83 ++i; 84 } 85 return false; 86 } 87 88 char delimeter () { 89 return delimeter; 90 } 91 }; 92 } 93 94 /*** 95 * Returns a specification satisfied by single-token strings matching the 96 * specified string pattern. 97 * 98 * @param stringPattern The pattern for the single token canidates. 99 * @param delimeter The token delimeter. 100 * 101 * @return A specification satisfied by single-token strings matching the 102 * specified string pattern. 103 * 104 * @see Strings#parse(String) 105 */ 106 public static Strings matchingToken (String stringPattern, char delimeter) { 107 if (stringPattern == null) 108 throw new NullPointerException("stringPattern"); 109 return matchingToken(Strings.parse(stringPattern), delimeter); 110 } 111 112 /*** 113 * Returns a specification satisfied by single-token strings that satisfy the 114 * supplied specification. 115 * 116 * @param specification The specification for the single token canidates. 117 * @param delimeter The token delimeter. 118 * 119 * @return A specification satisfied by single-token strings that satisfy the 120 * supplied specification. 121 */ 122 public static Strings matchingToken ( 123 final Strings specification, final char delimeter) { 124 if (specification == null) 125 throw new NullPointerException("specification"); 126 return new TokenizedStrings() { 127 boolean doEvaluate (String canidate, int begin, int end) { 128 for (int i = begin; i < end; ++i) 129 if (canidate.charAt(i) == delimeter) 130 return false; 131 return specification.isSatisfiedBy(canidate, begin, end); 132 } 133 134 char delimeter () { 135 return delimeter; 136 } 137 }; 138 } 139 140 /*** 141 * Returns a Builder whose resulting Strings specification will allow any number 142 * of inital tokens. 143 * 144 * @return A Builder whose resulting Strings specification will allow any number 145 * of inital tokens. 146 */ 147 public static Builder startingWithAnyTokens () { 148 return startingWithAtLeastNumTokens(0); 149 } 150 151 /*** 152 * Returns a Builder whose resulting Strings specification will require at least 153 * the supplied number of inital tokens. 154 * 155 * @param minTokens The minimum number of tokens to require. 156 * 157 * @return A Builder whose resulting Strings specification will require at least 158 * the supplied number of inital tokens. 159 */ 160 public static Builder startingWithAtLeastNumTokens (final int minTokens) { 161 if (minTokens < 0) 162 throw new IllegalArgumentException("minTokens < 0"); 163 return new Builder() { 164 TokenizedStrings create (TokenizedStrings next, char delimeter) { 165 return newWildcard(minTokens, next, delimeter); 166 } 167 }; 168 } 169 170 /*** 171 * Returns a Builder whose resulting Strings specification will require the 172 * supplied pattern at the beginning of canidate strings. 173 * 174 * @param stringPattern The pattern required at the beginning of canidates. 175 * 176 * @return A Builder whose resulting Strings specification will require the 177 * supplied pattern at the beginning of canidate strings. 178 * 179 * @see Strings#parse(String) 180 */ 181 public static Builder startingWithToken (final String stringPattern) { 182 if (stringPattern == null) 183 throw new NullPointerException("stringPattern"); 184 return startingWithToken(Strings.parse(stringPattern)); 185 } 186 187 /*** 188 * Returns a Builder whose resulting Strings specification will require the 189 * supplied specification at the beginning of canidate strings. 190 * 191 * @param specification The specification required at the beginning of canidates. 192 * 193 * @return A Builder whose resulting Strings specification will require the 194 * supplied specification at the beginning of canidate strings. 195 */ 196 public static Builder startingWithToken (final Strings specification) { 197 if (specification == null) 198 throw new NullPointerException("specification"); 199 return new Builder() { 200 TokenizedStrings create (TokenizedStrings next, char delimeter) { 201 return newToken(specification, next, delimeter); 202 } 203 }; 204 } 205 206 /*** 207 * Parses a delimeted string pattern containing the '*' wildcard. Format: 208 * <pre> 209 * tok-strings ::= token [ delimiter token ]... 210 * token ::= See Strings.parse() 211 * delimiter ::= delim-char [ delim-char ] 212 * delim-char ::= Any single character 213 * </pre> 214 * Astriks will match zero or more characters except the delimeter. Double 215 * delimeters will match any number of delimeted tokens, all other characters 216 * are matched exactly. Patterns may not start or end with the delimeter. 217 * 218 * @param tokStringPattern The content of the pattern. 219 * @param delimeter The token delimeter. 220 * 221 * @return A Strings specification from the supplied pattern. 222 * 223 * @throws ParseException if the pattern is invalid. 224 * 225 * @see Strings#parse(String) 226 */ 227 public static Strings parse (String tokStringPattern, char delimeter) { 228 if (tokStringPattern == null) 229 throw new NullPointerException("tokStringPattern"); 230 if (tokStringPattern.equals("*")) 231 return anyTokens(); 232 int firstDelim = tokStringPattern.indexOf(delimeter); 233 if (firstDelim < 0) 234 return matchingToken(tokStringPattern, delimeter); 235 if (firstDelim == 0) 236 throw new ParseException( 237 "Tokenized string patterns may not start with a delimeter '" 238 + tokStringPattern + "'"); 239 if (tokStringPattern.charAt(tokStringPattern.length() - 1) == delimeter) 240 throw new ParseException( 241 "Tokenized string patterns may not end with a delimeter '" 242 + tokStringPattern + "'"); 243 return readDelimeter( 244 tokStringPattern, delimeter, firstDelim, 245 startingWithToken(tokStringPattern.substring(0, firstDelim))); 246 } 247 248 /*** 249 * Reads a wildcard as the next pattern input. 250 * 251 * @param tokStringPattern The content of the pattern. 252 * @param delimeter The token delimeter. 253 * @param index The current index. 254 * @param builder The Builder to use. 255 * 256 * @return The finished Strings specification. 257 * 258 * @throws ParseException if the pattern is invalid. 259 */ 260 private static Strings readDelimeter ( 261 String tokStringPattern, char delimeter, int index, Builder builder) { 262 if (tokStringPattern.charAt(index + 1) == delimeter) 263 return readToken( 264 tokStringPattern, delimeter, index + 2, builder.followedByAnyTokens()); 265 return readToken(tokStringPattern, delimeter, index + 1, builder); 266 } 267 268 /*** 269 * Reads a token as the next pattern input. 270 * 271 * @param tokStringPattern The content of the pattern. 272 * @param delimeter The token delimeter. 273 * @param index The current index. 274 * @param builder The Builder to use. 275 * 276 * @return The finished Strings specification. 277 * 278 * @throws ParseException if the pattern is invalid. 279 */ 280 private static Strings readToken ( 281 String tokStringPattern, char delimeter, int index, Builder builder) { 282 int nextDelim = tokStringPattern.indexOf(delimeter, index); 283 if (nextDelim < 0) 284 return builder.endingWithToken(tokStringPattern.substring(index), delimeter); 285 return readDelimeter( 286 tokStringPattern, delimeter, nextDelim, 287 builder.followedByToken(tokStringPattern.substring(index, nextDelim))); 288 } 289 290 /*** 291 * Parses a logical expression as described in the Expression class, using this 292 * class's parse() method for creating values. 293 * 294 * @param tokStringPatternExpr The content of the pattern expression. 295 * @param delimeter The token delimeter. 296 * 297 * @return A Strings specification from the supplied pattern expression. 298 * 299 * @throws ParseException if the pattern expression is invalid. 300 * 301 * @see Expression 302 * @see TokenizedStrings#parse(String, char) 303 */ 304 public static Strings parseExpression ( 305 String tokStringPatternExpr, final char delimeter) { 306 if (tokStringPatternExpr == null) 307 throw new NullPointerException("tokStringPatternExpr"); 308 return (Strings)Expression.parse( 309 tokStringPatternExpr, 310 new Expression.Builder() { 311 public Object or (Object left, Object right) { 312 return ((Strings)left).or((Strings)right); 313 } 314 315 public Object and (Object left, Object right) { 316 return ((Strings)left).and((Strings)right); 317 } 318 319 public Object not (Object value) { 320 return ((Strings)value).not(); 321 } 322 323 public Object create (String tokStringPattern) { 324 return parse(tokStringPattern, delimeter); 325 } 326 }); 327 } 328 329 /* (non-Javadoc) 330 * @see com.lonniepryor.blues.util.Strings#evaluate(java.lang.String, int, int) 331 */ 332 protected final boolean evaluate (String canidate, int begin, int end) { 333 while ((begin < end) && (canidate.charAt(begin) == delimeter())) 334 ++begin; 335 while ((end > begin) && (canidate.charAt(end - 1) == delimeter())) 336 --end; 337 return doEvaluate(canidate, begin, end); 338 } 339 340 /*** 341 * Returns true if the specified range satisfies this specification. 342 * 343 * @param canidate The String to test. 344 * @param begin The beginning of the range (inclusive). 345 * @param end The end of the range(excusive). 346 * 347 * @return True if the specified range satisfies this specification. 348 */ 349 abstract boolean doEvaluate (String canidate, int begin, int end); 350 351 /*** 352 * Returns the delimeter character for this specification. 353 * 354 * @return The delimeter character for this specification. 355 */ 356 abstract char delimeter (); 357 358 /*** 359 * Helper class for sequentially constructing the elements of a common 360 * TokenizedStrings specification. 361 * 362 * @author Lonnie Pryor 363 * @version $Revision: 1.1 $ 364 */ 365 public abstract static class Builder { 366 /*** 367 * Returns a Builder whose resulting Strings specification will allow any 368 * number of tokens at this point in canidate strings. 369 * 370 * @return A Builder whose resulting Strings specification will allow any 371 * number of tokens at this point in canidate strings. 372 */ 373 public Builder followedByAnyTokens () { 374 return followedByAtLeastNumTokens(0); 375 } 376 377 /*** 378 * Returns a Builder whose resulting Strings specification will require at 379 * least the supplied number of tokens at this point in canidate strings. 380 * 381 * @param minTokens The minimum number of tokens to require. 382 * 383 * @return A Builder whose resulting Strings specification will require at 384 * least the supplied number of tokens at this point in canidate 385 * strings. 386 */ 387 public Builder followedByAtLeastNumTokens (final int minTokens) { 388 if (minTokens < 0) 389 throw new IllegalArgumentException("minTokens < 0"); 390 return new Builder() { 391 TokenizedStrings create (TokenizedStrings next, char delimeter) { 392 return Builder.this.create( 393 newWildcard(minTokens, next, delimeter), delimeter); 394 } 395 }; 396 } 397 398 /*** 399 * Returns a Builder whose resulting Strings specification will require the 400 * supplied pattern at this point in canidate strings. 401 * 402 * @param stringPattern The pattern required at this point in canidates. 403 * 404 * @return A Builder whose resulting Strings specification will require the 405 * supplied pattern at this point in canidate strings. 406 * 407 * @see Strings#parse(String) 408 */ 409 public Builder followedByToken (final String stringPattern) { 410 if (stringPattern == null) 411 throw new NullPointerException("stringPattern"); 412 return followedByToken(Strings.parse(stringPattern)); 413 } 414 415 /*** 416 * Returns a Builder whose resulting Strings specification will require the 417 * supplied specification at this point in canidate strings. 418 * 419 * @param specification The specification required at this point in canidates. 420 * 421 * @return A Builder whose resulting Strings specification will require the 422 * supplied specification at this point in canidate strings. 423 */ 424 public Builder followedByToken (final Strings specification) { 425 if (specification == null) 426 throw new NullPointerException("specification"); 427 return new Builder() { 428 TokenizedStrings create (TokenizedStrings next, char delimeter) { 429 return Builder.this.create( 430 newToken(specification, next, delimeter), delimeter); 431 } 432 }; 433 } 434 435 /*** 436 * Returns a Strings specification that will allow any number of tokens at the 437 * end of canidate strings. 438 * 439 * @param delimeter The token delimeter. 440 * 441 * @return A Strings specification that will allow any number of tokens at the 442 * end of canidate strings. 443 */ 444 public Strings endingWithAnyTokens (char delimeter) { 445 return endingWithAtLeastNumTokens(0, delimeter); 446 } 447 448 /*** 449 * Returns a Strings specification that will require at least the supplied 450 * number of tokens at the end of canidate strings. 451 * 452 * @param minTokens The minimum number of tokens to require at the end of 453 * canidates. 454 * @param delimeter The token delimeter. 455 * 456 * @return A Strings specification that will require at least the supplied 457 * number of tokens at the end of canidate strings. 458 */ 459 public Strings endingWithAtLeastNumTokens (final int minTokens, char delimeter) { 460 if (minTokens < 0) 461 throw new IllegalArgumentException("minTokens < 0"); 462 return Builder.this.create( 463 newWildcard(minTokens, null, delimeter), delimeter); 464 } 465 466 /*** 467 * Returns a Strings specification that will require a token satisfying the 468 * supplied pattern at the end of canidate strings. 469 * 470 * @param stringPattern The pattern required at the end of canidates. 471 * @param delimeter The token delimeter. 472 * 473 * @return A Strings specification that will require a token satisfying the 474 * supplied pattern at the end of canidate strings. 475 * 476 * @see Strings#parse(String) 477 */ 478 public Strings endingWithToken (final String stringPattern, char delimeter) { 479 if (stringPattern == null) 480 throw new NullPointerException("stringPattern"); 481 return endingWithToken(Strings.parse(stringPattern), delimeter); 482 } 483 484 /*** 485 * Returns a Strings specification that will require a token satisfying the 486 * supplied specification at the end of canidate strings. 487 * 488 * @param specification The specification required at the end of canidates. 489 * @param delimeter The token delimeter. 490 * 491 * @return A Strings specification that will require a token satisfying the 492 * supplied specification at the end of canidate strings. 493 */ 494 public Strings endingWithToken (final Strings specification, char delimeter) { 495 if (specification == null) 496 throw new NullPointerException("specification"); 497 return Builder.this.create( 498 newToken(specification, null, delimeter), delimeter); 499 } 500 501 /*** 502 * Chained method for constructing instances of Strings. 503 * 504 * @param next The next specification in the chain. 505 * @param delimeter The token delimeter. 506 * 507 * @return A chained Strings object for this Builder instance. 508 */ 509 abstract TokenizedStrings create (TokenizedStrings next, char delimeter); 510 511 /*** 512 * Creates a new chained Strings object that matched a number of characters. 513 * 514 * @param minTokens The minimum number of tokens to require. 515 * @param next The next specification in the chain. 516 * @param delimeter The token delimeter. 517 * 518 * @return A new chained Strings object that matches a number of tokens. 519 */ 520 static TokenizedStrings newWildcard ( 521 final int minTokens, final TokenizedStrings next, final char delimeter) { 522 return new TokenizedStrings() { 523 boolean doEvaluate (String canidate, int begin, int end) { 524 begin = skipRequiredTokens(canidate, begin, end); 525 if (begin < 0) 526 return false; 527 if (next == null) 528 return true; 529 do { 530 if (next.doEvaluate(canidate, begin, end)) 531 return true; 532 while ((begin < end) && (canidate.charAt(begin) != delimeter)) 533 ++begin; 534 if (begin >= end) 535 return false; 536 while ((begin < end) && (canidate.charAt(begin) == delimeter)) 537 ++begin; 538 } while (true); 539 } 540 541 private int skipRequiredTokens (String canidate, int begin, int end) { 542 int tokensFound = 0; 543 while ((begin < end) && (tokensFound < minTokens)) { 544 while ((begin < end) && (canidate.charAt(begin) != delimeter)) 545 ++begin; 546 ++tokensFound; 547 if (begin == end) 548 break; 549 while ((begin < end) && (canidate.charAt(begin) == delimeter)) 550 ++begin; 551 } 552 if (tokensFound < minTokens) 553 return -1; 554 return begin; 555 } 556 557 char delimeter () { 558 return delimeter; 559 } 560 }; 561 } 562 563 /*** 564 * Creates a new chained Strings object that matched a specific sequence of 565 * characters. 566 * 567 * @param specification The text to match. 568 * @param next The next specification in the chain. 569 * @param delimeter The token delimeter. 570 * 571 * @return A new chained Strings object that matched a specific sequence of 572 * characters. 573 */ 574 static TokenizedStrings newToken ( 575 final Strings specification, final TokenizedStrings next, final char delimeter) { 576 return new TokenizedStrings() { 577 boolean doEvaluate (String canidate, int begin, int end) { 578 if (begin == end) 579 return false; 580 int tokenEndsAt = begin; 581 while ( 582 (tokenEndsAt < end) && (canidate.charAt(tokenEndsAt) != delimeter)) 583 ++tokenEndsAt; 584 if (!specification.isSatisfiedBy(canidate, begin, tokenEndsAt)) 585 return false; 586 if (next == null) 587 return tokenEndsAt == end; 588 int nextBeginsAt = tokenEndsAt; 589 while ( 590 (nextBeginsAt < end) && (canidate.charAt(nextBeginsAt) == delimeter)) 591 ++nextBeginsAt; 592 return next.doEvaluate(canidate, nextBeginsAt, end); 593 } 594 595 char delimeter () { 596 return delimeter; 597 } 598 }; 599 } 600 } 601 }

This page was automatically generated by Maven