001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2024 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-2024 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 @NotNull 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 @NotNull private final String pattern; 250 251 // The thread-local array list that will be used to hold values for 252 // back-references. 253 @NotNull private final ThreadLocal<ArrayList<String>> refLists; 254 255 // The thread-local string builder that will be used to build values. 256 @NotNull private final ThreadLocal<StringBuilder> buffers; 257 258 // The value pattern components that will be used to generate values. 259 @NotNull 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(@NotNull 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(@NotNull final String s, @Nullable 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(@NotNull final String s, final int o, 363 @NotNull final ArrayList<ValuePatternComponent> l, 364 @NotNull final Random r, 365 @NotNull final AtomicBoolean ref) 366 throws ParseException 367 { 368 // Find the first occurrence of "[[". Parse the portion of the string 369 // before it, into the list, then add a string value pattern containing "[", 370 // then parse the portion of the string after it. 371 // First, parse out any occurrences of "[[" and replace them with string 372 // value pattern components containing only "[". 373 int pos = s.indexOf("[["); 374 if (pos >= 0) 375 { 376 if (pos > 0) 377 { 378 parse(s.substring(0, pos), o, l, r, ref); 379 } 380 381 l.add(new StringValuePatternComponent("[")); 382 383 if (pos < (s.length() - 2)) 384 { 385 parse(s.substring(pos+2), (o+pos+2), l, r, ref); 386 } 387 return; 388 } 389 390 // Find the first occurrence of "]]". Parse the portion of the string 391 // before it, into the list, then add a string value pattern containing "]", 392 // then parse the portion of the string after it. 393 pos = s.indexOf("]]"); 394 if (pos >= 0) 395 { 396 if (pos > 0) 397 { 398 parse(s.substring(0, pos), o, l, r, ref); 399 } 400 401 l.add(new StringValuePatternComponent("]")); 402 403 if (pos < (s.length() - 2)) 404 { 405 parse(s.substring(pos+2), (o+pos+2), l, r, ref); 406 } 407 return; 408 } 409 410 // Find the first occurrence of "[" and the corresponding "]". The part 411 // before that will be a string. Then parse out the numeric or URL 412 // component, and parse the rest of the string after the "]". 413 pos = s.indexOf('['); 414 if (pos >= 0) 415 { 416 final int closePos = s.indexOf(']'); 417 if (closePos < 0) 418 { 419 throw new ParseException( 420 ERR_VALUE_PATTERN_UNMATCHED_OPEN.get(o+pos), (o+pos)); 421 } 422 else if (closePos < pos) 423 { 424 throw new ParseException( 425 ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+closePos), (o+closePos)); 426 } 427 428 if (pos > 0) 429 { 430 l.add(new StringValuePatternComponent(s.substring(0, pos))); 431 } 432 433 final String bracketedToken = s.substring(pos+1, closePos); 434 if (bracketedToken.startsWith("random:")) 435 { 436 l.add(new RandomCharactersValuePatternComponent(bracketedToken, 437 r.nextLong())); 438 } 439 else if (bracketedToken.startsWith("file:")) 440 { 441 final String path = bracketedToken.substring(5); 442 try 443 { 444 l.add(new FileValuePatternComponent(path, r.nextLong(), false)); 445 } 446 catch (final IOException ioe) 447 { 448 Debug.debugException(ioe); 449 throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get( 450 path, StaticUtils.getExceptionMessage(ioe)), o+pos); 451 } 452 } 453 else if (bracketedToken.startsWith("randomfile:")) 454 { 455 final String path = bracketedToken.substring(11); 456 try 457 { 458 l.add(new FileValuePatternComponent(path, r.nextLong(), false)); 459 } 460 catch (final IOException ioe) 461 { 462 Debug.debugException(ioe); 463 throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get( 464 path, StaticUtils.getExceptionMessage(ioe)), o+pos); 465 } 466 } 467 else if (bracketedToken.startsWith("sequentialfile:")) 468 { 469 final String path = bracketedToken.substring(15); 470 try 471 { 472 l.add(new FileValuePatternComponent(path, r.nextLong(), true)); 473 } 474 catch (final IOException ioe) 475 { 476 Debug.debugException(ioe); 477 throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get( 478 path, StaticUtils.getExceptionMessage(ioe)), o+pos); 479 } 480 } 481 else if (bracketedToken.startsWith("streamfile:")) 482 { 483 final String path = bracketedToken.substring(11); 484 try 485 { 486 l.add(new StreamFileValuePatternComponent(path)); 487 } 488 catch (final IOException ioe) 489 { 490 Debug.debugException(ioe); 491 throw new ParseException(ERR_STREAM_FILE_VALUE_PATTERN_NOT_USABLE.get( 492 path, StaticUtils.getExceptionMessage(ioe)), o+pos); 493 } 494 } 495 else if (bracketedToken.startsWith("http://")) 496 { 497 try 498 { 499 l.add(new HTTPValuePatternComponent(bracketedToken, r.nextLong())); 500 } 501 catch (final IOException ioe) 502 { 503 Debug.debugException(ioe); 504 throw new ParseException(ERR_HTTP_VALUE_PATTERN_NOT_USABLE.get( 505 bracketedToken, StaticUtils.getExceptionMessage(ioe)), o+pos); 506 } 507 } 508 else if (bracketedToken.startsWith("timestamp")) 509 { 510 l.add(new TimestampValuePatternComponent(bracketedToken, 511 r.nextLong())); 512 } 513 else if (bracketedToken.equals("uuid")) 514 { 515 l.add(new UUIDValuePatternComponent()); 516 } 517 else if (bracketedToken.startsWith("ref:")) 518 { 519 ref.set(true); 520 521 final String valueStr = bracketedToken.substring(4); 522 try 523 { 524 final int index = Integer.parseInt(valueStr); 525 if (index == 0) 526 { 527 throw new ParseException(ERR_REF_VALUE_PATTERN_ZERO_INDEX.get(), 528 (o+pos+4)); 529 } 530 else if (index < 0) 531 { 532 throw new ParseException( 533 ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4)); 534 } 535 else 536 { 537 l.add(new BackReferenceValuePatternComponent(index)); 538 } 539 } 540 catch (final NumberFormatException nfe) 541 { 542 Debug.debugException(nfe); 543 throw new ParseException( 544 ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4)); 545 } 546 } 547 else 548 { 549 l.add(parseNumericComponent(s.substring(pos+1, closePos), (o+pos+1), 550 r)); 551 } 552 553 if (closePos < (s.length() - 1)) 554 { 555 parse(s.substring(closePos+1), (o+closePos+1), l, r, ref); 556 } 557 558 return; 559 } 560 561 562 // If there are any occurrences of "]" without a corresponding open, then 563 // that's invalid. 564 pos = s.indexOf(']'); 565 if (pos >= 0) 566 { 567 throw new ParseException( 568 ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+pos), (o+pos)); 569 } 570 571 // There are no brackets, so it's just a static string. 572 l.add(new StringValuePatternComponent(s)); 573 } 574 575 576 577 /** 578 * Parses the specified portion of the provided string as either a 579 * sequential or random numeric value pattern component. 580 * 581 * @param s The string to parse, not including the square brackets. 582 * @param o The offset in the overall value pattern string at which the 583 * provided substring begins. 584 * @param r The random number generator to use to seed random number 585 * generators used by components. 586 * 587 * @return The parsed numeric value pattern component. 588 * 589 * @throws ParseException If the specified substring cannot be parsed as a 590 * 591 */ 592 @NotNull() 593 private static ValuePatternComponent parseNumericComponent( 594 @NotNull final String s,final int o, @NotNull final Random r) 595 throws ParseException 596 { 597 boolean delimiterFound = false; 598 boolean sequential = false; 599 int pos = 0; 600 long lowerBound = 0L; 601 602lowerBoundLoop: 603 for ( ; pos < s.length(); pos++) 604 { 605 switch (s.charAt(pos)) 606 { 607 case '0': 608 case '1': 609 case '2': 610 case '3': 611 case '4': 612 case '5': 613 case '6': 614 case '7': 615 case '8': 616 case '9': 617 // These are all acceptable. 618 break; 619 620 case '-': 621 if (pos == 0) 622 { 623 // This indicates that the value is negative. 624 break; 625 } 626 else 627 { 628 // This indicates the end of the lower bound. 629 delimiterFound = true; 630 sequential = false; 631 632 try 633 { 634 lowerBound = Long.parseLong(s.substring(0, pos)); 635 } 636 catch (final NumberFormatException nfe) 637 { 638 Debug.debugException(nfe); 639 throw new ParseException( 640 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE, 641 Long.MAX_VALUE), 642 (o-1)); 643 } 644 pos++; 645 break lowerBoundLoop; 646 } 647 648 case ':': 649 delimiterFound = true; 650 sequential = true; 651 652 if (pos == 0) 653 { 654 throw new ParseException( 655 ERR_VALUE_PATTERN_EMPTY_LOWER_BOUND.get(o-1), (o-1)); 656 } 657 else 658 { 659 try 660 { 661 lowerBound = Long.parseLong(s.substring(0, pos)); 662 } 663 catch (final NumberFormatException nfe) 664 { 665 Debug.debugException(nfe); 666 throw new ParseException( 667 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE, 668 Long.MAX_VALUE), 669 (o-1)); 670 } 671 } 672 pos++; 673 break lowerBoundLoop; 674 675 default: 676 throw new ParseException( 677 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)), 678 (o+pos)); 679 } 680 } 681 682 if (! delimiterFound) 683 { 684 throw new ParseException(ERR_VALUE_PATTERN_NO_DELIMITER.get(o-1), (o-1)); 685 } 686 687 boolean hasIncrement = false; 688 int startPos = pos; 689 long upperBound = lowerBound; 690 long increment = 1L; 691 String formatString = null; 692 693 delimiterFound = false; 694 695upperBoundLoop: 696 for ( ; pos < s.length(); pos++) 697 { 698 switch (s.charAt(pos)) 699 { 700 case '0': 701 case '1': 702 case '2': 703 case '3': 704 case '4': 705 case '5': 706 case '6': 707 case '7': 708 case '8': 709 case '9': 710 // These are all acceptable. 711 break; 712 713 case '-': 714 if (pos == startPos) 715 { 716 // This indicates that the value is negative. 717 break; 718 } 719 else 720 { 721 throw new ParseException( 722 ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)), 723 (o+pos)); 724 } 725 726 case 'x': 727 delimiterFound = true; 728 hasIncrement = true; 729 730 if (pos == startPos) 731 { 732 throw new ParseException( 733 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1)); 734 } 735 else 736 { 737 try 738 { 739 upperBound = Long.parseLong(s.substring(startPos, pos)); 740 } 741 catch (final NumberFormatException nfe) 742 { 743 Debug.debugException(nfe); 744 throw new ParseException( 745 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE, 746 Long.MAX_VALUE), 747 (o-1)); 748 } 749 } 750 pos++; 751 break upperBoundLoop; 752 753 case '%': 754 delimiterFound = true; 755 hasIncrement = false; 756 757 if (pos == startPos) 758 { 759 throw new ParseException( 760 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1)); 761 } 762 else 763 { 764 try 765 { 766 upperBound = Long.parseLong(s.substring(startPos, pos)); 767 } 768 catch (final NumberFormatException nfe) 769 { 770 Debug.debugException(nfe); 771 throw new ParseException( 772 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE, 773 Long.MAX_VALUE), 774 (o-1)); 775 } 776 } 777 pos++; 778 break upperBoundLoop; 779 780 default: 781 throw new ParseException( 782 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)), 783 (o+pos)); 784 } 785 } 786 787 if (! delimiterFound) 788 { 789 if (pos == startPos) 790 { 791 throw new ParseException( 792 ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1)); 793 } 794 795 try 796 { 797 upperBound = Long.parseLong(s.substring(startPos, pos)); 798 } 799 catch (final NumberFormatException nfe) 800 { 801 Debug.debugException(nfe); 802 throw new ParseException( 803 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE, 804 Long.MAX_VALUE), 805 (o-1)); 806 } 807 808 if (sequential) 809 { 810 return new SequentialValuePatternComponent(lowerBound, upperBound, 1, 811 null); 812 } 813 else 814 { 815 return new RandomValuePatternComponent(lowerBound, upperBound, 816 r.nextLong(), null); 817 } 818 } 819 820 if (hasIncrement) 821 { 822 delimiterFound = false; 823 startPos = pos; 824 825incrementLoop: 826 for ( ; pos < s.length(); pos++) 827 { 828 switch (s.charAt(pos)) 829 { 830 case '0': 831 case '1': 832 case '2': 833 case '3': 834 case '4': 835 case '5': 836 case '6': 837 case '7': 838 case '8': 839 case '9': 840 // These are all acceptable. 841 break; 842 843 case '-': 844 if (pos == startPos) 845 { 846 // This indicates that the value is negative. 847 break; 848 } 849 else 850 { 851 throw new ParseException( 852 ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)), 853 (o+pos)); 854 } 855 856 case '%': 857 delimiterFound = true; 858 if (pos == startPos) 859 { 860 throw new ParseException( 861 ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1)); 862 } 863 else if (pos == (s.length() - 1)) 864 { 865 throw new ParseException( 866 ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1)); 867 } 868 else 869 { 870 try 871 { 872 increment = Long.parseLong(s.substring(startPos, pos)); 873 } 874 catch (final NumberFormatException nfe) 875 { 876 Debug.debugException(nfe); 877 throw new ParseException( 878 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE, 879 Long.MAX_VALUE), 880 (o-1)); 881 } 882 883 formatString = s.substring(pos+1); 884 } 885 break incrementLoop; 886 887 default: 888 throw new ParseException( 889 ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), 890 (o+pos)), 891 (o+pos)); 892 } 893 } 894 895 if (! delimiterFound) 896 { 897 if (pos == startPos) 898 { 899 throw new ParseException( 900 ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1)); 901 } 902 903 try 904 { 905 increment = Long.parseLong(s.substring(startPos, pos)); 906 } 907 catch (final NumberFormatException nfe) 908 { 909 Debug.debugException(nfe); 910 throw new ParseException( 911 ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE, 912 Long.MAX_VALUE), 913 (o-1)); 914 } 915 } 916 } 917 else 918 { 919 formatString = s.substring(pos); 920 if (formatString.length() == 0) 921 { 922 throw new ParseException( 923 ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1)); 924 } 925 } 926 927 if (sequential) 928 { 929 return new SequentialValuePatternComponent(lowerBound, upperBound, 930 increment, formatString); 931 } 932 else 933 { 934 return new RandomValuePatternComponent(lowerBound, upperBound, 935 r.nextLong(), formatString); 936 } 937 } 938 939 940 941 /** 942 * Retrieves the next value generated from the value pattern. 943 * 944 * @return The next value generated from the value pattern. 945 */ 946 @NotNull() 947 public String nextValue() 948 { 949 StringBuilder buffer = buffers.get(); 950 if (buffer == null) 951 { 952 buffer = new StringBuilder(); 953 buffers.set(buffer); 954 } 955 else 956 { 957 buffer.setLength(0); 958 } 959 960 ArrayList<String> refList = refLists.get(); 961 if (hasBackReference) 962 { 963 if (refList == null) 964 { 965 refList = new ArrayList<>(10); 966 refLists.set(refList); 967 } 968 else 969 { 970 refList.clear(); 971 } 972 } 973 974 for (final ValuePatternComponent c : components) 975 { 976 if (hasBackReference) 977 { 978 if (c instanceof BackReferenceValuePatternComponent) 979 { 980 final BackReferenceValuePatternComponent brvpc = 981 (BackReferenceValuePatternComponent) c; 982 final String value = refList.get(brvpc.getIndex() - 1); 983 buffer.append(value); 984 refList.add(value); 985 } 986 else if (c.supportsBackReference()) 987 { 988 final int startPos = buffer.length(); 989 c.append(buffer); 990 refList.add(buffer.substring(startPos)); 991 } 992 else 993 { 994 c.append(buffer); 995 } 996 } 997 else 998 { 999 c.append(buffer); 1000 } 1001 } 1002 1003 return buffer.toString(); 1004 } 1005 1006 1007 1008 /** 1009 * Retrieves a string representation of this value pattern, which will be the 1010 * original pattern string used to create it. 1011 * 1012 * @return A string representation of this value pattern. 1013 */ 1014 @Override() 1015 @NotNull() 1016 public String toString() 1017 { 1018 return pattern; 1019 } 1020}