001 /*
002 * Copyright 2008-2015 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2015 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. To make it clear that the file
116 * contents are randomly accessed, you may use {@code randomfile} in place
117 * of {@code file}.</LI>
118 * <LI><CODE>[sequentialfile:///tmp/mydata.txt]</CODE> -- A URL reference that
119 * will cause lines from the specified local file, selected in sequential
120 * order, to be used in place of the bracketed range.</LI>
121 * <LI><CODE>[http://server.example.com/tmp/mydata.txt]</CODE> -- A URL
122 * reference that will cause randomly-selected lines from the specified
123 * remote HTTP-accessible file to be used in place of the bracketed
124 * range.</LI>
125 * </UL>
126 * <BR>
127 * Examples of full value pattern strings include:
128 * <UL>
129 * <LI><CODE>dc=example,dc=com</CODE> -- A value pattern containing only
130 * static text and no numeric components.</LI>
131 * <LI><CODE>[1000:9999]</CODE> -- A value pattern containing only a numeric
132 * component that will choose numbers in sequential order from 1000 to
133 * 9999.</LI>
134 * <LI><CODE>(uid=user.[1-1000000])</CODE> -- A value pattern that combines
135 * the static text "<CODE>(uid=user.</CODE>" with a value chosen randomly
136 * between one and one million, and another static text string of
137 * "<CODE>)</CODE>".</LI>
138 * <LI><CODE>uid=user.[1-1000000],ou=org[1-10],dc=example,dc=com</CODE> -- A
139 * value pattern containing two numeric components interspersed between
140 * three static text components.</LI>
141 * <LI><CODE>uid=user.[1-1000000],ou=org[ref:1],dc=example,dc=com</CODE> -- A
142 * value pattern in which the organization number will be the same as the
143 * randomly-selected user number.</LI>
144 * </UL>
145 */
146 @NotMutable()
147 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
148 public final class ValuePattern
149 implements Serializable
150 {
151 /**
152 * The serial version UID for this serializable class.
153 */
154 private static final long serialVersionUID = 4502778464751705304L;
155
156
157
158 // Indicates whether the provided value pattern includes one or more
159 // back-references.
160 private final boolean hasBackReference;
161
162 // The string that was originally used to create this value pattern.
163 private final String pattern;
164
165 // The thread-local array list that will be used to hold values for
166 // back-references.
167 private final ThreadLocal<ArrayList<String>> refLists;
168
169 // The thread-local string builder that will be used to build values.
170 private final ThreadLocal<StringBuilder> buffers;
171
172 // The value pattern components that will be used to generate values.
173 private final ValuePatternComponent[] components;
174
175
176
177 /**
178 * Creates a new value pattern from the provided string.
179 *
180 * @param s The string representation of the value pattern to create. It
181 * must not be {@code null}.
182 *
183 * @throws ParseException If the provided string cannot be parsed as a valid
184 * value pattern string.
185 */
186 public ValuePattern(final String s)
187 throws ParseException
188 {
189 this(s, null);
190 }
191
192
193
194 /**
195 * Creates a new value pattern from the provided string.
196 *
197 * @param s The string representation of the value pattern to create. It
198 * must not be {@code null}.
199 * @param r The seed to use for the random number generator. It may be
200 * {@code null} if no seed is required.
201 *
202 * @throws ParseException If the provided string cannot be parsed as a valid
203 * value pattern string.
204 */
205 public ValuePattern(final String s, final Long r)
206 throws ParseException
207 {
208 Validator.ensureNotNull(s);
209
210 pattern = s;
211 refLists = new ThreadLocal<ArrayList<String>>();
212 buffers = new ThreadLocal<StringBuilder>();
213
214 final AtomicBoolean hasRef = new AtomicBoolean(false);
215
216 final Random random;
217 if (r == null)
218 {
219 random = new Random();
220 }
221 else
222 {
223 random = new Random(r);
224 }
225
226 final ArrayList<ValuePatternComponent> l =
227 new ArrayList<ValuePatternComponent>(3);
228 parse(s, 0, l, random, hasRef);
229
230 hasBackReference = hasRef.get();
231 if (hasBackReference)
232 {
233 int availableReferences = 0;
234 for (final ValuePatternComponent c : l)
235 {
236 if (c instanceof BackReferenceValuePatternComponent)
237 {
238 final BackReferenceValuePatternComponent brvpc =
239 (BackReferenceValuePatternComponent) c;
240 if (brvpc.getIndex() > availableReferences)
241 {
242 throw new ParseException(
243 ERR_REF_VALUE_PATTERN_INVALID_INDEX.get(brvpc.getIndex()), 0);
244 }
245 }
246
247 if (c.supportsBackReference())
248 {
249 availableReferences++;
250 }
251 }
252 }
253
254 components = new ValuePatternComponent[l.size()];
255 l.toArray(components);
256 }
257
258
259
260 /**
261 * Recursively parses the provided string into a list of value pattern
262 * components.
263 *
264 * @param s The string representation of the value pattern to create. It
265 * may be a portion of the entire value pattern string.
266 * @param o The offset of the first character of the provided string in
267 * the full value pattern string.
268 * @param l The list into which the parsed components should be added.
269 * @param r The random number generator to use to seed random number
270 * generators used by components.
271 * @param ref A value that may be updated if the pattern contains any
272 * back-references.
273 *
274 * @throws ParseException If the provided string cannot be parsed as a valid
275 * value pattern string.
276 */
277 private static void parse(final String s, final int o,
278 final ArrayList<ValuePatternComponent> l,
279 final Random r, final AtomicBoolean ref)
280 throws ParseException
281 {
282 // Find the first occurrence of "[[". Parse the portion of the string
283 // before it, into the list, then add a string value pattern containing "[",
284 // then parse the portion of the string after it.
285 // First, parse out any occurrences of "[[" and replace them with string
286 // value pattern components containing only "[".
287 int pos = s.indexOf("[[");
288 if (pos >= 0)
289 {
290 if (pos > 0)
291 {
292 parse(s.substring(0, pos), o, l, r, ref);
293 }
294
295 l.add(new StringValuePatternComponent("["));
296
297 if (pos < (s.length() - 2))
298 {
299 parse(s.substring(pos+2), (o+pos+2), l, r, ref);
300 }
301 return;
302 }
303
304 // Find the first occurrence of "]]". Parse the portion of the string
305 // before it, into the list, then add a string value pattern containing "]",
306 // then parse the portion of the string after it.
307 pos = s.indexOf("]]");
308 if (pos >= 0)
309 {
310 if (pos > 0)
311 {
312 parse(s.substring(0, pos), o, l, r, ref);
313 }
314
315 l.add(new StringValuePatternComponent("]"));
316
317 if (pos < (s.length() - 2))
318 {
319 parse(s.substring(pos+2), (o+pos+2), l, r, ref);
320 }
321 return;
322 }
323
324 // Find the first occurrence of "[" and the corresponding "]". The part
325 // before that will be a string. Then parse out the numeric or URL
326 // component, and parse the rest of the string after the "]".
327 pos = s.indexOf('[');
328 if (pos >= 0)
329 {
330 final int closePos = s.indexOf(']');
331 if (closePos < 0)
332 {
333 throw new ParseException(
334 ERR_VALUE_PATTERN_UNMATCHED_OPEN.get(o+pos), (o+pos));
335 }
336 else if (closePos < pos)
337 {
338 throw new ParseException(
339 ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+closePos), (o+closePos));
340 }
341
342 if (pos > 0)
343 {
344 l.add(new StringValuePatternComponent(s.substring(0, pos)));
345 }
346
347 final String bracketedToken = s.substring(pos+1, closePos);
348 if (bracketedToken.startsWith("file:"))
349 {
350 final String path = bracketedToken.substring(5);
351 try
352 {
353 l.add(new FileValuePatternComponent(path, r.nextLong(), false));
354 }
355 catch (IOException ioe)
356 {
357 debugException(ioe);
358 throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get(
359 path, getExceptionMessage(ioe)), o+pos);
360 }
361 }
362 else if (bracketedToken.startsWith("randomfile:"))
363 {
364 final String path = bracketedToken.substring(11);
365 try
366 {
367 l.add(new FileValuePatternComponent(path, r.nextLong(), false));
368 }
369 catch (IOException ioe)
370 {
371 debugException(ioe);
372 throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get(
373 path, getExceptionMessage(ioe)), o+pos);
374 }
375 }
376 else if (bracketedToken.startsWith("sequentialfile:"))
377 {
378 final String path = bracketedToken.substring(15);
379 try
380 {
381 l.add(new FileValuePatternComponent(path, r.nextLong(), true));
382 }
383 catch (IOException ioe)
384 {
385 debugException(ioe);
386 throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get(
387 path, getExceptionMessage(ioe)), o+pos);
388 }
389 }
390 else if (bracketedToken.startsWith("http://"))
391 {
392 try
393 {
394 l.add(new HTTPValuePatternComponent(bracketedToken, r.nextLong()));
395 }
396 catch (IOException ioe)
397 {
398 debugException(ioe);
399 throw new ParseException(ERR_HTTP_VALUE_PATTERN_NOT_USABLE.get(
400 bracketedToken, getExceptionMessage(ioe)), o+pos);
401 }
402 }
403 else if (bracketedToken.startsWith("ref:"))
404 {
405 ref.set(true);
406
407 final String valueStr = bracketedToken.substring(4);
408 try
409 {
410 final int index = Integer.parseInt(valueStr);
411 if (index == 0)
412 {
413 throw new ParseException(ERR_REF_VALUE_PATTERN_ZERO_INDEX.get(),
414 (o+pos+4));
415 }
416 else if (index < 0)
417 {
418 throw new ParseException(
419 ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4));
420 }
421 else
422 {
423 l.add(new BackReferenceValuePatternComponent(index));
424 }
425 }
426 catch (final NumberFormatException nfe)
427 {
428 debugException(nfe);
429 throw new ParseException(
430 ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4));
431 }
432 }
433 else
434 {
435 l.add(parseNumericComponent(s.substring(pos+1, closePos), (o+pos+1),
436 r));
437 }
438
439 if (closePos < (s.length() - 1))
440 {
441 parse(s.substring(closePos+1), (o+closePos+1), l, r, ref);
442 }
443
444 return;
445 }
446
447
448 // If there are any occurrences of "]" without a corresponding open, then
449 // that's invalid.
450 pos = s.indexOf(']');
451 if (pos >= 0)
452 {
453 throw new ParseException(
454 ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+pos), (o+pos));
455 }
456
457 // There are no brackets, so it's just a static string.
458 l.add(new StringValuePatternComponent(s));
459 }
460
461
462
463 /**
464 * Parses the specified portion of the provided string as either a
465 * sequential or random numeric value pattern component.
466 *
467 * @param s The string to parse, not including the square brackets.
468 * @param o The offset in the overall value pattern string at which the
469 * provided substring begins.
470 * @param r The random number generator to use to seed random number
471 * generators used by components.
472 *
473 * @return The parsed numeric value pattern component.
474 *
475 * @throws ParseException If the specified substring cannot be parsed as a
476 *
477 */
478 private static ValuePatternComponent parseNumericComponent(final String s,
479 final int o,
480 final Random r)
481 throws ParseException
482 {
483 boolean delimiterFound = false;
484 boolean sequential = false;
485 int pos = 0;
486 long lowerBound = 0L;
487
488 lowerBoundLoop:
489 for ( ; pos < s.length(); pos++)
490 {
491 switch (s.charAt(pos))
492 {
493 case '0':
494 case '1':
495 case '2':
496 case '3':
497 case '4':
498 case '5':
499 case '6':
500 case '7':
501 case '8':
502 case '9':
503 // These are all acceptable.
504 break;
505
506 case '-':
507 if (pos == 0)
508 {
509 // This indicates that the value is negative.
510 break;
511 }
512 else
513 {
514 // This indicates the end of the lower bound.
515 delimiterFound = true;
516 sequential = false;
517
518 try
519 {
520 lowerBound = Long.parseLong(s.substring(0, pos));
521 }
522 catch (NumberFormatException nfe)
523 {
524 Debug.debugException(nfe);
525 throw new ParseException(
526 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
527 Long.MAX_VALUE),
528 (o-1));
529 }
530 pos++;
531 break lowerBoundLoop;
532 }
533
534 case ':':
535 delimiterFound = true;
536 sequential = true;
537
538 if (pos == 0)
539 {
540 throw new ParseException(
541 ERR_VALUE_PATTERN_EMPTY_LOWER_BOUND.get(o-1), (o-1));
542 }
543 else
544 {
545 try
546 {
547 lowerBound = Long.parseLong(s.substring(0, pos));
548 }
549 catch (NumberFormatException nfe)
550 {
551 Debug.debugException(nfe);
552 throw new ParseException(
553 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
554 Long.MAX_VALUE),
555 (o-1));
556 }
557 }
558 pos++;
559 break lowerBoundLoop;
560
561 default:
562 throw new ParseException(
563 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)),
564 (o+pos));
565 }
566 }
567
568 if (! delimiterFound)
569 {
570 throw new ParseException(ERR_VALUE_PATTERN_NO_DELIMITER.get(o-1), (o-1));
571 }
572
573 boolean hasIncrement = false;
574 int startPos = pos;
575 long upperBound = lowerBound;
576 long increment = 1L;
577 String formatString = null;
578
579 delimiterFound = false;
580
581 upperBoundLoop:
582 for ( ; pos < s.length(); pos++)
583 {
584 switch (s.charAt(pos))
585 {
586 case '0':
587 case '1':
588 case '2':
589 case '3':
590 case '4':
591 case '5':
592 case '6':
593 case '7':
594 case '8':
595 case '9':
596 // These are all acceptable.
597 break;
598
599 case '-':
600 if (pos == startPos)
601 {
602 // This indicates that the value is negative.
603 break;
604 }
605 else
606 {
607 throw new ParseException(
608 ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)),
609 (o+pos));
610 }
611
612 case 'x':
613 delimiterFound = true;
614 hasIncrement = true;
615
616 if (pos == startPos)
617 {
618 throw new ParseException(
619 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
620 }
621 else
622 {
623 try
624 {
625 upperBound = Long.parseLong(s.substring(startPos, pos));
626 }
627 catch (NumberFormatException nfe)
628 {
629 Debug.debugException(nfe);
630 throw new ParseException(
631 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
632 Long.MAX_VALUE),
633 (o-1));
634 }
635 }
636 pos++;
637 break upperBoundLoop;
638
639 case '%':
640 delimiterFound = true;
641 hasIncrement = false;
642
643 if (pos == startPos)
644 {
645 throw new ParseException(
646 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
647 }
648 else
649 {
650 try
651 {
652 upperBound = Long.parseLong(s.substring(startPos, pos));
653 }
654 catch (NumberFormatException nfe)
655 {
656 Debug.debugException(nfe);
657 throw new ParseException(
658 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
659 Long.MAX_VALUE),
660 (o-1));
661 }
662 }
663 pos++;
664 break upperBoundLoop;
665
666 default:
667 throw new ParseException(
668 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)),
669 (o+pos));
670 }
671 }
672
673 if (! delimiterFound)
674 {
675 if (pos == startPos)
676 {
677 throw new ParseException(
678 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
679 }
680
681 try
682 {
683 upperBound = Long.parseLong(s.substring(startPos, pos));
684 }
685 catch (NumberFormatException nfe)
686 {
687 Debug.debugException(nfe);
688 throw new ParseException(
689 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
690 Long.MAX_VALUE),
691 (o-1));
692 }
693
694 if (sequential)
695 {
696 return new SequentialValuePatternComponent(lowerBound, upperBound, 1,
697 null);
698 }
699 else
700 {
701 return new RandomValuePatternComponent(lowerBound, upperBound,
702 r.nextLong(), null);
703 }
704 }
705
706 if (hasIncrement)
707 {
708 delimiterFound = false;
709 startPos = pos;
710
711 incrementLoop:
712 for ( ; pos < s.length(); pos++)
713 {
714 switch (s.charAt(pos))
715 {
716 case '0':
717 case '1':
718 case '2':
719 case '3':
720 case '4':
721 case '5':
722 case '6':
723 case '7':
724 case '8':
725 case '9':
726 // These are all acceptable.
727 break;
728
729 case '-':
730 if (pos == startPos)
731 {
732 // This indicates that the value is negative.
733 break;
734 }
735 else
736 {
737 throw new ParseException(
738 ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)),
739 (o+pos));
740 }
741
742 case '%':
743 delimiterFound = true;
744 if (pos == startPos)
745 {
746 throw new ParseException(
747 ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1));
748 }
749 else if (pos == (s.length() - 1))
750 {
751 throw new ParseException(
752 ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1));
753 }
754 else
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 formatString = s.substring(pos+1);
770 }
771 break incrementLoop;
772
773 default:
774 throw new ParseException(
775 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos),
776 (o+pos)),
777 (o+pos));
778 }
779 }
780
781 if (! delimiterFound)
782 {
783 if (pos == startPos)
784 {
785 throw new ParseException(
786 ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1));
787 }
788
789 try
790 {
791 increment = Long.parseLong(s.substring(startPos, pos));
792 }
793 catch (NumberFormatException nfe)
794 {
795 Debug.debugException(nfe);
796 throw new ParseException(
797 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
798 Long.MAX_VALUE),
799 (o-1));
800 }
801 }
802 }
803 else
804 {
805 formatString = s.substring(pos);
806 if (formatString.length() == 0)
807 {
808 throw new ParseException(
809 ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1));
810 }
811 }
812
813 if (sequential)
814 {
815 return new SequentialValuePatternComponent(lowerBound, upperBound,
816 increment, formatString);
817 }
818 else
819 {
820 return new RandomValuePatternComponent(lowerBound, upperBound,
821 r.nextLong(), formatString);
822 }
823 }
824
825
826
827 /**
828 * Retrieves the next value generated from the value pattern.
829 *
830 * @return The next value generated from the value pattern.
831 */
832 public String nextValue()
833 {
834 StringBuilder buffer = buffers.get();
835 if (buffer == null)
836 {
837 buffer = new StringBuilder();
838 buffers.set(buffer);
839 }
840 else
841 {
842 buffer.setLength(0);
843 }
844
845 ArrayList<String> refList = refLists.get();
846 if (hasBackReference)
847 {
848 if (refList == null)
849 {
850 refList = new ArrayList<String>(10);
851 refLists.set(refList);
852 }
853 else
854 {
855 refList.clear();
856 }
857 }
858
859 for (final ValuePatternComponent c : components)
860 {
861 if (hasBackReference)
862 {
863 if (c instanceof BackReferenceValuePatternComponent)
864 {
865 final BackReferenceValuePatternComponent brvpc =
866 (BackReferenceValuePatternComponent) c;
867 final String value = refList.get(brvpc.getIndex() - 1);
868 buffer.append(value);
869 refList.add(value);
870 }
871 else if (c.supportsBackReference())
872 {
873 final int startPos = buffer.length();
874 c.append(buffer);
875 refList.add(buffer.substring(startPos));
876 }
877 else
878 {
879 c.append(buffer);
880 }
881 }
882 else
883 {
884 c.append(buffer);
885 }
886 }
887
888 return buffer.toString();
889 }
890
891
892
893 /**
894 * Retrieves a string representation of this value pattern, which will be the
895 * original pattern string used to create it.
896 *
897 * @return A string representation of this value pattern.
898 */
899 @Override()
900 public String toString()
901 {
902 return pattern;
903 }
904 }