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