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 */ 021package com.unboundid.util; 022 023 024 025import java.io.IOException; 026import java.io.Serializable; 027import java.text.ParseException; 028import java.util.ArrayList; 029import java.util.Random; 030import java.util.concurrent.atomic.AtomicBoolean; 031 032import static com.unboundid.util.Debug.*; 033import static com.unboundid.util.StaticUtils.*; 034import 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) 148public 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 488lowerBoundLoop: 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 581upperBoundLoop: 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 711incrementLoop: 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}