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