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