diff --git a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java index eb6615e75..d64ec5ebf 100644 --- a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java +++ b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java @@ -16,201 +16,212 @@ */ package org.apache.commons.lang3; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.Security; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; - -import org.apache.commons.lang3.exception.UncheckedException; +import java.util.function.Supplier; /** * Generates random {@link String}s. *

- * Starting in version 3.15.0, this classes uses {@link SecureRandom#getInstanceStrong()}. + * Starting in version 3.16.0, this class uses {@link #secure()} for static methods and adds {@link #insecure()}. *

*

- * Before version 3.15.0, this classes used {@link ThreadLocalRandom#current()}, which was NOT cryptographically secure. + * Starting in version 3.15.0, this class uses {@link SecureRandom#getInstanceStrong()} for static methods. + *

+ *

+ * Before version 3.15.0, this class used {@link ThreadLocalRandom#current()} for static methods, which is not + * cryptographically secure. + *

+ *

+ * Use {@link #secure()} to get the singleton instance based on {@link SecureRandom#getInstanceStrong()} which uses an + * algorithms/providers specified in the {@code securerandom.strongAlgorithms} {@link Security} property. + *

+ *

+ * Use {@link #insecure()} to get the singleton instance based on {@link ThreadLocalRandom#current()}; which is not + * cryptographically secure. *

*

* RandomStringUtils is intended for simple use cases. For more advanced use cases consider using Apache Commons Text's - * RandomStringGenerator - * instead. + * + * RandomStringGenerator instead. *

*

- * The Apache Commons project provides Commons RNG dedicated to pseudo-random number generation, - * that may be a better choice for applications with more stringent requirements (performance and/or correctness). + * The Apache Commons project provides Commons RNG + * dedicated to pseudo-random number generation, that may be a better choice for applications with more stringent + * requirements (performance and/or correctness). *

*

- * Note that private high surrogate characters are ignored. These are Unicode characters that fall between the values 56192 (db80) and 56319 (dbff) as - * we don't know how to handle them. High and low surrogates are correctly dealt with - that is if a high surrogate is randomly chosen, 55296 (d800) to 56191 - * (db7f) then it is followed by a low surrogate. If a low surrogate is chosen, 56320 (dc00) to 57343 (dfff) then it is placed after a randomly chosen high + * Note that private high surrogate characters are ignored. These are Unicode characters that fall between the + * values 56192 (db80) and 56319 (dbff) as we don't know how to handle them. High and low surrogates are correctly dealt + * with - that is if a high surrogate is randomly chosen, 55296 (d800) to 56191 (db7f) then it is followed by a low + * surrogate. If a low surrogate is chosen, 56320 (dc00) to 57343 (dfff) then it is placed after a randomly chosen high * surrogate. *

*

* #ThreadSafe# *

* + * @see RandomUtils * @since 1.0 */ public class RandomStringUtils { - private static final ThreadLocal RANDOM = ThreadLocal.withInitial(() -> { - try { - return SecureRandom.getInstanceStrong(); - } catch (NoSuchAlgorithmException e) { - throw new UncheckedException(e); - } - }); + private static final Supplier SECURE_SUPPLIER = RandomUtils::secure; - static SecureRandom random() { - return RANDOM.get(); - } + private static RandomStringUtils INSECURE = new RandomStringUtils(RandomUtils::insecure); - private static final char[] ALPHANUMERICAL_CHARS = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' - }; + private static RandomStringUtils SECURE = new RandomStringUtils(SECURE_SUPPLIER); + + private static final char[] ALPHANUMERICAL_CHARS = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', + '2', '3', '4', '5', '6', '7', '8', '9' }; /** - * Creates a random string whose length is the number of characters - * specified. + * Gets the singleton instance based on {@link ThreadLocalRandom#current()}; which is not cryptographically + * secure; use {@link #secure()} to use an algorithms/providers specified in the + * {@code securerandom.strongAlgorithms} {@link Security} property. + *

+ * The method {@link ThreadLocalRandom#current()} is called on-demand. + *

* - *

Characters will be chosen from the set of all characters.

+ * @return the singleton instance based on {@link ThreadLocalRandom#current()}. + * @see ThreadLocalRandom#current() + * @see #secure() + * @since 3.16.0 + */ + public static RandomStringUtils insecure() { + return INSECURE; + } + + /** + * Creates a random string whose length is the number of characters specified. * - * @param count the length of random string to create + *

+ * Characters will be chosen from the set of all characters. + *

+ * + * @param count the length of random string to create * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ public static String random(final int count) { - return random(count, false, false); + return secure().next(count); } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of alpha-numeric - * characters as indicated by the arguments.

+ *

+ * Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + *

* - * @param count the length of random string to create - * @param letters if {@code true}, generated string may include - * alphabetic characters - * @param numbers if {@code true}, generated string may include - * numeric characters + * @param count the length of random string to create + * @param letters if {@code true}, generated string may include alphabetic characters + * @param numbers if {@code true}, generated string may include numeric characters * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ public static String random(final int count, final boolean letters, final boolean numbers) { - return random(count, 0, 0, letters, numbers); + return secure().next(count, letters, numbers); } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of characters specified.

+ *

+ * Characters will be chosen from the set of characters specified. + *

* - * @param count the length of random string to create - * @param chars the character array containing the set of characters to use, - * may be null + * @param count the length of random string to create + * @param chars the character array containing the set of characters to use, may be null * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ public static String random(final int count, final char... chars) { - if (chars == null) { - return random(count, 0, 0, false, false, null, random()); - } - return random(count, 0, chars.length, false, false, chars, random()); + return secure().next(count, chars); } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of alpha-numeric - * characters as indicated by the arguments.

+ *

+ * Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + *

* - * @param count the length of random string to create - * @param start the position in set of chars to start at - * @param end the position in set of chars to end before - * @param letters if {@code true}, generated string may include - * alphabetic characters - * @param numbers if {@code true}, generated string may include - * numeric characters + * @param count the length of random string to create + * @param start the position in set of chars to start at + * @param end the position in set of chars to end before + * @param letters if {@code true}, generated string may include alphabetic characters + * @param numbers if {@code true}, generated string may include numeric characters * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ - public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers) { - return random(count, start, end, letters, numbers, null, random()); + public static String random(final int count, final int start, final int end, final boolean letters, + final boolean numbers) { + return secure().next(count, start, end, letters, numbers); } /** - * Creates a random string based on a variety of options, using - * default source of randomness. + * Creates a random string based on a variety of options, using default source of randomness. * - *

This method has exactly the same semantics as - * {@link #random(int,int,int,boolean,boolean,char[],Random)}, but - * instead of using an externally supplied source of randomness, it uses - * the internal static {@link Random} instance.

+ *

+ * This method has exactly the same semantics as {@link #random(int,int,int,boolean,boolean,char[],Random)}, but + * instead of using an externally supplied source of randomness, it uses the internal static {@link Random} + * instance. + *

* - * @param count the length of random string to create - * @param start the position in set of chars to start at - * @param end the position in set of chars to end before - * @param letters if {@code true}, generated string may include - * alphabetic characters - * @param numbers if {@code true}, generated string may include - * numeric characters - * @param chars the set of chars to choose randoms from. - * If {@code null}, then it will use the set of all chars. + * @param count the length of random string to create + * @param start the position in set of chars to start at + * @param end the position in set of chars to end before + * @param letters if {@code true}, generated string may include alphabetic characters + * @param numbers if {@code true}, generated string may include numeric characters + * @param chars the set of chars to choose randoms from. If {@code null}, then it will use the set of all chars. * @return the random string - * @throws ArrayIndexOutOfBoundsException if there are not - * {@code (end - start) + 1} characters in the set array. - * @throws IllegalArgumentException if {@code count} < 0. + * @throws ArrayIndexOutOfBoundsException if there are not {@code (end - start) + 1} characters in the set array. + * @throws IllegalArgumentException if {@code count} < 0. */ - public static String random(final int count, final int start, final int end, final boolean letters, final boolean numbers, final char... chars) { - return random(count, start, end, letters, numbers, chars, random()); + public static String random(final int count, final int start, final int end, final boolean letters, + final boolean numbers, final char... chars) { + return secure().next(count, start, end, letters, numbers, chars); } /** - * Creates a random string based on a variety of options, using - * supplied source of randomness. + * Creates a random string based on a variety of options, using supplied source of randomness. * - *

If start and end are both {@code 0}, start and end are set - * to {@code ' '} and {@code 'z'}, the ASCII printable - * characters, will be used, unless letters and numbers are both - * {@code false}, in which case, start and end are set to - * {@code 0} and {@link Character#MAX_CODE_POINT}. + *

+ * If start and end are both {@code 0}, start and end are set to {@code ' '} and {@code 'z'}, the ASCII printable + * characters, will be used, unless letters and numbers are both {@code false}, in which case, start and end are set + * to {@code 0} and {@link Character#MAX_CODE_POINT}. * - *

If set is not {@code null}, characters between start and - * end are chosen.

+ *

+ * If set is not {@code null}, characters between start and end are chosen. + *

* - *

This method accepts a user-supplied {@link Random} - * instance to use as a source of randomness. By seeding a single - * {@link Random} instance with a fixed seed and using it for each call, - * the same random sequence of strings can be generated repeatedly - * and predictably.

+ *

+ * This method accepts a user-supplied {@link Random} instance to use as a source of randomness. By seeding a single + * {@link Random} instance with a fixed seed and using it for each call, the same random sequence of strings can be + * generated repeatedly and predictably. + *

* - * @param count the length of random string to create - * @param start the position in set of chars to start at (inclusive) - * @param end the position in set of chars to end before (exclusive) - * @param letters if {@code true}, generated string may include - * alphabetic characters - * @param numbers if {@code true}, generated string may include - * numeric characters - * @param chars the set of chars to choose randoms from, must not be empty. - * If {@code null}, then it will use the set of all chars. + * @param count the length of random string to create + * @param start the position in set of chars to start at (inclusive) + * @param end the position in set of chars to end before (exclusive) + * @param letters if {@code true}, generated string may include alphabetic characters + * @param numbers if {@code true}, generated string may include numeric characters + * @param chars the set of chars to choose randoms from, must not be empty. If {@code null}, then it will use the + * set of all chars. * @param random a source of randomness. * @return the random string - * @throws ArrayIndexOutOfBoundsException if there are not - * {@code (end - start) + 1} characters in the set array. - * @throws IllegalArgumentException if {@code count} < 0 or the provided chars array is empty. + * @throws ArrayIndexOutOfBoundsException if there are not {@code (end - start) + 1} characters in the set array. + * @throws IllegalArgumentException if {@code count} < 0 or the provided chars array is empty. * @since 2.0 */ public static String random(int count, int start, int end, final boolean letters, final boolean numbers, - final char[] chars, final Random random) { + final char[] chars, final Random random) { if (count == 0) { return StringUtils.EMPTY; } @@ -231,7 +242,8 @@ public class RandomStringUtils { start = ' '; } } else if (end <= start) { - throw new IllegalArgumentException("Parameter end (" + end + ") must be greater than start (" + start + ")"); + throw new IllegalArgumentException( + "Parameter end (" + end + ") must be greater than start (" + start + ")"); } else if (start < 0 || end < 0) { throw new IllegalArgumentException("Character positions MUST be >= 0"); } @@ -275,10 +287,10 @@ public class RandomStringUtils { final int zeroDigitAscii = 48; final int firstLetterAscii = 65; - if (chars == null && (numbers && end <= zeroDigitAscii - || letters && end <= firstLetterAscii)) { - throw new IllegalArgumentException("Parameter end (" + end + ") must be greater then (" + zeroDigitAscii + ") for generating digits " + - "or greater then (" + firstLetterAscii + ") for generating letters."); + if (chars == null && (numbers && end <= zeroDigitAscii || letters && end <= firstLetterAscii)) { + throw new IllegalArgumentException( + "Parameter end (" + end + ") must be greater then (" + zeroDigitAscii + ") for generating digits " + + "or greater then (" + firstLetterAscii + ") for generating letters."); } final StringBuilder builder = new StringBuilder(count); @@ -322,8 +334,7 @@ public class RandomStringUtils { continue; } - if (letters && Character.isLetter(codePoint) - || numbers && Character.isDigit(codePoint) + if (letters && Character.isLetter(codePoint) || numbers && Character.isDigit(codePoint) || !letters && !numbers) { builder.appendCodePoint(codePoint); @@ -339,46 +350,43 @@ public class RandomStringUtils { } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of characters - * specified by the string, must not be empty. - * If null, the set of all characters is used.

+ *

+ * Characters will be chosen from the set of characters specified by the string, must not be empty. If null, the set + * of all characters is used. + *

* - * @param count the length of random string to create - * @param chars the String containing the set of characters to use, - * may be null, but must not be empty + * @param count the length of random string to create + * @param chars the String containing the set of characters to use, may be null, but must not be empty * @return the random string * @throws IllegalArgumentException if {@code count} < 0 or the string is empty. */ public static String random(final int count, final String chars) { - if (chars == null) { - return random(count, 0, 0, false, false, null, random()); - } - return random(count, chars.toCharArray()); + return secure().next(count, chars); } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of Latin alphabetic - * characters (a-z, A-Z).

+ *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z). + *

* - * @param count the length of random string to create + * @param count the length of random string to create * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ public static String randomAlphabetic(final int count) { - return random(count, true, false); + return secure().nextAlphabetic(count); } /** - * Creates a random string whose length is between the inclusive minimum and - * the exclusive maximum. + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. * - *

Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z).

+ *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z). + *

* * @param minLengthInclusive the inclusive minimum length of the string to generate * @param maxLengthExclusive the exclusive maximum length of the string to generate @@ -386,30 +394,30 @@ public class RandomStringUtils { * @since 3.5 */ public static String randomAlphabetic(final int minLengthInclusive, final int maxLengthExclusive) { - return randomAlphabetic(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive)); + return secure().nextAlphabetic(minLengthInclusive, maxLengthExclusive); } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of Latin alphabetic - * characters (a-z, A-Z) and the digits 0-9.

+ *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z) and the digits 0-9. + *

* - * @param count the length of random string to create + * @param count the length of random string to create * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ public static String randomAlphanumeric(final int count) { - return random(count, true, true); + return secure().nextAlphanumeric(count); } /** - * Creates a random string whose length is between the inclusive minimum and - * the exclusive maximum. + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. * - *

Characters will be chosen from the set of Latin alphabetic - * characters (a-z, A-Z) and the digits 0-9.

+ *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z) and the digits 0-9. + *

* * @param minLengthInclusive the inclusive minimum length of the string to generate * @param maxLengthExclusive the exclusive maximum length of the string to generate @@ -417,30 +425,32 @@ public class RandomStringUtils { * @since 3.5 */ public static String randomAlphanumeric(final int minLengthInclusive, final int maxLengthExclusive) { - return randomAlphanumeric(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive)); + return secure().nextAlphanumeric(minLengthInclusive, maxLengthExclusive); } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of characters whose - * ASCII value is between {@code 32} and {@code 126} (inclusive).

+ *

+ * Characters will be chosen from the set of characters whose ASCII value is between {@code 32} and {@code 126} + * (inclusive). + *

* - * @param count the length of random string to create + * @param count the length of random string to create * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ public static String randomAscii(final int count) { - return random(count, 32, 127, false, false); + return secure().nextAscii(count); } /** - * Creates a random string whose length is between the inclusive minimum and - * the exclusive maximum. + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. * - *

Characters will be chosen from the set of characters whose - * ASCII value is between {@code 32} and {@code 126} (inclusive).

+ *

+ * Characters will be chosen from the set of characters whose ASCII value is between {@code 32} and {@code 126} + * (inclusive). + *

* * @param minLengthInclusive the inclusive minimum length of the string to generate * @param maxLengthExclusive the exclusive maximum length of the string to generate @@ -448,30 +458,32 @@ public class RandomStringUtils { * @since 3.5 */ public static String randomAscii(final int minLengthInclusive, final int maxLengthExclusive) { - return randomAscii(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive)); + return secure().nextAscii(minLengthInclusive, maxLengthExclusive); } /** * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of characters which match the POSIX [:graph:] - * regular expression character class. This class contains all visible ASCII characters - * (i.e. anything except spaces and control characters).

+ *

+ * Characters will be chosen from the set of characters which match the POSIX [:graph:] regular expression character + * class. This class contains all visible ASCII characters (i.e. anything except spaces and control characters). + *

* - * @param count the length of random string to create + * @param count the length of random string to create * @return the random string * @throws IllegalArgumentException if {@code count} < 0. * @since 3.5 */ public static String randomGraph(final int count) { - return random(count, 33, 126, false, false); + return secure().nextGraph(count); } /** - * Creates a random string whose length is between the inclusive minimum and - * the exclusive maximum. + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. * - *

Characters will be chosen from the set of \p{Graph} characters.

+ *

+ * Characters will be chosen from the set of \p{Graph} characters. + *

* * @param minLengthInclusive the inclusive minimum length of the string to generate * @param maxLengthExclusive the exclusive maximum length of the string to generate @@ -479,29 +491,30 @@ public class RandomStringUtils { * @since 3.5 */ public static String randomGraph(final int minLengthInclusive, final int maxLengthExclusive) { - return randomGraph(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive)); + return secure().nextGraph(minLengthInclusive, maxLengthExclusive); } /** - * Creates a random string whose length is the number of characters - * specified. + * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of numeric - * characters.

+ *

+ * Characters will be chosen from the set of numeric characters. + *

* - * @param count the length of random string to create + * @param count the length of random string to create * @return the random string * @throws IllegalArgumentException if {@code count} < 0. */ public static String randomNumeric(final int count) { - return random(count, false, true); + return secure().nextNumeric(count); } /** - * Creates a random string whose length is between the inclusive minimum and - * the exclusive maximum. + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. * - *

Characters will be chosen from the set of \p{Digit} characters.

+ *

+ * Characters will be chosen from the set of \p{Digit} characters. + *

* * @param minLengthInclusive the inclusive minimum length of the string to generate * @param maxLengthExclusive the exclusive maximum length of the string to generate @@ -509,30 +522,32 @@ public class RandomStringUtils { * @since 3.5 */ public static String randomNumeric(final int minLengthInclusive, final int maxLengthExclusive) { - return randomNumeric(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive)); + return secure().nextNumeric(minLengthInclusive, maxLengthExclusive); } /** * Creates a random string whose length is the number of characters specified. * - *

Characters will be chosen from the set of characters which match the POSIX [:print:] - * regular expression character class. This class includes all visible ASCII characters and spaces - * (i.e. anything except control characters).

+ *

+ * Characters will be chosen from the set of characters which match the POSIX [:print:] regular expression character + * class. This class includes all visible ASCII characters and spaces (i.e. anything except control characters). + *

* - * @param count the length of random string to create + * @param count the length of random string to create * @return the random string * @throws IllegalArgumentException if {@code count} < 0. * @since 3.5 */ public static String randomPrint(final int count) { - return random(count, 32, 126, false, false); + return secure().nextPrint(count); } /** - * Creates a random string whose length is between the inclusive minimum and - * the exclusive maximum. + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. * - *

Characters will be chosen from the set of \p{Print} characters.

+ *

+ * Characters will be chosen from the set of \p{Print} characters. + *

* * @param minLengthInclusive the inclusive minimum length of the string to generate * @param maxLengthExclusive the exclusive maximum length of the string to generate @@ -540,22 +555,378 @@ public class RandomStringUtils { * @since 3.5 */ public static String randomPrint(final int minLengthInclusive, final int maxLengthExclusive) { - return randomPrint(RandomUtils.nextInt(minLengthInclusive, maxLengthExclusive)); + return secure().nextPrint(minLengthInclusive, maxLengthExclusive); } /** - * {@link RandomStringUtils} instances should NOT be constructed in - * standard programming. Instead, the class should be used as - * {@code RandomStringUtils.random(5);}. + * Gets the singleton instance based on {@link SecureRandom#getInstanceStrong()} which uses an algorithms/providers + * specified in the {@code securerandom.strongAlgorithms} {@link Security} property. + *

+ * The method {@link SecureRandom#getInstanceStrong()} is called on-demand. + *

* - *

This constructor is public to permit tools that require a JavaBean instance - * to operate.

+ * @return the singleton instance based on {@link SecureRandom#getInstanceStrong()}. + * @see SecureRandom#getInstanceStrong() + * @since 3.16.0 + */ + public static RandomStringUtils secure() { + return SECURE; + } + + private final Supplier random; + + /** + * {@link RandomStringUtils} instances should NOT be constructed in standard programming. Instead, the class should + * be used as {@code RandomStringUtils.random(5);}. + * + *

+ * This constructor is public to permit tools that require a JavaBean instance to operate. + *

* * @deprecated TODO Make private in 4.0. */ @Deprecated public RandomStringUtils() { - // empty + this(SECURE_SUPPLIER); + } + + private RandomStringUtils(final Supplier random) { + this.random = random; + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of all characters. + *

+ * + * @param count the length of random string to create + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + * @since 3.16.0 + */ + public String next(final int count) { + return random(count, false, false); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + *

+ * + * @param count the length of random string to create + * @param letters if {@code true}, generated string may include alphabetic characters + * @param numbers if {@code true}, generated string may include numeric characters + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + * @since 3.16.0 + */ + public String next(final int count, final boolean letters, final boolean numbers) { + return random(count, 0, 0, letters, numbers); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of characters specified. + *

+ * + * @param count the length of random string to create + * @param chars the character array containing the set of characters to use, may be null + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + * @since 3.16.0 + */ + public String next(final int count, final char... chars) { + if (chars == null) { + return random(count, 0, 0, false, false, null, random()); + } + return random(count, 0, chars.length, false, false, chars, random()); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of alpha-numeric characters as indicated by the arguments. + *

+ * + * @param count the length of random string to create + * @param start the position in set of chars to start at + * @param end the position in set of chars to end before + * @param letters if {@code true}, generated string may include alphabetic characters + * @param numbers if {@code true}, generated string may include numeric characters + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + * @since 3.16.0 + */ + public String next(final int count, final int start, final int end, final boolean letters, final boolean numbers) { + return random(count, start, end, letters, numbers, null, random()); + } + + /** + * Creates a random string based on a variety of options, using default source of randomness. + * + *

+ * This method has exactly the same semantics as {@link #random(int,int,int,boolean,boolean,char[],Random)}, but + * instead of using an externally supplied source of randomness, it uses the internal static {@link Random} + * instance. + *

+ * + * @param count the length of random string to create + * @param start the position in set of chars to start at + * @param end the position in set of chars to end before + * @param letters if {@code true}, generated string may include alphabetic characters + * @param numbers if {@code true}, generated string may include numeric characters + * @param chars the set of chars to choose randoms from. If {@code null}, then it will use the set of all chars. + * @return the random string + * @throws ArrayIndexOutOfBoundsException if there are not {@code (end - start) + 1} characters in the set array. + * @throws IllegalArgumentException if {@code count} < 0. + */ + public String next(final int count, final int start, final int end, final boolean letters, final boolean numbers, + final char... chars) { + return random(count, start, end, letters, numbers, chars, random()); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of characters specified by the string, must not be empty. If null, the set + * of all characters is used. + *

+ * + * @param count the length of random string to create + * @param chars the String containing the set of characters to use, may be null, but must not be empty + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0 or the string is empty. + * @since 3.16.0 + */ + public String next(final int count, final String chars) { + if (chars == null) { + return random(count, 0, 0, false, false, null, random()); + } + return random(count, chars.toCharArray()); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z). + *

+ * + * @param count the length of random string to create + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + */ + public String nextAlphabetic(final int count) { + return random(count, true, false); + } + + /** + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. + * + *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z). + *

+ * + * @param minLengthInclusive the inclusive minimum length of the string to generate + * @param maxLengthExclusive the exclusive maximum length of the string to generate + * @return the random string + * @since 3.5 + */ + public String nextAlphabetic(final int minLengthInclusive, final int maxLengthExclusive) { + return randomAlphabetic(randomUtils().randomInt(minLengthInclusive, maxLengthExclusive)); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z) and the digits 0-9. + *

+ * + * @param count the length of random string to create + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + */ + public String nextAlphanumeric(final int count) { + return random(count, true, true); + } + + /** + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. + * + *

+ * Characters will be chosen from the set of Latin alphabetic characters (a-z, A-Z) and the digits 0-9. + *

+ * + * @param minLengthInclusive the inclusive minimum length of the string to generate + * @param maxLengthExclusive the exclusive maximum length of the string to generate + * @return the random string + * @since 3.5 + */ + public String nextAlphanumeric(final int minLengthInclusive, final int maxLengthExclusive) { + return randomAlphanumeric(randomUtils().randomInt(minLengthInclusive, maxLengthExclusive)); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of characters whose ASCII value is between {@code 32} and {@code 126} + * (inclusive). + *

+ * + * @param count the length of random string to create + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + */ + public String nextAscii(final int count) { + return random(count, 32, 127, false, false); + } + + /** + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. + * + *

+ * Characters will be chosen from the set of characters whose ASCII value is between {@code 32} and {@code 126} + * (inclusive). + *

+ * + * @param minLengthInclusive the inclusive minimum length of the string to generate + * @param maxLengthExclusive the exclusive maximum length of the string to generate + * @return the random string + * @since 3.5 + */ + public String nextAscii(final int minLengthInclusive, final int maxLengthExclusive) { + return randomAscii(randomUtils().randomInt(minLengthInclusive, maxLengthExclusive)); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of characters which match the POSIX [:graph:] regular expression character + * class. This class contains all visible ASCII characters (i.e. anything except spaces and control characters). + *

+ * + * @param count the length of random string to create + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + * @since 3.5 + */ + public String nextGraph(final int count) { + return random(count, 33, 126, false, false); + } + + /** + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. + * + *

+ * Characters will be chosen from the set of \p{Graph} characters. + *

+ * + * @param minLengthInclusive the inclusive minimum length of the string to generate + * @param maxLengthExclusive the exclusive maximum length of the string to generate + * @return the random string + * @since 3.5 + */ + public String nextGraph(final int minLengthInclusive, final int maxLengthExclusive) { + return randomGraph(randomUtils().randomInt(minLengthInclusive, maxLengthExclusive)); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of numeric characters. + *

+ * + * @param count the length of random string to create + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + */ + public String nextNumeric(final int count) { + return random(count, false, true); + } + + /** + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. + * + *

+ * Characters will be chosen from the set of \p{Digit} characters. + *

+ * + * @param minLengthInclusive the inclusive minimum length of the string to generate + * @param maxLengthExclusive the exclusive maximum length of the string to generate + * @return the random string + * @since 3.5 + */ + public String nextNumeric(final int minLengthInclusive, final int maxLengthExclusive) { + return randomNumeric(randomUtils().randomInt(minLengthInclusive, maxLengthExclusive)); + } + + /** + * Creates a random string whose length is the number of characters specified. + * + *

+ * Characters will be chosen from the set of characters which match the POSIX [:print:] regular expression character + * class. This class includes all visible ASCII characters and spaces (i.e. anything except control characters). + *

+ * + * @param count the length of random string to create + * @return the random string + * @throws IllegalArgumentException if {@code count} < 0. + * @since 3.5 + * @since 3.16.0 + */ + public String nextPrint(final int count) { + return random(count, 32, 126, false, false); + } + + /** + * Creates a random string whose length is between the inclusive minimum and the exclusive maximum. + * + *

+ * Characters will be chosen from the set of \p{Print} characters. + *

+ * + * @param minLengthInclusive the inclusive minimum length of the string to generate + * @param maxLengthExclusive the exclusive maximum length of the string to generate + * @return the random string + * @since 3.16.0 + */ + public String nextPrint(final int minLengthInclusive, final int maxLengthExclusive) { + return randomPrint(randomUtils().randomInt(minLengthInclusive, maxLengthExclusive)); + } + + /** + * Gets the Random. + * + * @return the Random. + */ + private Random random() { + return randomUtils().random(); + } + + /** + * Gets the RandomUtils. + * + * @return the RandomUtils. + */ + private RandomUtils randomUtils() { + return random.get(); + } + + @Override + public String toString() { + return "RandomStringUtils [random=" + random() + "]"; } } diff --git a/src/main/java/org/apache/commons/lang3/RandomUtils.java b/src/main/java/org/apache/commons/lang3/RandomUtils.java index b3c652445..5a5851419 100644 --- a/src/main/java/org/apache/commons/lang3/RandomUtils.java +++ b/src/main/java/org/apache/commons/lang3/RandomUtils.java @@ -16,23 +16,78 @@ */ package org.apache.commons.lang3; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.Security; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Supplier; + +import org.apache.commons.lang3.exception.UncheckedException; /** - * Utility library that supplements the standard {@link Random} class. + * Supplements the standard {@link Random} class. + *

+ * Use {@link #secure()} to get the singleton instance based on {@link SecureRandom#getInstanceStrong()} which uses an + * algorithms/providers specified in the {@code securerandom.strongAlgorithms} {@link Security} property. + *

+ *

+ * Use {@link #insecure()} to get the singleton instance based on {@link ThreadLocalRandom#current()}; which is not + * cryptographically secure. + *

+ *

+ * Starting in version 3.15.0, this class uses {@link SecureRandom#getInstanceStrong()} for static methods. + *

+ *

+ * Starting in version 3.16.0, this class uses {@link #secure()} for static methods and adds {@link #insecure()}. + *

+ *

+ * Before version 3.15.0, this class used {@link ThreadLocalRandom#current()} for static methods, which is not + * cryptographically secure. + *

+ *

+ * Please note that the Apache Commons project provides a component dedicated to pseudo-random number generation, namely + * Commons RNG, that may be a better choice for + * applications with more stringent requirements (performance and/or correctness). + *

* - *

Please note that the Apache Commons project provides a component - * dedicated to pseudo-random number generation, namely - * Commons RNG, that may be - * a better choice for applications with more stringent requirements - * (performance and/or correctness).

- * - * @deprecated Use Apache Commons RNG's optimized UniformRandomProvider + * @see RandomStringUtils * @since 3.3 */ -@Deprecated public class RandomUtils { + private static RandomUtils INSECURE = new RandomUtils(ThreadLocalRandom::current); + + private static final Supplier SECURE_SUPPLIER = () -> RandomUtils.SECURE_RANDOM.get(); + + private static RandomUtils SECURE = new RandomUtils(SECURE_SUPPLIER); + + private static final ThreadLocal SECURE_RANDOM = ThreadLocal.withInitial(() -> { + try { + return SecureRandom.getInstanceStrong(); + } catch (final NoSuchAlgorithmException e) { + throw new UncheckedException(e); + } + }); + + /** + * Gets the singleton instance based on {@link ThreadLocalRandom#current()}; which is not cryptographically + * secure; use {@link #secure()} to use an algorithms/providers specified in the + * {@code securerandom.strongAlgorithms} {@link Security} property. + *

+ *

+ * The method {@link ThreadLocalRandom#current()} is called on-demand. + *

+ * + * @return the singleton instance based on {@link ThreadLocalRandom#current()}. + * @see ThreadLocalRandom#current() + * @see #secure() + * @since 3.16.0 + */ + static RandomUtils insecure() { + return INSECURE; + } + /** * Generates a random boolean value. * @@ -40,22 +95,18 @@ public class RandomUtils { * @since 3.5 */ public static boolean nextBoolean() { - return RandomStringUtils.random().nextBoolean(); + return secure().randomBoolean(); } /** * Generates an array of random bytes. * - * @param count - * the size of the returned array + * @param count the size of the returned array * @return the random byte array * @throws IllegalArgumentException if {@code count} is negative */ public static byte[] nextBytes(final int count) { - Validate.isTrue(count >= 0, "Count cannot be negative."); - final byte[] result = new byte[count]; - RandomStringUtils.random().nextBytes(result); - return result; + return secure().randomBytes(count); } /** @@ -66,29 +117,20 @@ public class RandomUtils { * @since 3.5 */ public static double nextDouble() { - return nextDouble(0, Double.MAX_VALUE); + return secure().randomDouble(); } /** * Generates a random double within the specified range. * - * @param startInclusive - * the smallest value that can be returned, must be non-negative - * @param endExclusive - * the upper bound (not included) - * @throws IllegalArgumentException - * if {@code startInclusive > endExclusive} or if - * {@code startInclusive} is negative + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative * @return the random double */ public static double nextDouble(final double startInclusive, final double endExclusive) { - Validate.isTrue(endExclusive >= startInclusive, - "Start value must be smaller or equal to end value."); - Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); - if (startInclusive == endExclusive) { - return startInclusive; - } - return startInclusive + (endExclusive - startInclusive) * RandomStringUtils.random().nextDouble(); + return secure().randomDouble(startInclusive, endExclusive); } /** @@ -99,29 +141,20 @@ public class RandomUtils { * @since 3.5 */ public static float nextFloat() { - return nextFloat(0, Float.MAX_VALUE); + return secure().randomFloat(); } /** * Generates a random float within the specified range. * - * @param startInclusive - * the smallest value that can be returned, must be non-negative - * @param endExclusive - * the upper bound (not included) - * @throws IllegalArgumentException - * if {@code startInclusive > endExclusive} or if - * {@code startInclusive} is negative + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative * @return the random float */ public static float nextFloat(final float startInclusive, final float endExclusive) { - Validate.isTrue(endExclusive >= startInclusive, - "Start value must be smaller or equal to end value."); - Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); - if (startInclusive == endExclusive) { - return startInclusive; - } - return startInclusive + (endExclusive - startInclusive) * RandomStringUtils.random().nextFloat(); + return secure().randomFloat(startInclusive, endExclusive); } /** @@ -132,29 +165,20 @@ public class RandomUtils { * @since 3.5 */ public static int nextInt() { - return nextInt(0, Integer.MAX_VALUE); + return secure().randomInt(); } /** * Generates a random integer within the specified range. * - * @param startInclusive - * the smallest value that can be returned, must be non-negative - * @param endExclusive - * the upper bound (not included) - * @throws IllegalArgumentException - * if {@code startInclusive > endExclusive} or if - * {@code startInclusive} is negative + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative * @return the random integer */ public static int nextInt(final int startInclusive, final int endExclusive) { - Validate.isTrue(endExclusive >= startInclusive, - "Start value must be smaller or equal to end value."); - Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); - if (startInclusive == endExclusive) { - return startInclusive; - } - return startInclusive + RandomStringUtils.random().nextInt(endExclusive - startInclusive); + return secure().randomInt(startInclusive, endExclusive); } /** @@ -165,24 +189,213 @@ public class RandomUtils { * @since 3.5 */ public static long nextLong() { + return secure().randomLong(); + } + + /** + * Generates a {@code long} value between 0 (inclusive) and the specified value (exclusive). + * + * @param n Bound on the random number to be returned. Must be positive. + * @return a random {@code long} value between 0 (inclusive) and {@code n} (exclusive). + */ + private static long nextLong(final long n) { + return secure().randomLong(n); + } + + /** + * Generates a random long within the specified range. + * + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative + * @return the random long + */ + public static long nextLong(final long startInclusive, final long endExclusive) { + return secure().randomLong(startInclusive, endExclusive); + } + + /** + * Gets the singleton instance based on {@link SecureRandom#getInstanceStrong()} which uses an algorithms/providers + * specified in the {@code securerandom.strongAlgorithms} {@link Security} property. + *

+ * The method {@link SecureRandom#getInstanceStrong()} is called on-demand. + *

+ * + * @return the singleton instance based on {@link SecureRandom#getInstanceStrong()}. + * @see SecureRandom#getInstanceStrong() + * @since 3.16.0 + */ + public static RandomUtils secure() { + return SECURE; + } + + static SecureRandom secureRandom() { + return SECURE_RANDOM.get(); + } + + private final Supplier random; + + /** + * {@link RandomUtils} instances should NOT be constructed in standard programming. Instead, the class should be + * used as {@code RandomUtils.nextBytes(5);}. + *

+ * This constructor is public to permit tools that require a JavaBean instance to operate. + *

+ * + * @deprecated TODO Make private in 4.0. + */ + @Deprecated + public RandomUtils() { + this(SECURE_SUPPLIER); + } + + private RandomUtils(final Supplier random) { + this.random = random; + } + + Random random() { + return random.get(); + } + + /** + * Generates a random boolean value. + * + * @return the random boolean + * @since 3.16.0 + */ + public boolean randomBoolean() { + return random().nextBoolean(); + } + + /** + * Generates an array of random bytes. + * + * @param count the size of the returned array + * @return the random byte array + * @throws IllegalArgumentException if {@code count} is negative + * @since 3.16.0 + */ + public byte[] randomBytes(final int count) { + Validate.isTrue(count >= 0, "Count cannot be negative."); + final byte[] result = new byte[count]; + random().nextBytes(result); + return result; + } + + /** + * Generates a random double between 0 (inclusive) and Double.MAX_VALUE (exclusive). + * + * @return the random double + * @see #nextDouble(double, double) + * @since 3.16.0 + */ + public double randomDouble() { + return nextDouble(0, Double.MAX_VALUE); + } + + /** + * Generates a random double within the specified range. + * + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative + * @return the random double + * @since 3.16.0 + */ + public double randomDouble(final double startInclusive, final double endExclusive) { + Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value."); + Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); + if (startInclusive == endExclusive) { + return startInclusive; + } + return startInclusive + (endExclusive - startInclusive) * random().nextDouble(); + } + + /** + * Generates a random float between 0 (inclusive) and Float.MAX_VALUE (exclusive). + * + * @return the random float + * @see #nextFloat(float, float) + * @since 3.16.0 + */ + public float randomFloat() { + return nextFloat(0, Float.MAX_VALUE); + } + + /** + * Generates a random float within the specified range. + * + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative + * @return the random float + */ + public float randomFloat(final float startInclusive, final float endExclusive) { + Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value."); + Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); + if (startInclusive == endExclusive) { + return startInclusive; + } + return startInclusive + (endExclusive - startInclusive) * random().nextFloat(); + } + + /** + * Generates a random int between 0 (inclusive) and Integer.MAX_VALUE (exclusive). + * + * @return the random integer + * @see #nextInt(int, int) + * @since 3.16.0 + */ + public int randomInt() { + return nextInt(0, Integer.MAX_VALUE); + } + + /** + * Generates a random integer within the specified range. + * + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative + * @return the random integer + * @since 3.16.0 + */ + public int randomInt(final int startInclusive, final int endExclusive) { + Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value."); + Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); + if (startInclusive == endExclusive) { + return startInclusive; + } + return startInclusive + random().nextInt(endExclusive - startInclusive); + } + + /** + * Generates a random long between 0 (inclusive) and Long.MAX_VALUE (exclusive). + * + * @return the random long + * @see #nextLong(long, long) + * @since 3.16.0 + */ + public long randomLong() { return nextLong(Long.MAX_VALUE); } /** - * Generates a {@code long} value between 0 (inclusive) and the specified - * value (exclusive). + * Generates a {@code long} value between 0 (inclusive) and the specified value (exclusive). * - * @param n Bound on the random number to be returned. Must be positive. - * @return a random {@code long} value between 0 (inclusive) and {@code n} - * (exclusive). + * @param n Bound on the random number to be returned. Must be positive. + * @return a random {@code long} value between 0 (inclusive) and {@code n} (exclusive). */ - private static long nextLong(final long n) { + private long randomLong(final long n) { // Extracted from o.a.c.rng.core.BaseProvider.nextLong(long) long bits; long val; do { - bits = RandomStringUtils.random().nextLong() >>> 1; - val = bits % n; + bits = random().nextLong() >>> 1; + val = bits % n; } while (bits - val + n - 1 < 0); return val; } @@ -190,18 +403,15 @@ public class RandomUtils { /** * Generates a random long within the specified range. * - * @param startInclusive - * the smallest value that can be returned, must be non-negative - * @param endExclusive - * the upper bound (not included) - * @throws IllegalArgumentException - * if {@code startInclusive > endExclusive} or if - * {@code startInclusive} is negative + * @param startInclusive the smallest value that can be returned, must be non-negative + * @param endExclusive the upper bound (not included) + * @throws IllegalArgumentException if {@code startInclusive > endExclusive} or if {@code startInclusive} is + * negative * @return the random long + * @since 3.16.0 */ - public static long nextLong(final long startInclusive, final long endExclusive) { - Validate.isTrue(endExclusive >= startInclusive, - "Start value must be smaller or equal to end value."); + public long randomLong(final long startInclusive, final long endExclusive) { + Validate.isTrue(endExclusive >= startInclusive, "Start value must be smaller or equal to end value."); Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative."); if (startInclusive == endExclusive) { return startInclusive; @@ -209,19 +419,9 @@ public class RandomUtils { return startInclusive + nextLong(endExclusive - startInclusive); } - /** - * {@link RandomUtils} instances should NOT be constructed in standard - * programming. Instead, the class should be used as - * {@code RandomUtils.nextBytes(5);}. - *

- * This constructor is public to permit tools that require a JavaBean - * instance to operate. - *

- * - * @deprecated TODO Make private in 4.0. - */ - @Deprecated - public RandomUtils() { - // empty + @Override + public String toString() { + return "RandomUtils [random=" + random() + "]"; } + } diff --git a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java index 2999d8ebc..d5c543ef5 100644 --- a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java @@ -24,19 +24,20 @@ import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.fail; -import java.lang.reflect.Constructor; -import java.lang.reflect.Modifier; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Random; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests {@link RandomStringUtils}. @@ -45,6 +46,10 @@ public class RandomStringUtilsTest extends AbstractLangTest { private static final int LOOP_COUNT = 1_000; + static Stream randomProvider() { + return Stream.of(RandomStringUtils.secure(), RandomStringUtils.insecure()); + } + /** * Computes Chi-Square statistic given observed and expected counts * @@ -85,11 +90,6 @@ public class RandomStringUtilsTest extends AbstractLangTest { @Test public void testConstructor() { assertNotNull(new RandomStringUtils()); - final Constructor[] cons = RandomStringUtils.class.getDeclaredConstructors(); - assertEquals(1, cons.length); - assertTrue(Modifier.isPublic(cons[0].getModifiers())); - assertTrue(Modifier.isPublic(RandomStringUtils.class.getModifiers())); - assertFalse(Modifier.isFinal(RandomStringUtils.class.getModifiers())); } @Test @@ -108,31 +108,118 @@ public class RandomStringUtilsTest extends AbstractLangTest { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(1, Integer.MIN_VALUE, -10, false, false, null)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testExceptionsRandom(final RandomStringUtils rsu) { + assertThrows(IllegalArgumentException.class, () -> rsu.next(-1)); + assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, true, true)); + assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, new char[] { 'a' })); + assertThrows(IllegalArgumentException.class, () -> rsu.next(1, new char[0])); + assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, "")); + assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, (String) null)); + assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, 'a', 'z', false, false)); + assertThrows(IllegalArgumentException.class, () -> rsu.next(-1, 'a', 'z', false, false, new char[] { 'a' })); + assertThrows(IllegalArgumentException.class, () -> rsu.next(8, 32, 48, false, true)); + assertThrows(IllegalArgumentException.class, () -> rsu.next(8, 32, 65, true, false)); + assertThrows(IllegalArgumentException.class, () -> rsu.next(1, Integer.MIN_VALUE, -10, false, false, null)); + } + @Test public void testExceptionsRandomAlphabetic() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAlphabetic(-1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testExceptionsRandomAlphabetic(final RandomStringUtils rsu) { + assertThrows(IllegalArgumentException.class, () -> rsu.nextAlphabetic(-1)); + } + @Test public void testExceptionsRandomAscii() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAscii(-1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testExceptionsRandomAscii(final RandomStringUtils rsu) { + assertThrows(IllegalArgumentException.class, () -> rsu.nextAscii(-1)); + } + @Test public void testExceptionsRandomGraph() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomGraph(-1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testExceptionsRandomGraph(final RandomStringUtils rsu) { + assertThrows(IllegalArgumentException.class, () -> rsu.nextGraph(-1)); + } + @Test public void testExceptionsRandomNumeric() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomNumeric(-1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testExceptionsRandomNumeric(final RandomStringUtils rsu) { + assertThrows(IllegalArgumentException.class, () -> rsu.nextNumeric(-1)); + } + @Test public void testExceptionsRandomPrint() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomPrint(-1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testExceptionsRandomPrint(final RandomStringUtils rsu) { + assertThrows(IllegalArgumentException.class, () -> rsu.nextPrint(-1)); + } + + /** + * Test homogeneity of random strings generated -- i.e., test that characters show up with expected frequencies in generated strings. Will fail randomly + * about 1 in 100,000 times. Repeated failures indicate a problem. + * + * @param rsu the instance to test. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testHomogeneity(final RandomStringUtils rsu) { + final String set = "abc"; + final char[] chars = set.toCharArray(); + final int[] counts = { 0, 0, 0 }; + final int[] expected = { 200, 200, 200 }; + for (int i = 0; i < 100; i++) { + final String gen = rsu.next(6, chars); + for (int j = 0; j < 6; j++) { + switch (gen.charAt(j)) { + case 'a': { + counts[0]++; + break; + } + case 'b': { + counts[1]++; + break; + } + case 'c': { + counts[2]++; + break; + } + default: { + fail("generated character not in set"); + } + } + } + } + // Perform chi-square test with degrees of freedom = 3-1 = 2, testing at 1e-5 level. + // This expects a failure rate of 1 in 100,000. + // critical value: from scipy.stats import chi2; chi2(2).isf(1e-5) + assertThat("test homogeneity -- will fail about 1 in 100,000 times", chiSquare(expected, counts), lessThan(23.025850929940457d)); + } + /** * Checks if the string got by {@link RandomStringUtils#random(int)} can be converted to UTF-8 and back without loss. * @@ -159,15 +246,44 @@ public class RandomStringUtilsTest extends AbstractLangTest { assertEquals(orig, copy); } + /** + * Checks if the string got by {@link RandomStringUtils#random(int)} can be converted to UTF-8 and back without loss. + * + * @param rsu the instance to test + * @see LANG-100 + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testLang100(final RandomStringUtils rsu) { + final int size = 5000; + final Charset charset = StandardCharsets.UTF_8; + final String orig = rsu.next(size); + final byte[] bytes = orig.getBytes(charset); + final String copy = new String(bytes, charset); + + // for a verbose compare: + for (int i = 0; i < orig.length() && i < copy.length(); i++) { + final char o = orig.charAt(i); + final char c = copy.charAt(i); + assertEquals(o, c, "differs at " + i + "(" + Integer.toHexString(Character.valueOf(o).hashCode()) + "," + + Integer.toHexString(Character.valueOf(c).hashCode()) + ")"); + } + // compare length also + assertEquals(orig.length(), copy.length()); + // just to be complete + assertEquals(orig, copy); + } + @Test public void testLANG805() { final long seedMillis = System.currentTimeMillis(); assertEquals("aaa", RandomStringUtils.random(3, 0, 0, false, false, new char[] { 'a' }, new Random(seedMillis))); } - @Test - public void testLANG807() { - final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(3, 5, 5, false, false)); + @ParameterizedTest + @MethodSource("randomProvider") + public void testLANG807(final RandomStringUtils rsu) { + final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> rsu.next(3, 5, 5, false, false)); final String msg = ex.getMessage(); assertTrue(msg.contains("start"), "Message (" + msg + ") must contain 'start'"); assertTrue(msg.contains("end"), "Message (" + msg + ") must contain 'end'"); @@ -193,6 +309,29 @@ public class RandomStringUtilsTest extends AbstractLangTest { } } + /** + * Make sure boundary alpha characters are generated by randomAlphabetic This test will fail randomly with probability = 4 * (51/52)**1000 ~ 1.58E-8 + * + * @param rsu the instance to test + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomAlphabetic(final RandomStringUtils rsu) { + final char[] testChars = { 'a', 'z', 'A', 'Z' }; + final boolean[] found = { false, false, false, false }; + for (int i = 0; i < LOOP_COUNT; i++) { + final String randString = rsu.nextAlphabetic(10); + for (int j = 0; j < testChars.length; j++) { + if (randString.indexOf(testChars[j]) > 0) { + found[j] = true; + } + } + } + for (int i = 0; i < testChars.length; i++) { + assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem "); + } + } + @Test public void testRandomAlphabeticRange() { final int expectedMinLengthInclusive = 1; @@ -218,6 +357,32 @@ public class RandomStringUtilsTest extends AbstractLangTest { assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomAlphabeticRange(final RandomStringUtils rsu) { + final int expectedMinLengthInclusive = 1; + final int expectedMaxLengthExclusive = 11; + final String pattern = "^\\p{Alpha}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$"; + + int maxCreatedLength = expectedMinLengthInclusive; + int minCreatedLength = expectedMaxLengthExclusive - 1; + for (int i = 0; i < LOOP_COUNT; i++) { + final String s = rsu.nextAlphabetic(expectedMinLengthInclusive, expectedMaxLengthExclusive); + assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1))); + assertTrue(s.matches(pattern), s); + + if (s.length() < minCreatedLength) { + minCreatedLength = s.length(); + } + + if (s.length() > maxCreatedLength) { + maxCreatedLength = s.length(); + } + } + assertThat("min generated, may fail randomly rarely", minCreatedLength, is(expectedMinLengthInclusive)); + assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1)); + } + /** * Make sure boundary alphanumeric characters are generated by randomAlphaNumeric This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7 */ @@ -238,6 +403,29 @@ public class RandomStringUtilsTest extends AbstractLangTest { } } + /** + * Make sure boundary alphanumeric characters are generated by randomAlphaNumeric This test will fail randomly with probability = 6 * (61/62)**1000 ~ 5.2E-7 + * + * @param rsu the instance to test + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomAlphaNumeric(final RandomStringUtils rsu) { + final char[] testChars = { 'a', 'z', 'A', 'Z', '0', '9' }; + final boolean[] found = { false, false, false, false, false, false }; + for (int i = 0; i < LOOP_COUNT; i++) { + final String randString = rsu.nextAlphanumeric(10); + for (int j = 0; j < testChars.length; j++) { + if (randString.indexOf(testChars[j]) > 0) { + found[j] = true; + } + } + } + for (int i = 0; i < testChars.length; i++) { + assertTrue(found[i], "alphanumeric character not generated in 1000 attempts: " + testChars[i] + " -- repeated failures indicate a problem "); + } + } + @Test public void testRandomAlphanumericRange() { final int expectedMinLengthInclusive = 1; @@ -264,15 +452,114 @@ public class RandomStringUtilsTest extends AbstractLangTest { } /** - * Make sure 32 and 127 are generated by randomNumeric This test will fail randomly with probability = 2*(95/96)**1000 ~ 5.7E-5 + * Test the implementation + * + * @param rsu the instance to test. */ - @Test - public void testRandomAscii() { + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomApis(final RandomStringUtils rsu) { + String r1 = rsu.next(50); + assertEquals(50, r1.length(), "random(50) length"); + String r2 = rsu.next(50); + assertEquals(50, r2.length(), "random(50) length"); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.nextAscii(50); + assertEquals(50, r1.length(), "randomAscii(50) length"); + for (int i = 0; i < r1.length(); i++) { + assertThat("char >= 32 && <= 127", (int) r1.charAt(i), allOf(greaterThanOrEqualTo(32), lessThanOrEqualTo(127))); + } + r2 = rsu.nextAscii(50); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.nextAlphabetic(50); + assertEquals(50, r1.length(), "randomAlphabetic(50)"); + for (int i = 0; i < r1.length(); i++) { + assertTrue(Character.isLetter(r1.charAt(i)) && !Character.isDigit(r1.charAt(i)), "r1 contains alphabetic"); + } + r2 = rsu.nextAlphabetic(50); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.nextAlphanumeric(50); + assertEquals(50, r1.length(), "randomAlphanumeric(50)"); + for (int i = 0; i < r1.length(); i++) { + assertTrue(Character.isLetterOrDigit(r1.charAt(i)), "r1 contains alphanumeric"); + } + r2 = rsu.nextAlphabetic(50); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.nextGraph(50); + assertEquals(50, r1.length(), "randomGraph(50) length"); + for (int i = 0; i < r1.length(); i++) { + assertTrue(r1.charAt(i) >= 33 && r1.charAt(i) <= 126, "char between 33 and 126"); + } + r2 = rsu.nextGraph(50); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.nextNumeric(50); + assertEquals(50, r1.length(), "randomNumeric(50)"); + for (int i = 0; i < r1.length(); i++) { + assertTrue(Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric"); + } + r2 = rsu.nextNumeric(50); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.nextPrint(50); + assertEquals(50, r1.length(), "randomPrint(50) length"); + for (int i = 0; i < r1.length(); i++) { + assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 126, "char between 32 and 126"); + } + r2 = rsu.nextPrint(50); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + String set = "abcdefg"; + r1 = rsu.next(50, set); + assertEquals(50, r1.length(), "random(50, \"abcdefg\")"); + for (int i = 0; i < r1.length(); i++) { + assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set"); + } + r2 = rsu.next(50, set); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.next(50, (String) null); + assertEquals(50, r1.length(), "random(50) length"); + r2 = rsu.next(50, (String) null); + assertEquals(50, r2.length(), "random(50) length"); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + set = "stuvwxyz"; + r1 = rsu.next(50, set.toCharArray()); + assertEquals(50, r1.length(), "random(50, \"stuvwxyz\")"); + for (int i = 0; i < r1.length(); i++) { + assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set"); + } + r2 = rsu.next(50, set); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.next(50, (char[]) null); + assertEquals(50, r1.length(), "random(50) length"); + r2 = rsu.next(50, (char[]) null); + assertEquals(50, r2.length(), "random(50) length"); + assertFalse(r1.equals(r2), "!r1.equals(r2)"); + + r1 = rsu.next(0); + assertEquals("", r1, "random(0).equals(\"\")"); + } + + /** + * Make sure 32 and 127 are generated by randomNumeric This test will fail randomly with probability = 2*(95/96)**1000 ~ 5.7E-5 + * + * @param rsu the instance to test + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomAscii(final RandomStringUtils rsu) { final char[] testChars = { (char) 32, (char) 126 }; final boolean[] found = { false, false }; // Test failures have been observed on GitHub builds with a 100 limit. for (int i = 0; i < LOOP_COUNT; i++) { - final String randString = RandomStringUtils.randomAscii(10); + final String randString = rsu.nextAscii(10); for (int j = 0; j < testChars.length; j++) { if (randString.indexOf(testChars[j]) > 0) { found[j] = true; @@ -284,8 +571,9 @@ public class RandomStringUtilsTest extends AbstractLangTest { } } - @Test - public void testRandomAsciiRange() { + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomAsciiRange(final RandomStringUtils rsu) { final int expectedMinLengthInclusive = 1; final int expectedMaxLengthExclusive = 11; final String pattern = "^\\p{ASCII}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$"; @@ -293,7 +581,7 @@ public class RandomStringUtilsTest extends AbstractLangTest { int maxCreatedLength = expectedMinLengthInclusive; int minCreatedLength = expectedMaxLengthExclusive - 1; for (int i = 0; i < LOOP_COUNT; i++) { - final String s = RandomStringUtils.randomAscii(expectedMinLengthInclusive, expectedMaxLengthExclusive); + final String s = rsu.nextAscii(expectedMinLengthInclusive, expectedMaxLengthExclusive); assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1))); assertTrue(s.matches(pattern), s); @@ -309,8 +597,9 @@ public class RandomStringUtilsTest extends AbstractLangTest { assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1)); } - @Test - public void testRandomGraphRange() { + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomGraphRange(final RandomStringUtils rsu) { final int expectedMinLengthInclusive = 1; final int expectedMaxLengthExclusive = 11; final String pattern = "^\\p{Graph}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$"; @@ -318,7 +607,7 @@ public class RandomStringUtilsTest extends AbstractLangTest { int maxCreatedLength = expectedMinLengthInclusive; int minCreatedLength = expectedMaxLengthExclusive - 1; for (int i = 0; i < LOOP_COUNT; i++) { - final String s = RandomStringUtils.randomGraph(expectedMinLengthInclusive, expectedMaxLengthExclusive); + final String s = rsu.nextGraph(expectedMinLengthInclusive, expectedMaxLengthExclusive); assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1))); assertTrue(s.matches(pattern), s); @@ -336,13 +625,16 @@ public class RandomStringUtilsTest extends AbstractLangTest { /** * Make sure '0' and '9' are generated by randomNumeric This test will fail randomly with probability = 2 * (9/10)**1000 ~ 3.5E-46 + * + * @param rsu the instance to test */ - @Test - public void testRandomNumeric() { + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomNumeric(final RandomStringUtils rsu) { final char[] testChars = { '0', '9' }; final boolean[] found = { false, false }; for (int i = 0; i < LOOP_COUNT; i++) { - final String randString = RandomStringUtils.randomNumeric(10); + final String randString = rsu.nextNumeric(10); for (int j = 0; j < testChars.length; j++) { if (randString.indexOf(testChars[j]) > 0) { found[j] = true; @@ -354,8 +646,9 @@ public class RandomStringUtilsTest extends AbstractLangTest { } } - @Test - public void testRandomNumericRange() { + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomNumericRange(final RandomStringUtils rsu) { final int expectedMinLengthInclusive = 1; final int expectedMaxLengthExclusive = 11; final String pattern = "^\\p{Digit}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$"; @@ -363,7 +656,7 @@ public class RandomStringUtilsTest extends AbstractLangTest { int maxCreatedLength = expectedMinLengthInclusive; int minCreatedLength = expectedMaxLengthExclusive - 1; for (int i = 0; i < LOOP_COUNT; i++) { - final String s = RandomStringUtils.randomNumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive); + final String s = rsu.nextNumeric(expectedMinLengthInclusive, expectedMaxLengthExclusive); assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1))); assertTrue(s.matches(pattern), s); @@ -380,7 +673,16 @@ public class RandomStringUtilsTest extends AbstractLangTest { } @Test - public void testRandomPrintRange() { + public void testRandomParameter() { + final long seedMillis = System.currentTimeMillis(); + final String r1 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis)); + final String r2 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis)); + assertEquals(r1, r2, "r1.equals(r2)"); + } + + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomPrintRange(final RandomStringUtils rsu) { final int expectedMinLengthInclusive = 1; final int expectedMaxLengthExclusive = 11; final String pattern = "^\\p{Print}{" + expectedMinLengthInclusive + ',' + expectedMaxLengthExclusive + "}$"; @@ -388,7 +690,7 @@ public class RandomStringUtilsTest extends AbstractLangTest { int maxCreatedLength = expectedMinLengthInclusive; int minCreatedLength = expectedMaxLengthExclusive - 1; for (int i = 0; i < LOOP_COUNT; i++) { - final String s = RandomStringUtils.randomPrint(expectedMinLengthInclusive, expectedMaxLengthExclusive); + final String s = rsu.nextPrint(expectedMinLengthInclusive, expectedMaxLengthExclusive); assertThat("within range", s.length(), allOf(greaterThanOrEqualTo(expectedMinLengthInclusive), lessThanOrEqualTo(expectedMaxLengthExclusive - 1))); assertTrue(s.matches(pattern), s); @@ -404,162 +706,29 @@ public class RandomStringUtilsTest extends AbstractLangTest { assertThat("max generated, may fail randomly rarely", maxCreatedLength, is(expectedMaxLengthExclusive - 1)); } - /** - * Test the implementation - */ - @Test - public void testRandomStringUtils() { - String r1 = RandomStringUtils.random(50); - assertEquals(50, r1.length(), "random(50) length"); - String r2 = RandomStringUtils.random(50); - assertEquals(50, r2.length(), "random(50) length"); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.randomAscii(50); - assertEquals(50, r1.length(), "randomAscii(50) length"); - for (int i = 0; i < r1.length(); i++) { - assertThat("char >= 32 && <= 127", (int) r1.charAt(i), allOf(greaterThanOrEqualTo(32), lessThanOrEqualTo(127))); - } - r2 = RandomStringUtils.randomAscii(50); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.randomAlphabetic(50); - assertEquals(50, r1.length(), "randomAlphabetic(50)"); - for (int i = 0; i < r1.length(); i++) { - assertTrue(Character.isLetter(r1.charAt(i)) && !Character.isDigit(r1.charAt(i)), "r1 contains alphabetic"); - } - r2 = RandomStringUtils.randomAlphabetic(50); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.randomAlphanumeric(50); - assertEquals(50, r1.length(), "randomAlphanumeric(50)"); - for (int i = 0; i < r1.length(); i++) { - assertTrue(Character.isLetterOrDigit(r1.charAt(i)), "r1 contains alphanumeric"); - } - r2 = RandomStringUtils.randomAlphabetic(50); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.randomGraph(50); - assertEquals(50, r1.length(), "randomGraph(50) length"); - for (int i = 0; i < r1.length(); i++) { - assertTrue(r1.charAt(i) >= 33 && r1.charAt(i) <= 126, "char between 33 and 126"); - } - r2 = RandomStringUtils.randomGraph(50); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.randomNumeric(50); - assertEquals(50, r1.length(), "randomNumeric(50)"); - for (int i = 0; i < r1.length(); i++) { - assertTrue(Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric"); - } - r2 = RandomStringUtils.randomNumeric(50); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.randomPrint(50); - assertEquals(50, r1.length(), "randomPrint(50) length"); - for (int i = 0; i < r1.length(); i++) { - assertTrue(r1.charAt(i) >= 32 && r1.charAt(i) <= 126, "char between 32 and 126"); - } - r2 = RandomStringUtils.randomPrint(50); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - String set = "abcdefg"; - r1 = RandomStringUtils.random(50, set); - assertEquals(50, r1.length(), "random(50, \"abcdefg\")"); - for (int i = 0; i < r1.length(); i++) { - assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set"); - } - r2 = RandomStringUtils.random(50, set); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.random(50, (String) null); - assertEquals(50, r1.length(), "random(50) length"); - r2 = RandomStringUtils.random(50, (String) null); - assertEquals(50, r2.length(), "random(50) length"); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - set = "stuvwxyz"; - r1 = RandomStringUtils.random(50, set.toCharArray()); - assertEquals(50, r1.length(), "random(50, \"stuvwxyz\")"); - for (int i = 0; i < r1.length(); i++) { - assertTrue(set.indexOf(r1.charAt(i)) > -1, "random char in set"); - } - r2 = RandomStringUtils.random(50, set); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - r1 = RandomStringUtils.random(50, (char[]) null); - assertEquals(50, r1.length(), "random(50) length"); - r2 = RandomStringUtils.random(50, (char[]) null); - assertEquals(50, r2.length(), "random(50) length"); - assertFalse(r1.equals(r2), "!r1.equals(r2)"); - - final long seedMillis = System.currentTimeMillis(); - r1 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis)); - r2 = RandomStringUtils.random(50, 0, 0, true, true, null, new Random(seedMillis)); - assertEquals(r1, r2, "r1.equals(r2)"); - - r1 = RandomStringUtils.random(0); - assertEquals("", r1, "random(0).equals(\"\")"); - } - - /** - * Test homogeneity of random strings generated -- i.e., test that characters show up with expected frequencies in generated strings. Will fail randomly - * about 1 in 100,000 times. Repeated failures indicate a problem. - */ - @Test - public void testRandomStringUtilsHomog() { - final String set = "abc"; - final char[] chars = set.toCharArray(); - final int[] counts = { 0, 0, 0 }; - final int[] expected = { 200, 200, 200 }; - for (int i = 0; i < 100; i++) { - final String gen = RandomStringUtils.random(6, chars); - for (int j = 0; j < 6; j++) { - switch (gen.charAt(j)) { - case 'a': { - counts[0]++; - break; - } - case 'b': { - counts[1]++; - break; - } - case 'c': { - counts[2]++; - break; - } - default: { - fail("generated character not in set"); - } - } - } - } - // Perform chi-square test with degrees of freedom = 3-1 = 2, testing at 1e-5 level. - // This expects a failure rate of 1 in 100,000. - // critical value: from scipy.stats import chi2; chi2(2).isf(1e-5) - assertThat("test homogeneity -- will fail about 1 in 100,000 times", chiSquare(expected, counts), lessThan(23.025850929940457d)); - } - /** * Test {@code RandomStringUtils.random} works appropriately when chars specified. + * + * @param rsu the instance to test. */ - @Test - void testRandomWithChars() { + @ParameterizedTest + @MethodSource("randomProvider") + public void testRandomWithChars(final RandomStringUtils rsu) { final char[] digitChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; String r1, r2, r3; - r1 = RandomStringUtils.random(50, 0, 0, true, true, digitChars); + r1 = rsu.next(50, 0, 0, true, true, digitChars); assertEquals(50, r1.length(), "randomNumeric(50)"); for (int i = 0; i < r1.length(); i++) { assertTrue( Character.isDigit(r1.charAt(i)) && !Character.isLetter(r1.charAt(i)), "r1 contains numeric"); } - r2 = RandomStringUtils.randomNumeric(50); + r2 = rsu.nextNumeric(50); assertNotEquals(r1, r2); - r3 = RandomStringUtils.random(50, 0, 0, true, true, digitChars); + r3 = rsu.next(50, 0, 0, true, true, digitChars); assertNotEquals(r1, r3); assertNotEquals(r2, r3); } diff --git a/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java index eda6dc712..7768d5f00 100644 --- a/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/RandomUtilsTest.java @@ -22,16 +22,16 @@ import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.lang.reflect.Constructor; -import java.lang.reflect.Modifier; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * Tests for {@link RandomUtils} @@ -43,6 +43,10 @@ public class RandomUtilsTest extends AbstractLangTest { */ private static final double DELTA = 1e-5; + static Stream randomProvider() { + return Stream.of(RandomUtils.secure(), RandomUtils.insecure()); + } + /** * Tests next boolean */ @@ -52,14 +56,16 @@ public class RandomUtilsTest extends AbstractLangTest { assertTrue(result || !result); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testBoolean(final RandomUtils ru) { + final boolean result = ru.randomBoolean(); + assertTrue(result || !result); + } + @Test public void testConstructor() { assertNotNull(new RandomUtils()); - final Constructor[] cons = RandomUtils.class.getDeclaredConstructors(); - assertEquals(1, cons.length); - assertTrue(Modifier.isPublic(cons[0].getModifiers())); - assertTrue(Modifier.isPublic(RandomUtils.class.getModifiers())); - assertFalse(Modifier.isFinal(RandomUtils.class.getModifiers())); } /** @@ -71,6 +77,13 @@ public class RandomUtilsTest extends AbstractLangTest { assertTrue(result >= 0 && result <= Double.MAX_VALUE); // TODO: should be = 0 && result <= Double.MAX_VALUE); // TODO: should be = 0f && result <= Float.MAX_VALUE); // TODO: should be = 0f && result <= Float.MAX_VALUE); // TODO: should be = 0 && result < Integer.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0), lessThan(Integer.MAX_VALUE))); } + /** + * Tests extreme range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testExtremeRangeInt(final RandomUtils ru) { + final int result = ru.randomInt(0, Integer.MAX_VALUE); + assertThat("result >= 0 && result < Integer.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0), lessThan(Integer.MAX_VALUE))); + } + /** * Tests extreme range. */ @@ -98,6 +131,16 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("result >= 0 && result < Long.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE))); } + /** + * Tests extreme range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testExtremeRangeLong(final RandomUtils ru) { + final long result = ru.randomLong(0, Long.MAX_VALUE); + assertThat("result >= 0 && result < Long.MAX_VALUE", result, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE))); + } + /** * Test a large value for long. A previous implementation using * {@link RandomUtils#nextDouble(double, double)} could generate a value equal @@ -122,6 +165,31 @@ public class RandomUtilsTest extends AbstractLangTest { } } + /** + * Test a large value for long. A previous implementation using + * {@link RandomUtils#nextDouble(double, double)} could generate a value equal + * to the upper limit. + * + *
+     * return (long) nextDouble(startInclusive, endExclusive);
+     * 
+ * + *

See LANG-1592.

+ */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testLargeValueRangeLong(final RandomUtils ru) { + final long startInclusive = 12900000000001L; + final long endExclusive = 12900000000016L; + // Note: The method using 'return (long) nextDouble(startInclusive, endExclusive)' + // takes thousands of calls to generate an error. This size loop fails most + // of the time with the previous method. + final int n = (int) (endExclusive - startInclusive) * 1000; + for (int i = 0; i < n; i++) { + assertNotEquals(endExclusive, ru.randomLong(startInclusive, endExclusive)); + } + } + /** * Tests random byte array. */ @@ -131,11 +199,27 @@ public class RandomUtilsTest extends AbstractLangTest { assertEquals(20, result.length); } + /** + * Tests random byte array. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextBytes(final RandomUtils ru) { + final byte[] result = ru.randomBytes(20); + assertEquals(20, result.length); + } + @Test public void testNextBytesNegative() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextBytes(-1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextBytesNegative(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomBytes(-1)); + } + /** * Tests next double range. */ @@ -145,11 +229,27 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("result >= 33d && result < 42d", result, allOf(greaterThanOrEqualTo(33d), lessThan(42d))); } + /** + * Tests next double range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextDouble(final RandomUtils ru) { + final double result = ru.randomDouble(33d, 42d); + assertThat("result >= 33d && result < 42d", result, allOf(greaterThanOrEqualTo(33d), lessThan(42d))); + } + @Test public void testNextDoubleLowerGreaterUpper() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextDouble(2, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextDoubleLowerGreaterUpper(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomDouble(2, 1)); + } + /** * Test next double range with minimal range. */ @@ -158,11 +258,26 @@ public class RandomUtilsTest extends AbstractLangTest { assertEquals(42.1, RandomUtils.nextDouble(42.1, 42.1), DELTA); } + /** + * Test next double range with minimal range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextDoubleMinimalRange(final RandomUtils ru) { + assertEquals(42.1, ru.randomDouble(42.1, 42.1), DELTA); + } + @Test public void testNextDoubleNegative() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextDouble(-1, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextDoubleNegative(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomDouble(-1, 1)); + } + /** * Tests next double range, random result. */ @@ -172,6 +287,16 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("randomResult >= 0 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0d), lessThan(Double.MAX_VALUE))); } + /** + * Tests next double range, random result. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextDoubleRandomResult(final RandomUtils ru) { + final double randomResult = ru.randomDouble(); + assertThat("randomResult >= 0 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0d), lessThan(Double.MAX_VALUE))); + } + /** * Tests next float range. */ @@ -181,11 +306,27 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("result >= 33f && result < 42f", result, allOf(greaterThanOrEqualTo(33f), lessThan(42f))); } + /** + * Tests next float range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextFloat(final RandomUtils ru) { + final float result = ru.randomFloat(33f, 42f); + assertThat("result >= 33f && result < 42f", result, allOf(greaterThanOrEqualTo(33f), lessThan(42f))); + } + @Test public void testNextFloatLowerGreaterUpper() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextFloat(2, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextFloatLowerGreaterUpper(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomFloat(2, 1)); + } + /** * Test next float range with minimal range. */ @@ -194,11 +335,26 @@ public class RandomUtilsTest extends AbstractLangTest { assertEquals(42.1f, RandomUtils.nextFloat(42.1f, 42.1f), DELTA); } + /** + * Test next float range with minimal range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextFloatMinimalRange(final RandomUtils ru) { + assertEquals(42.1f, ru.randomFloat(42.1f, 42.1f), DELTA); + } + @Test public void testNextFloatNegative() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextFloat(-1, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextFloatNegative(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomFloat(-1, 1)); + } + /** * Tests next float range, random result. */ @@ -208,6 +364,16 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("randomResult >= 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0f), lessThan(Float.MAX_VALUE))); } + /** + * Tests next float range, random result. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextFloatRandomResult(final RandomUtils ru) { + final float randomResult = ru.randomFloat(); + assertThat("randomResult >= 0 && randomResult < Double.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0f), lessThan(Float.MAX_VALUE))); + } + /** * Tests next int range. */ @@ -217,11 +383,27 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("result >= 33 && result < 42", result, allOf(greaterThanOrEqualTo(33), lessThan(42))); } + /** + * Tests next int range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextInt(final RandomUtils ru) { + final int result = ru.randomInt(33, 42); + assertThat("result >= 33 && result < 42", result, allOf(greaterThanOrEqualTo(33), lessThan(42))); + } + @Test public void testNextIntLowerGreaterUpper() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextInt(2, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextIntLowerGreaterUpper(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomInt(2, 1)); + } + /** * Test next int range with minimal range. */ @@ -230,11 +412,26 @@ public class RandomUtilsTest extends AbstractLangTest { assertEquals(42, RandomUtils.nextInt(42, 42)); } + /** + * Test next int range with minimal range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextIntMinimalRange(final RandomUtils ru) { + assertEquals(42, ru.randomInt(42, 42)); + } + @Test public void testNextIntNegative() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextInt(-1, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextIntNegative(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomInt(-1, 1)); + } + /** * Tests next int range, random result. */ @@ -245,6 +442,17 @@ public class RandomUtilsTest extends AbstractLangTest { assertTrue(randomResult < Integer.MAX_VALUE); } + /** + * Tests next int range, random result. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextIntRandomResult(final RandomUtils ru) { + final int randomResult = ru.randomInt(); + assertTrue(randomResult > 0); + assertTrue(randomResult < Integer.MAX_VALUE); + } + /** * Tests next long range. */ @@ -254,11 +462,27 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("result >= 33L && result < 42L", result, allOf(greaterThanOrEqualTo(33L), lessThan(42L))); } + /** + * Tests next long range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextLong(final RandomUtils ru) { + final long result = ru.randomLong(33L, 42L); + assertThat("result >= 33L && result < 42L", result, allOf(greaterThanOrEqualTo(33L), lessThan(42L))); + } + @Test public void testNextLongLowerGreaterUpper() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextLong(2, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextLongLowerGreaterUpper(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomLong(2, 1)); + } + /** * Test next long range with minimal range. */ @@ -267,11 +491,26 @@ public class RandomUtilsTest extends AbstractLangTest { assertEquals(42L, RandomUtils.nextLong(42L, 42L)); } + /** + * Test next long range with minimal range. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextLongMinimalRange(final RandomUtils ru) { + assertEquals(42L, ru.randomLong(42L, 42L)); + } + @Test public void testNextLongNegative() { assertThrows(IllegalArgumentException.class, () -> RandomUtils.nextLong(-1, 1)); } + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextLongNegative(final RandomUtils ru) { + assertThrows(IllegalArgumentException.class, () -> ru.randomLong(-1, 1)); + } + /** * Tests next long range, random result. */ @@ -281,6 +520,16 @@ public class RandomUtilsTest extends AbstractLangTest { assertThat("randomResult >= 0 && randomResult < Long.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE))); } + /** + * Tests next long range, random result. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testNextLongRandomResult(final RandomUtils ru) { + final long randomResult = ru.randomLong(); + assertThat("randomResult >= 0 && randomResult < Long.MAX_VALUE", randomResult, allOf(greaterThanOrEqualTo(0L), lessThan(Long.MAX_VALUE))); + } + /** * Tests a zero byte array length. */ @@ -288,4 +537,13 @@ public class RandomUtilsTest extends AbstractLangTest { public void testZeroLengthNextBytes() { assertArrayEquals(new byte[0], RandomUtils.nextBytes(0)); } + + /** + * Tests a zero byte array length. + */ + @ParameterizedTest + @MethodSource("randomProvider") + public void testZeroLengthNextBytes(final RandomUtils ru) { + assertArrayEquals(new byte[0], ru.randomBytes(0)); + } }