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