com.unboundid.util.ValuePattern Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unboundid-ldapsdk Show documentation
Show all versions of unboundid-ldapsdk Show documentation
The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use
Java API for communicating with LDAP directory servers and performing
related tasks like reading and writing LDIF, encoding and decoding data
using base64 and ASN.1 BER, and performing secure communication. This
package contains the Standard Edition of the LDAP SDK, which is a
complete, general-purpose library for communicating with LDAPv3 directory
servers.
/*
* Copyright 2008-2022 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2008-2022 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2008-2022 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.util;
import java.io.IOException;
import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.unboundid.util.UtilityMessages.*;
/**
* This class provides a method for generating a string value comprised of zero
* or more components. The components may be any combination of zero or more
* strings, sequential numeric ranges, and random numeric ranges. These
* components should be formatted as follows:
*
* - Strings are simply any kind of static text that will be used as-is
* without any modification, except that double opening or closing square
* brackets (i.e., "
[[
" or "]]
") will be
* replaced with single opening or closing square brackets to distinguish
* them from the square brackets used in numeric ranges or URL
* references.
* - Sequential numeric ranges consist of an opening square bracket, a
* numeric value to be used as the lower bound for the range, a colon, a
* second numeric value to be used as the upper bound for the range, an
* optional '
x
' character followed by a numeric value to be
* used as the increment, an optional '%
' character followed
* by a format string as allowed by the {@link java.text.DecimalFormat}
* class to define how the resulting value should be formatted, and a
* closing square bracket to indicate the end of the range.
* - Random numeric ranges consist of an opening square bracket, a
* numeric value to be used as the lower bound for the range, a dash, a
* second numeric value to be used as the upper bound for the range, an
* optional '
%
' character followed by a format string as
* allowed by the {@link java.text.DecimalFormat} class to define how the
* resulting value should be formatted, and a closing square bracket to
* indicate the end of the range.
* - Randomly character ranges consist of an opening square bracket, the
* word "random", a colon, the number of random characters to generate,
* another colon, the set of characters to include, and a closing square
* bracket. For example, "[random:4:0123456789abcdef]" will generate a
* string of four randomly selected characters from the set of hexadecimal
* digits. The final colon and character set may be omitted to use the
* set of lowercase alphabetic characters.
* - Strings read from a file specified by a given URL. That file may be
* contained on the local filesystem (using a URL like
* "file:///tmp/mydata.txt") or read from a remote server via HTTP (using
* a URL like "http://server.example.com/mydata.txt"). In either case,
* the provided URL must not contain a closing square bracket character.
* If this option is used, then that file must contain one value per line,
* and its contents will be read into memory and values from the file will
* be selected in a random order and used in place of the bracketed
* URL. Alternately, a local file may be read in sequential order by
* using "sequentialfile:" or "streamfile:" instead of "file:"; the former
* will load the entire file into memory while the latter will only hold
* a small amount of data in memory at any time.
* - Timestamps in a specified format. A pattern of just "[timestamp]" will
* be replaced with the current time, with millisecond precision, in the
* generalized time format (for example, "20180102030405.678Z"). A value
* A value of "[timestamp:format=XXX]" will be replaced with the current
* time in the specified format, where the format value can be one of
* "milliseconds" for the number of milliseconds since the epoch (January
* 1, 1970 at midnight UTC), "seconds" for the number of seconds since the
* epoch, or any value supported by Java's {@code SimpleDateFormat} class.
* A pattern of "[timestamp:min=XXX:max=XXX]" will be replaced with a
* randomly selected timestamp in generalized time format between the
* given minimum and maximum timestamps (inclusive), which must be in
* generalized time format. A pattern of
* "[timestamp:min=XXX:max=XXX:format=XXX]" will be replaced with a
* randomly selected timestamp in the specified format between the given
* minimum and maximum timestamps (where the minimum and maximum
* timestamp values must be in the generalized time format).
*
- Randomly generated UUIDs (universally unique identifiers) as described
* in RFC 4122. These
* UUIDs may be generated using a pattern string of "[uuid]".
* - Back-references that will be replaced with the same value as the
* bracketed token in the specified position in the string. For example,
* a component of "[ref:1]" will be replaced with the same value as used
* in the first bracketed component of the value pattern. Back-references
* must only reference components that have been previously defined in the
* value pattern, and not those which appear after the reference.
*
*
* It must be possible to represent all of the numeric values used in sequential
* or random numeric ranges as {@code long} values. In a sequential numeric
* range, if the first value is larger than the second value, then values will
* be chosen in descending rather than ascending order (and if an increment is
* given, then it should be positive). In addition, once the end of a
* sequential range has been reached, then the value will wrap around to the
* beginning of that range.
*
* Examples of value pattern components include:
*
* Hello
-- The static text "Hello
".
* [[Hello]]
-- The static text "[Hello]
" (note
* that the double square brackets were replaced with single square
* brackets).
* [0:1000]
-- A sequential numeric range that will iterate
* in ascending sequential order from 0 to 1000. The 1002nd value that is
* requested will cause the value to be wrapped around to 0 again.
* [1000:0]
-- A sequential numeric range that will iterate
* in descending sequential order from 1000 to 0. The 1002nd value that is
* requested will cause the value to be wrapped around to 1000 again.
* [0:1000x5%0000]
-- A sequential numeric range that will
* iterate in ascending sequential order from 0 to 1000 in increments of
* five with all values represented as four-digit numbers padded with
* leading zeroes. For example, the first four values generated by this
* component will be "0000", "0005", "0010", and "0015".
* [0-1000]
-- A random numeric range that will choose values
* at random between 0 and 1000, inclusive.
* [0-1000%0000]
-- A random numeric range that will choose
* values at random between 0 and 1000, inclusive, and values will be
* padded with leading zeroes as necessary so that they are represented
* using four digits.
* [random:5]
-- Will generate a string of five randomly
* selected lowercase letters to be used in place of the bracketed
* range.
* [random:4:0123456789abcdef]
-- Will generate a string of
* four randomly selected hexadecimal digits to be used in place of the
* bracketed range.
* [random:5:abcdefghijklmnopqrstuvwxyz]
-- Will generate a
* string of five randomly selected lowercase letters to be used in place
* of the bracketed range.
* [file:///tmp/mydata.txt]
-- A URL reference that will
* cause randomly-selected lines from the specified local file to be used
* in place of the bracketed range. To make it clear that the file
* contents are randomly accessed, you may use {@code randomfile} in place
* of {@code file}. The entire file will be read into memory, so this may
* not be a suitable option for very large files.
* [sequentialfile:///tmp/mydata.txt]
-- A URL reference that
* will cause lines from the specified local file, selected in sequential
* order, to be used in place of the bracketed range. The entire file
* will be read into memory, so this may not be a suitable option for very
* large files.
* [streamfile:///tmp/mydata.txt]
-- A URL reference that
* will cause lines from the specified local file, selected in sequential
* order, to be used in place of the bracketed range. A background thread
* will be used to read data from the file and place it into a queue so
* that it is available quickly, but only a small amount of data will be
* held in memory at any time, so this is a suitable option for very
* large files.
* [timestamp]
-- The current time in generalized time
* format, with millisecond precision.
* [timestamp:format=milliseconds]
-- The current time
* expressed as the number of milliseconds since January 1, 1970 at
* midnight UTC (that is, the output of
* {@code System.currentTimeMillis()}.
* [timestamp:format=seconds]
-- The current time expressed
* as the number of seconds since January 1, 1970 at midnight UTC.
* [timestamp:format=yyyy-MM-dd'T'HH:mm:ss.SSSZ]
-- The
* current time expressed in the specified format string.
* [timestamp:min=20180101000000.000Z:max=20181231235959.999Z:
* format=yyyyMMddHHmmss]
-- A randomly selected timestamp
* sometime in the year 2018 in the specified format.
* [http://server.example.com/tmp/mydata.txt]
-- A URL
* reference that will cause randomly-selected lines from the specified
* remote HTTP-accessible file to be used in place of the bracketed
* range.
* [uuid]
-- Will cause a randomly generated UUID to be used
* in place of the bracketed range.
*
*
* Examples of full value pattern strings include:
*
* dc=example,dc=com
-- A value pattern containing only
* static text and no numeric components.
* [1000:9999]
-- A value pattern containing only a numeric
* component that will choose numbers in sequential order from 1000 to
* 9999.
* (uid=user.[1-1000000])
-- A value pattern that combines
* the static text "(uid=user.
" with a value chosen randomly
* between one and one million, and another static text string of
* ")
".
* uid=user.[1-1000000],ou=org[1-10],dc=example,dc=com
-- A
* value pattern containing two numeric components interspersed between
* three static text components.
* uid=user.[1-1000000],ou=org[ref:1],dc=example,dc=com
-- A
* value pattern in which the organization number will be the same as the
* randomly-selected user number.
*
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class ValuePattern
implements Serializable
{
/**
* The URL to the publicly-accessible javadoc for this class, which provides
* a detailed overview of the supported value pattern syntax.
*/
@NotNull public static final String PUBLIC_JAVADOC_URL =
"https://docs.ldap.com/ldap-sdk/docs/javadoc/index.html?" +
"com/unboundid/util/ValuePattern.html";
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 4502778464751705304L;
// Indicates whether the provided value pattern includes one or more
// back-references.
private final boolean hasBackReference;
// The string that was originally used to create this value pattern.
@NotNull private final String pattern;
// The thread-local array list that will be used to hold values for
// back-references.
@NotNull private final ThreadLocal> refLists;
// The thread-local string builder that will be used to build values.
@NotNull private final ThreadLocal buffers;
// The value pattern components that will be used to generate values.
@NotNull private final ValuePatternComponent[] components;
/**
* Creates a new value pattern from the provided string.
*
* @param s The string representation of the value pattern to create. It
* must not be {@code null}.
*
* @throws ParseException If the provided string cannot be parsed as a valid
* value pattern string.
*/
public ValuePattern(@NotNull final String s)
throws ParseException
{
this(s, null);
}
/**
* Creates a new value pattern from the provided string.
*
* @param s The string representation of the value pattern to create. It
* must not be {@code null}.
* @param r The seed to use for the random number generator. It may be
* {@code null} if no seed is required.
*
* @throws ParseException If the provided string cannot be parsed as a valid
* value pattern string.
*/
public ValuePattern(@NotNull final String s, @Nullable final Long r)
throws ParseException
{
Validator.ensureNotNull(s);
pattern = s;
refLists = new ThreadLocal<>();
buffers = new ThreadLocal<>();
final AtomicBoolean hasRef = new AtomicBoolean(false);
final Random random;
if (r == null)
{
random = new Random();
}
else
{
random = new Random(r);
}
final ArrayList l = new ArrayList<>(3);
parse(s, 0, l, random, hasRef);
hasBackReference = hasRef.get();
if (hasBackReference)
{
int availableReferences = 0;
for (final ValuePatternComponent c : l)
{
if (c instanceof BackReferenceValuePatternComponent)
{
final BackReferenceValuePatternComponent brvpc =
(BackReferenceValuePatternComponent) c;
if (brvpc.getIndex() > availableReferences)
{
throw new ParseException(
ERR_REF_VALUE_PATTERN_INVALID_INDEX.get(brvpc.getIndex()), 0);
}
}
if (c.supportsBackReference())
{
availableReferences++;
}
}
}
components = new ValuePatternComponent[l.size()];
l.toArray(components);
}
/**
* Recursively parses the provided string into a list of value pattern
* components.
*
* @param s The string representation of the value pattern to create. It
* may be a portion of the entire value pattern string.
* @param o The offset of the first character of the provided string in
* the full value pattern string.
* @param l The list into which the parsed components should be added.
* @param r The random number generator to use to seed random number
* generators used by components.
* @param ref A value that may be updated if the pattern contains any
* back-references.
*
* @throws ParseException If the provided string cannot be parsed as a valid
* value pattern string.
*/
private static void parse(@NotNull final String s, final int o,
@NotNull final ArrayList l,
@NotNull final Random r,
@NotNull final AtomicBoolean ref)
throws ParseException
{
// Find the first occurrence of "[[". Parse the portion of the string
// before it, into the list, then add a string value pattern containing "[",
// then parse the portion of the string after it.
// First, parse out any occurrences of "[[" and replace them with string
// value pattern components containing only "[".
int pos = s.indexOf("[[");
if (pos >= 0)
{
if (pos > 0)
{
parse(s.substring(0, pos), o, l, r, ref);
}
l.add(new StringValuePatternComponent("["));
if (pos < (s.length() - 2))
{
parse(s.substring(pos+2), (o+pos+2), l, r, ref);
}
return;
}
// Find the first occurrence of "]]". Parse the portion of the string
// before it, into the list, then add a string value pattern containing "]",
// then parse the portion of the string after it.
pos = s.indexOf("]]");
if (pos >= 0)
{
if (pos > 0)
{
parse(s.substring(0, pos), o, l, r, ref);
}
l.add(new StringValuePatternComponent("]"));
if (pos < (s.length() - 2))
{
parse(s.substring(pos+2), (o+pos+2), l, r, ref);
}
return;
}
// Find the first occurrence of "[" and the corresponding "]". The part
// before that will be a string. Then parse out the numeric or URL
// component, and parse the rest of the string after the "]".
pos = s.indexOf('[');
if (pos >= 0)
{
final int closePos = s.indexOf(']');
if (closePos < 0)
{
throw new ParseException(
ERR_VALUE_PATTERN_UNMATCHED_OPEN.get(o+pos), (o+pos));
}
else if (closePos < pos)
{
throw new ParseException(
ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+closePos), (o+closePos));
}
if (pos > 0)
{
l.add(new StringValuePatternComponent(s.substring(0, pos)));
}
final String bracketedToken = s.substring(pos+1, closePos);
if (bracketedToken.startsWith("random:"))
{
l.add(new RandomCharactersValuePatternComponent(bracketedToken,
r.nextLong()));
}
else if (bracketedToken.startsWith("file:"))
{
final String path = bracketedToken.substring(5);
try
{
l.add(new FileValuePatternComponent(path, r.nextLong(), false));
}
catch (final IOException ioe)
{
Debug.debugException(ioe);
throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get(
path, StaticUtils.getExceptionMessage(ioe)), o+pos);
}
}
else if (bracketedToken.startsWith("randomfile:"))
{
final String path = bracketedToken.substring(11);
try
{
l.add(new FileValuePatternComponent(path, r.nextLong(), false));
}
catch (final IOException ioe)
{
Debug.debugException(ioe);
throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get(
path, StaticUtils.getExceptionMessage(ioe)), o+pos);
}
}
else if (bracketedToken.startsWith("sequentialfile:"))
{
final String path = bracketedToken.substring(15);
try
{
l.add(new FileValuePatternComponent(path, r.nextLong(), true));
}
catch (final IOException ioe)
{
Debug.debugException(ioe);
throw new ParseException(ERR_FILE_VALUE_PATTERN_NOT_USABLE.get(
path, StaticUtils.getExceptionMessage(ioe)), o+pos);
}
}
else if (bracketedToken.startsWith("streamfile:"))
{
final String path = bracketedToken.substring(11);
try
{
l.add(new StreamFileValuePatternComponent(path));
}
catch (final IOException ioe)
{
Debug.debugException(ioe);
throw new ParseException(ERR_STREAM_FILE_VALUE_PATTERN_NOT_USABLE.get(
path, StaticUtils.getExceptionMessage(ioe)), o+pos);
}
}
else if (bracketedToken.startsWith("http://"))
{
try
{
l.add(new HTTPValuePatternComponent(bracketedToken, r.nextLong()));
}
catch (final IOException ioe)
{
Debug.debugException(ioe);
throw new ParseException(ERR_HTTP_VALUE_PATTERN_NOT_USABLE.get(
bracketedToken, StaticUtils.getExceptionMessage(ioe)), o+pos);
}
}
else if (bracketedToken.startsWith("timestamp"))
{
l.add(new TimestampValuePatternComponent(bracketedToken,
r.nextLong()));
}
else if (bracketedToken.equals("uuid"))
{
l.add(new UUIDValuePatternComponent());
}
else if (bracketedToken.startsWith("ref:"))
{
ref.set(true);
final String valueStr = bracketedToken.substring(4);
try
{
final int index = Integer.parseInt(valueStr);
if (index == 0)
{
throw new ParseException(ERR_REF_VALUE_PATTERN_ZERO_INDEX.get(),
(o+pos+4));
}
else if (index < 0)
{
throw new ParseException(
ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4));
}
else
{
l.add(new BackReferenceValuePatternComponent(index));
}
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_REF_VALUE_PATTERN_NOT_VALID.get(valueStr), (o+pos+4));
}
}
else
{
l.add(parseNumericComponent(s.substring(pos+1, closePos), (o+pos+1),
r));
}
if (closePos < (s.length() - 1))
{
parse(s.substring(closePos+1), (o+closePos+1), l, r, ref);
}
return;
}
// If there are any occurrences of "]" without a corresponding open, then
// that's invalid.
pos = s.indexOf(']');
if (pos >= 0)
{
throw new ParseException(
ERR_VALUE_PATTERN_UNMATCHED_CLOSE.get(o+pos), (o+pos));
}
// There are no brackets, so it's just a static string.
l.add(new StringValuePatternComponent(s));
}
/**
* Parses the specified portion of the provided string as either a
* sequential or random numeric value pattern component.
*
* @param s The string to parse, not including the square brackets.
* @param o The offset in the overall value pattern string at which the
* provided substring begins.
* @param r The random number generator to use to seed random number
* generators used by components.
*
* @return The parsed numeric value pattern component.
*
* @throws ParseException If the specified substring cannot be parsed as a
*
*/
@NotNull()
private static ValuePatternComponent parseNumericComponent(
@NotNull final String s,final int o, @NotNull final Random r)
throws ParseException
{
boolean delimiterFound = false;
boolean sequential = false;
int pos = 0;
long lowerBound = 0L;
lowerBoundLoop:
for ( ; pos < s.length(); pos++)
{
switch (s.charAt(pos))
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// These are all acceptable.
break;
case '-':
if (pos == 0)
{
// This indicates that the value is negative.
break;
}
else
{
// This indicates the end of the lower bound.
delimiterFound = true;
sequential = false;
try
{
lowerBound = Long.parseLong(s.substring(0, pos));
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
Long.MAX_VALUE),
(o-1));
}
pos++;
break lowerBoundLoop;
}
case ':':
delimiterFound = true;
sequential = true;
if (pos == 0)
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_LOWER_BOUND.get(o-1), (o-1));
}
else
{
try
{
lowerBound = Long.parseLong(s.substring(0, pos));
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
Long.MAX_VALUE),
(o-1));
}
}
pos++;
break lowerBoundLoop;
default:
throw new ParseException(
ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)),
(o+pos));
}
}
if (! delimiterFound)
{
throw new ParseException(ERR_VALUE_PATTERN_NO_DELIMITER.get(o-1), (o-1));
}
boolean hasIncrement = false;
int startPos = pos;
long upperBound = lowerBound;
long increment = 1L;
String formatString = null;
delimiterFound = false;
upperBoundLoop:
for ( ; pos < s.length(); pos++)
{
switch (s.charAt(pos))
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// These are all acceptable.
break;
case '-':
if (pos == startPos)
{
// This indicates that the value is negative.
break;
}
else
{
throw new ParseException(
ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)),
(o+pos));
}
case 'x':
delimiterFound = true;
hasIncrement = true;
if (pos == startPos)
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
}
else
{
try
{
upperBound = Long.parseLong(s.substring(startPos, pos));
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
Long.MAX_VALUE),
(o-1));
}
}
pos++;
break upperBoundLoop;
case '%':
delimiterFound = true;
hasIncrement = false;
if (pos == startPos)
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
}
else
{
try
{
upperBound = Long.parseLong(s.substring(startPos, pos));
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
Long.MAX_VALUE),
(o-1));
}
}
pos++;
break upperBoundLoop;
default:
throw new ParseException(
ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos), (o+pos)),
(o+pos));
}
}
if (! delimiterFound)
{
if (pos == startPos)
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_UPPER_BOUND.get(o-1), (o-1));
}
try
{
upperBound = Long.parseLong(s.substring(startPos, pos));
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
Long.MAX_VALUE),
(o-1));
}
if (sequential)
{
return new SequentialValuePatternComponent(lowerBound, upperBound, 1,
null);
}
else
{
return new RandomValuePatternComponent(lowerBound, upperBound,
r.nextLong(), null);
}
}
if (hasIncrement)
{
delimiterFound = false;
startPos = pos;
incrementLoop:
for ( ; pos < s.length(); pos++)
{
switch (s.charAt(pos))
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// These are all acceptable.
break;
case '-':
if (pos == startPos)
{
// This indicates that the value is negative.
break;
}
else
{
throw new ParseException(
ERR_VALUE_PATTERN_INVALID_CHARACTER.get('-', (o+pos)),
(o+pos));
}
case '%':
delimiterFound = true;
if (pos == startPos)
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1));
}
else if (pos == (s.length() - 1))
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1));
}
else
{
try
{
increment = Long.parseLong(s.substring(startPos, pos));
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
Long.MAX_VALUE),
(o-1));
}
formatString = s.substring(pos+1);
}
break incrementLoop;
default:
throw new ParseException(
ERR_VALUE_PATTERN_INVALID_CHARACTER.get(s.charAt(pos),
(o+pos)),
(o+pos));
}
}
if (! delimiterFound)
{
if (pos == startPos)
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_INCREMENT.get(o-1), (o-1));
}
try
{
increment = Long.parseLong(s.substring(startPos, pos));
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new ParseException(
ERR_VALUE_PATTERN_VALUE_NOT_LONG.get((o-1), Long.MIN_VALUE,
Long.MAX_VALUE),
(o-1));
}
}
}
else
{
formatString = s.substring(pos);
if (formatString.length() == 0)
{
throw new ParseException(
ERR_VALUE_PATTERN_EMPTY_FORMAT.get(o-1), (o-1));
}
}
if (sequential)
{
return new SequentialValuePatternComponent(lowerBound, upperBound,
increment, formatString);
}
else
{
return new RandomValuePatternComponent(lowerBound, upperBound,
r.nextLong(), formatString);
}
}
/**
* Retrieves the next value generated from the value pattern.
*
* @return The next value generated from the value pattern.
*/
@NotNull()
public String nextValue()
{
StringBuilder buffer = buffers.get();
if (buffer == null)
{
buffer = new StringBuilder();
buffers.set(buffer);
}
else
{
buffer.setLength(0);
}
ArrayList refList = refLists.get();
if (hasBackReference)
{
if (refList == null)
{
refList = new ArrayList<>(10);
refLists.set(refList);
}
else
{
refList.clear();
}
}
for (final ValuePatternComponent c : components)
{
if (hasBackReference)
{
if (c instanceof BackReferenceValuePatternComponent)
{
final BackReferenceValuePatternComponent brvpc =
(BackReferenceValuePatternComponent) c;
final String value = refList.get(brvpc.getIndex() - 1);
buffer.append(value);
refList.add(value);
}
else if (c.supportsBackReference())
{
final int startPos = buffer.length();
c.append(buffer);
refList.add(buffer.substring(startPos));
}
else
{
c.append(buffer);
}
}
else
{
c.append(buffer);
}
}
return buffer.toString();
}
/**
* Retrieves a string representation of this value pattern, which will be the
* original pattern string used to create it.
*
* @return A string representation of this value pattern.
*/
@Override()
@NotNull()
public String toString()
{
return pattern;
}
}