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