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