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}