001 /*
002 * Copyright 2008-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2013 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.util;
022
023
024
025 import java.io.IOException;
026 import java.io.Serializable;
027 import java.text.ParseException;
028 import java.util.ArrayList;
029 import java.util.Random;
030 import java.util.concurrent.atomic.AtomicBoolean;
031
032 import static com.unboundid.util.Debug.*;
033 import static com.unboundid.util.StaticUtils.*;
034 import static com.unboundid.util.UtilityMessages.*;
035
036
037
038 /**
039 * This class provides a method for generating a string value comprised of zero
040 * or more components. The components may be any combination of zero or more
041 * strings, sequential numeric ranges, and random numeric ranges. These
042 * components should be formatted as follows:
043 * <UL>
044 * <LI>Strings are simply any kind of static text that will be used as-is
045 * without any modification, except that double opening or closing square
046 * brackets (i.e., "<CODE>[[</CODE>" or "<CODE>]]</CODE>") will be
047 * replaced with single opening or closing square brackets to distinguish
048 * them from the square brackets used in numeric ranges or URL
049 * references.</LI>
050 * <LI>Sequential numeric ranges consist of an opening square bracket, a
051 * numeric value to be used as the lower bound for the range, a colon, a
052 * second numeric value to be used as the upper bound for the range, an
053 * optional '<CODE>x</CODE>' character followed by a numeric value to be
054 * used as the increment, an optional '<CODE>%</CODE>' character followed
055 * by a format string as allowed by the {@link java.text.DecimalFormat}
056 * class to define how the resulting value should be formatted, and a
057 * closing square bracket to indicate the end of the range.</LI>
058 * <LI>Random numeric ranges consist of an opening square bracket, a
059 * numeric value to be used as the lower bound for the range, a dash, a
060 * second numeric value to be used as the upper bound for the range, an
061 * optional '<CODE>%</CODE>' character followed by a format string as
062 * allowed by the {@link java.text.DecimalFormat} class to define how the
063 * resulting value should be formatted, and a closing square bracket to
064 * indicate the end of the range.</LI>
065 * <LI>Strings read from a file specified by a given URL. That file may be
066 * contained on the local filesystem (using a URL like
067 * "file:///tmp/mydata.txt") or read from a remote server via HTTP (using
068 * a URL like "http://server.example.com/mydata.txt"). In either case,
069 * the provided URL must not contain a closing square bracket character.
070 * If this option is used, then that file must contain one value per line,
071 * and its contents will be read into memory and values from the file will
072 * be selected in a random order and used in place of the bracketed
073 * URL.</LI>
074 * <LI>Back-references that will be replaced with the same value as the
075 * bracketed token in the specified position in the string. For example,
076 * a component of "[ref:1]" will be replaced with the same value as used
077 * in the first bracketed component of the value pattern. Back-references
078 * must only reference components that have been previously defined in the
079 * value pattern, and not those which appear after the reference.</LI>
080 * </UL>
081 * <BR>
082 * It must be possible to represent all of the numeric values used in sequential
083 * or random numeric ranges as {@code long} values. In a sequential numeric
084 * range, if the first value is larger than the second value, then values will
085 * be chosen in descending rather than ascending order (and if an increment is
086 * given, then it should be positive). In addition, once the end of a
087 * sequential range has been reached, then the value will wrap around to the
088 * beginning of that range.
089 * <BR>
090 * Examples of value pattern components include:
091 * <UL>
092 * <LI><CODE>Hello</CODE> -- The static text "<CODE>Hello</CODE>".</LI>
093 * <LI><CODE>[[Hello]]</CODE> -- The static text "<CODE>[Hello]</CODE>" (note
094 * that the double square brackets were replaced with single square
095 * brackets).</LI>
096 * <LI><CODE>[0:1000]</CODE> -- A sequential numeric range that will iterate
097 * in ascending sequential order from 0 to 1000. The 1002nd value that is
098 * requested will cause the value to be wrapped around to 0 again.</LI>
099 * <LI><CODE>[1000:0]</CODE> -- A sequential numeric range that will iterate
100 * in descending sequential order from 1000 to 0. The 1002nd value that is
101 * requested will cause the value to be wrapped around to 1000 again.</LI>
102 * <LI><CODE>[0:1000x5%0000]</CODE> -- A sequential numeric range that will
103 * iterate in ascending sequential order from 0 to 1000 in increments of
104 * five with all values represented as four-digit numbers padded with
105 * leading zeroes. For example, the first four values generated by this
106 * component will be "0000", "0005", "0010", and "0015".</LI>
107 * <LI><CODE>[0-1000]</CODE> -- A random numeric range that will choose values
108 * at random between 0 and 1000, inclusive.</LI>
109 * <LI><CODE>[0-1000%0000]</CODE> -- A random numeric range that will choose
110 * values at random between 0 and 1000, inclusive, and values will be
111 * padded with leading zeroes as necessary so that they are represented
112 * using four digits.</LI>
113 * <LI><CODE>[file:///tmp/mydata.txt]</CODE> -- A URL reference that will
114 * cause randomly-selected lines from the specified local file to be used
115 * in place of the bracketed range.</LI>
116 * <LI><CODE>[http://server.example.com/tmp/mydata.txt]</CODE> -- A URL
117 * reference that will cause randomly-selected lines from the specified
118 * remote HTTP-accessible file to be used in place of the bracketed
119 * range.</LI>
120 * </UL>
121 * <BR>
122 * Examples of full value pattern strings include:
123 * <UL>
124 * <LI><CODE>dc=example,dc=com</CODE> -- A value pattern containing only
125 * static text and no numeric components.</LI>
126 * <LI><CODE>[1000:9999]</CODE> -- A value pattern containing only a numeric
127 * component that will choose numbers in sequential order from 1000 to
128 * 9999.</LI>
129 * <LI><CODE>(uid=user.[1-1000000])</CODE> -- A value pattern that combines
130 * the static text "<CODE>(uid=user.</CODE>" with a value chosen randomly
131 * between one and one million, and another static text string of
132 * "<CODE>)</CODE>".</LI>
133 * <LI><CODE>uid=user.[1-1000000],ou=org[1-10],dc=example,dc=com</CODE> -- A
134 * value pattern containing two numeric components interspersed between
135 * three static text components.</LI>
136 * <LI><CODE>uid=user.[1-1000000],ou=org[ref:1],dc=example,dc=com</CODE> -- A
137 * value pattern in which the organization number will be the same as the
138 * randomly-selected user number.</LI>
139 * </UL>
140 */
141 @NotMutable()
142 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
143 public final class ValuePattern
144 implements Serializable
145 {
146 /**
147 * The serial version UID for this serializable class.
148 */
149 private static final long serialVersionUID = 4502778464751705304L;
150
151
152
153 // Indicates whether the provided value pattern includes one or more
154 // back-references.
155 private final boolean hasBackReference;
156
157 // The string that was originally used to create this value pattern.
158 private final String pattern;
159
160 // The thread-local array list that will be used to hold values for
161 // back-references.
162 private final ThreadLocal<ArrayList<String>> refLists;
163
164 // The thread-local string builder that will be used to build values.
165 private final ThreadLocal<StringBuilder> buffers;
166
167 // The value pattern components that will be used to generate values.
168 private final ValuePatternComponent[] components;
169
170
171
172 /**
173 * Creates a new value pattern from the provided string.
174 *
175 * @param s The string representation of the value pattern to create. It
176 * must not be {@code null}.
177 *
178 * @throws ParseException If the provided string cannot be parsed as a valid
179 * value pattern string.
180 */
181 public ValuePattern(final String s)
182 throws ParseException
183 {
184 this(s, null);
185 }
186
187
188
189 /**
190 * Creates a new value pattern from the provided string.
191 *
192 * @param s The string representation of the value pattern to create. It
193 * must not be {@code null}.
194 * @param r The seed to use for the random number generator. It may be
195 * {@code null} if no seed is required.
196 *
197 * @throws ParseException If the provided string cannot be parsed as a valid
198 * value pattern string.
199 */
200 public ValuePattern(final String s, final Long r)
201 throws ParseException
202 {
203 Validator.ensureNotNull(s);
204
205 pattern = s;
206 refLists = new ThreadLocal<ArrayList<String>>();
207 buffers = new ThreadLocal<StringBuilder>();
208
209 final AtomicBoolean hasRef = new AtomicBoolean(false);
210
211 final Random random;
212 if (r == null)
213 {
214 random = new Random();
215 }
216 else
217 {
218 random = new Random(r);
219 }
220
221 final ArrayList<ValuePatternComponent> l =
222 new ArrayList<ValuePatternComponent>(3);
223 parse(s, 0, l, random, hasRef);
224
225 hasBackReference = hasRef.get();
226 if (hasBackReference)
227 {
228 int availableReferences = 0;
229 for (final ValuePatternComponent c : l)
230 {
231 if (c instanceof BackReferenceValuePatternComponent)
232 {
233 final BackReferenceValuePatternComponent brvpc =
234 (BackReferenceValuePatternComponent) c;
235 if (brvpc.getIndex() > availableReferences)
236 {
237 throw new ParseException(
238 ERR_REF_VALUE_PATTERN_INVALID_INDEX.get(brvpc.getIndex()), 0);
239 }
240 }
241
242 if (c.supportsBackReference())
243 {
244 availableReferences++;
245 }
246 }
247 }
248
249 components = new ValuePatternComponent[l.size()];
250 l.toArray(components);
251 }
252
253
254
255 /**
256 * Recursively parses the provided string into a list of value pattern
257 * components.
258 *
259 * @param s The string representation of the value pattern to create. It
260 * may be a portion of the entire value pattern string.
261 * @param o The offset of the first character of the provided string in
262 * the full value pattern string.
263 * @param l The list into which the parsed components should be added.
264 * @param r The random number generator to use to seed random number
265 * generators used by components.
266 * @param ref A value that may be updated if the pattern contains any
267 * back-references.
268 *
269 * @throws ParseException If the provided string cannot be parsed as a valid
270 * value pattern string.
271 */
272 private static void parse(final String s, final int o,
273 final ArrayList<ValuePatternComponent> l,
274 final Random r, final AtomicBoolean ref)
275 throws ParseException
276 {
277 // Find the first occurrence of "[[". Parse the portion of the string
278 // before it, into the list, then add a string value pattern containing "[",
279 // then parse the portion of the string after it.
280 // First, parse out any occurrences of "[[" and replace them with string
281 // value pattern components containing only "[".
282 int pos = s.indexOf("[[");
283 if (pos >= 0)
284 {
285 if (pos > 0)
286 {
287 parse(s.substring(0, pos), o, l, r, ref);
288 }
289
290 l.add(new StringValuePatternComponent("["));
291
292 if (pos < (s.length() - 2))
293 {
294 parse(s.substring(pos+2), (o+pos+2), l, r, ref);
295 }
296 return;
297 }
298
299 // Find the first occurrence of "]]". Parse the portion of the string
300 // before it, into the list, then add a string value pattern containing "]",
301 // then parse the portion of the string after it.
302 pos = s.indexOf("]]");
303 if (pos >= 0)
304 {
305 if (pos > 0)
306 {
307 parse(s.substring(0, pos), o, l, r, ref);
308 }
309
310 l.add(new StringValuePatternComponent("]"));
311
312 if (pos < (s.length() - 2))
313 {
314 parse(s.substring(pos+2), (o+pos+2), l, r, ref);
315 }
316 return;
317 }
318
319 // Find the first occurrence of "[" and the corresponding "]". The part
320 // before that will be a string. Then parse out the numeric or URL
321 // component, and parse the rest of the string after the "]".
322 pos = s.indexOf('[');
323 if (pos >= 0)
324 {
325 final int closePos = s.indexOf(']');
326 if (closePos < 0)
327 {
328 throw new ParseException(
329 ERR_VALUE_PATTERN_UNMATCHED_OPEN.get(o+pos), (o+pos));
330 }
331 else if (closePos < pos)
332 {
333 throw new ParseException(
334 ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+closePos), (o+closePos));
335 }
336
337 if (pos > 0)
338 {
339 l.add(new StringValuePatternComponent(s.substring(0, pos)));
340 }
341
342 final String bracketedToken = s.substring(pos+1, closePos);
343 if (bracketedToken.startsWith("file:"))
344 {
345 final String path = bracketedToken.substring(5);
346 try
347 {
348 l.add(new FileValuePatternComponent(path, r.nextLong()));
349 }
350 catch (IOException ioe)
351 {
352 debugException(ioe);
353 throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get(
354 path, getExceptionMessage(ioe)), o+pos);
355 }
356 }
357 else if (bracketedToken.startsWith("http://"))
358 {
359 try
360 {
361 l.add(new HTTPValuePatternComponent(bracketedToken, r.nextLong()));
362 }
363 catch (IOException ioe)
364 {
365 debugException(ioe);
366 throw new ParseException(ERR_HTTP_VALUE_PATTERN_NOT_USABLE.get(
367 bracketedToken, getExceptionMessage(ioe)), o+pos);
368 }
369 }
370 else if (bracketedToken.startsWith("ref:"))
371 {
372 ref.set(true);
373
374 final String valueStr = bracketedToken.substring(4);
375 try
376 {
377 final int index = Integer.parseInt(valueStr);
378 if (index == 0)
379 {
380 throw new ParseException(ERR_REF_VALUE_PATTERN_ZERO_INDEX.get(),
381 (o+pos+4));
382 }
383 else if (index < 0)
384 {
385 throw new ParseException(
386 ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4));
387 }
388 else
389 {
390 l.add(new BackReferenceValuePatternComponent(index));
391 }
392 }
393 catch (final NumberFormatException nfe)
394 {
395 debugException(nfe);
396 throw new ParseException(
397 ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4));
398 }
399 }
400 else
401 {
402 l.add(parseNumericComponent(s.substring(pos+1, closePos), (o+pos+1),
403 r));
404 }
405
406 if (closePos < (s.length() - 1))
407 {
408 parse(s.substring(closePos+1), (o+closePos+1), l, r, ref);
409 }
410
411 return;
412 }
413
414
415 // If there are any occurrences of "]" without a corresponding open, then
416 // that's invalid.
417 pos = s.indexOf(']');
418 if (pos >= 0)
419 {
420 throw new ParseException(
421 ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+pos), (o+pos));
422 }
423
424 // There are no brackets, so it's just a static string.
425 l.add(new StringValuePatternComponent(s));
426 }
427
428
429
430 /**
431 * Parses the specified portion of the provided string as either a
432 * sequential or random numeric value pattern component.
433 *
434 * @param s The string to parse, not including the square brackets.
435 * @param o The offset in the overall value pattern string at which the
436 * provided substring begins.
437 * @param r The random number generator to use to seed random number
438 * generators used by components.
439 *
440 * @return The parsed numeric value pattern component.
441 *
442 * @throws ParseException If the specified substring cannot be parsed as a
443 *
444 */
445 private static ValuePatternComponent parseNumericComponent(final String s,
446 final int o,
447 final Random r)
448 throws ParseException
449 {
450 boolean delimiterFound = false;
451 boolean sequential = false;
452 int pos = 0;
453 long lowerBound = 0L;
454
455 lowerBoundLoop:
456 for ( ; pos < s.length(); pos++)
457 {
458 switch (s.charAt(pos))
459 {
460 case '0':
461 case '1':
462 case '2':
463 case '3':
464 case '4':
465 case '5':
466 case '6':
467 case '7':
468 case '8':
469 case '9':
470 // These are all acceptable.
471 break;
472
473 case '-':
474 if (pos == 0)
475 {
476 // This indicates that the value is negative.
477 break;
478 }
479 else
480 {
481 // This indicates the end of the lower bound.
482 delimiterFound = true;
483 sequential = false;
484
485 try
486 {
487 lowerBound = Long.parseLong(s.substring(0, pos));
488 }
489 catch (NumberFormatException nfe)
490 {
491 Debug.debugException(nfe);
492 throw new ParseException(
493 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
494 Long.MAX_VALUE),
495 (o-1));
496 }
497 pos++;
498 break lowerBoundLoop;
499 }
500
501 case ':':
502 delimiterFound = true;
503 sequential = true;
504
505 if (pos == 0)
506 {
507 throw new ParseException(
508 ERR_VALUE_PATTERN_EMPTY_LOWER_BOUND.get(o-1), (o-1));
509 }
510 else
511 {
512 try
513 {
514 lowerBound = Long.parseLong(s.substring(0, pos));
515 }
516 catch (NumberFormatException nfe)
517 {
518 Debug.debugException(nfe);
519 throw new ParseException(
520 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
521 Long.MAX_VALUE),
522 (o-1));
523 }
524 }
525 pos++;
526 break lowerBoundLoop;
527
528 default:
529 throw new ParseException(
530 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)),
531 (o+pos));
532 }
533 }
534
535 if (! delimiterFound)
536 {
537 throw new ParseException(ERR_VALUE_PATTERN_NO_DELIMITER.get(o-1), (o-1));
538 }
539
540 boolean hasIncrement = false;
541 int startPos = pos;
542 long upperBound = lowerBound;
543 long increment = 1L;
544 String formatString = null;
545
546 delimiterFound = false;
547
548 upperBoundLoop:
549 for ( ; pos < s.length(); pos++)
550 {
551 switch (s.charAt(pos))
552 {
553 case '0':
554 case '1':
555 case '2':
556 case '3':
557 case '4':
558 case '5':
559 case '6':
560 case '7':
561 case '8':
562 case '9':
563 // These are all acceptable.
564 break;
565
566 case '-':
567 if (pos == startPos)
568 {
569 // This indicates that the value is negative.
570 break;
571 }
572 else
573 {
574 throw new ParseException(
575 ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)),
576 (o+pos));
577 }
578
579 case 'x':
580 delimiterFound = true;
581 hasIncrement = true;
582
583 if (pos == startPos)
584 {
585 throw new ParseException(
586 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
587 }
588 else
589 {
590 try
591 {
592 upperBound = Long.parseLong(s.substring(startPos, pos));
593 }
594 catch (NumberFormatException nfe)
595 {
596 Debug.debugException(nfe);
597 throw new ParseException(
598 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
599 Long.MAX_VALUE),
600 (o-1));
601 }
602 }
603 pos++;
604 break upperBoundLoop;
605
606 case '%':
607 delimiterFound = true;
608 hasIncrement = false;
609
610 if (pos == startPos)
611 {
612 throw new ParseException(
613 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
614 }
615 else
616 {
617 try
618 {
619 upperBound = Long.parseLong(s.substring(startPos, pos));
620 }
621 catch (NumberFormatException nfe)
622 {
623 Debug.debugException(nfe);
624 throw new ParseException(
625 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
626 Long.MAX_VALUE),
627 (o-1));
628 }
629 }
630 pos++;
631 break upperBoundLoop;
632
633 default:
634 throw new ParseException(
635 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)),
636 (o+pos));
637 }
638 }
639
640 if (! delimiterFound)
641 {
642 if (pos == startPos)
643 {
644 throw new ParseException(
645 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
646 }
647
648 try
649 {
650 upperBound = Long.parseLong(s.substring(startPos, pos));
651 }
652 catch (NumberFormatException nfe)
653 {
654 Debug.debugException(nfe);
655 throw new ParseException(
656 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
657 Long.MAX_VALUE),
658 (o-1));
659 }
660
661 if (sequential)
662 {
663 return new SequentialValuePatternComponent(lowerBound, upperBound, 1,
664 null);
665 }
666 else
667 {
668 return new RandomValuePatternComponent(lowerBound, upperBound,
669 r.nextLong(), null);
670 }
671 }
672
673 if (hasIncrement)
674 {
675 delimiterFound = false;
676 startPos = pos;
677
678 incrementLoop:
679 for ( ; pos < s.length(); pos++)
680 {
681 switch (s.charAt(pos))
682 {
683 case '0':
684 case '1':
685 case '2':
686 case '3':
687 case '4':
688 case '5':
689 case '6':
690 case '7':
691 case '8':
692 case '9':
693 // These are all acceptable.
694 break;
695
696 case '-':
697 if (pos == startPos)
698 {
699 // This indicates that the value is negative.
700 break;
701 }
702 else
703 {
704 throw new ParseException(
705 ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)),
706 (o+pos));
707 }
708
709 case '%':
710 delimiterFound = true;
711 if (pos == startPos)
712 {
713 throw new ParseException(
714 ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1));
715 }
716 else if (pos == (s.length() - 1))
717 {
718 throw new ParseException(
719 ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1));
720 }
721 else
722 {
723 try
724 {
725 increment = Long.parseLong(s.substring(startPos, pos));
726 }
727 catch (NumberFormatException nfe)
728 {
729 Debug.debugException(nfe);
730 throw new ParseException(
731 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
732 Long.MAX_VALUE),
733 (o-1));
734 }
735
736 formatString = s.substring(pos+1);
737 }
738 break incrementLoop;
739
740 default:
741 throw new ParseException(
742 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos),
743 (o+pos)),
744 (o+pos));
745 }
746 }
747
748 if (! delimiterFound)
749 {
750 if (pos == startPos)
751 {
752 throw new ParseException(
753 ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1));
754 }
755
756 try
757 {
758 increment = Long.parseLong(s.substring(startPos, pos));
759 }
760 catch (NumberFormatException nfe)
761 {
762 Debug.debugException(nfe);
763 throw new ParseException(
764 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
765 Long.MAX_VALUE),
766 (o-1));
767 }
768 }
769 }
770 else
771 {
772 formatString = s.substring(pos);
773 if (formatString.length() == 0)
774 {
775 throw new ParseException(
776 ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1));
777 }
778 }
779
780 if (sequential)
781 {
782 return new SequentialValuePatternComponent(lowerBound, upperBound,
783 increment, formatString);
784 }
785 else
786 {
787 return new RandomValuePatternComponent(lowerBound, upperBound,
788 r.nextLong(), formatString);
789 }
790 }
791
792
793
794 /**
795 * Retrieves the next value generated from the value pattern.
796 *
797 * @return The next value generated from the value pattern.
798 */
799 public String nextValue()
800 {
801 StringBuilder buffer = buffers.get();
802 if (buffer == null)
803 {
804 buffer = new StringBuilder();
805 buffers.set(buffer);
806 }
807 else
808 {
809 buffer.setLength(0);
810 }
811
812 ArrayList<String> refList = refLists.get();
813 if (hasBackReference)
814 {
815 if (refList == null)
816 {
817 refList = new ArrayList<String>(10);
818 refLists.set(refList);
819 }
820 else
821 {
822 refList.clear();
823 }
824 }
825
826 for (final ValuePatternComponent c : components)
827 {
828 if (hasBackReference)
829 {
830 if (c instanceof BackReferenceValuePatternComponent)
831 {
832 final BackReferenceValuePatternComponent brvpc =
833 (BackReferenceValuePatternComponent) c;
834 final String value = refList.get(brvpc.getIndex() - 1);
835 buffer.append(value);
836 refList.add(value);
837 }
838 else if (c.supportsBackReference())
839 {
840 final int startPos = buffer.length();
841 c.append(buffer);
842 refList.add(buffer.substring(startPos));
843 }
844 else
845 {
846 c.append(buffer);
847 }
848 }
849 else
850 {
851 c.append(buffer);
852 }
853 }
854
855 return buffer.toString();
856 }
857
858
859
860 /**
861 * Retrieves a string representation of this value pattern, which will be the
862 * original pattern string used to create it.
863 *
864 * @return A string representation of this value pattern.
865 */
866 @Override()
867 public String toString()
868 {
869 return pattern;
870 }
871 }