460 lines
17 KiB
Java
460 lines
17 KiB
Java
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.apache.commons.text;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
import org.apache.commons.lang3.ArrayUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.Validate;
|
|
|
|
/**
|
|
* Generates random Unicode strings containing the specified number of code points.
|
|
* Instances are created using a builder class, which allows the
|
|
* callers to define the properties of the generator. See the documentation for the
|
|
* {@link Builder} class to see available properties.
|
|
*
|
|
* <pre>
|
|
* // Generates a 20 code point string, using only the letters a-z
|
|
* RandomStringGenerator generator = new RandomStringGenerator.Builder()
|
|
* .withinRange('a', 'z').build();
|
|
* String randomLetters = generator.generate(20);
|
|
* </pre>
|
|
* <pre>
|
|
* // Using Apache Commons RNG for randomness
|
|
* UniformRandomProvider rng = RandomSource.create(...);
|
|
* // Generates a 20 code point string, using only the letters a-z
|
|
* RandomStringGenerator generator = new RandomStringGenerator.Builder()
|
|
* .withinRange('a', 'z')
|
|
* .usingRandom(rng::nextInt) // uses Java 8 syntax
|
|
* .build();
|
|
* String randomLetters = generator.generate(20);
|
|
* </pre>
|
|
* <p>
|
|
* {@code RandomStringGenerator} instances are thread-safe when using the
|
|
* default random number generator (RNG). If a custom RNG is set by calling the method
|
|
* {@link Builder#usingRandom(TextRandomProvider) Builder.usingRandom(TextRandomProvider)}, thread-safety
|
|
* must be ensured externally.
|
|
* </p>
|
|
* @since 1.1
|
|
*/
|
|
public final class RandomStringGenerator {
|
|
|
|
/**
|
|
* A builder for generating {@code RandomStringGenerator} instances.
|
|
*
|
|
* <p>The behavior of a generator is controlled by properties set by this
|
|
* builder. Each property has a default value, which can be overridden by
|
|
* calling the methods defined in this class, prior to calling {@link #build()}.</p>
|
|
*
|
|
* <p>All the property setting methods return the {@code Builder} instance to allow for method chaining.</p>
|
|
*
|
|
* <p>The minimum and maximum code point values are defined using {@link #withinRange(int, int)}. The
|
|
* default values are {@code 0} and {@link Character#MAX_CODE_POINT} respectively.</p>
|
|
*
|
|
* <p>The source of randomness can be set using {@link #usingRandom(TextRandomProvider)},
|
|
* otherwise {@link ThreadLocalRandom} is used.</p>
|
|
*
|
|
* <p>The type of code points returned can be filtered using {@link #filteredBy(CharacterPredicate...)},
|
|
* which defines a collection of tests that are applied to the randomly generated code points.
|
|
* The code points will only be included in the result if they pass at least one of the tests.
|
|
* Some commonly used predicates are provided by the {@link CharacterPredicates} enum.</p>
|
|
*
|
|
* <p>This class is not thread safe.</p>
|
|
* @since 1.1
|
|
*/
|
|
public static class Builder implements org.apache.commons.text.Builder<RandomStringGenerator> {
|
|
|
|
/**
|
|
* The default maximum code point allowed: {@link Character#MAX_CODE_POINT}
|
|
* ({@value}).
|
|
*/
|
|
public static final int DEFAULT_MAXIMUM_CODE_POINT = Character.MAX_CODE_POINT;
|
|
|
|
/**
|
|
* The default string length produced by this builder: {@value}.
|
|
*/
|
|
public static final int DEFAULT_LENGTH = 0;
|
|
|
|
/**
|
|
* The default minimum code point allowed: {@value}.
|
|
*/
|
|
public static final int DEFAULT_MINIMUM_CODE_POINT = 0;
|
|
|
|
/**
|
|
* The minimum code point allowed.
|
|
*/
|
|
private int minimumCodePoint = DEFAULT_MINIMUM_CODE_POINT;
|
|
|
|
/**
|
|
* The maximum code point allowed.
|
|
*/
|
|
private int maximumCodePoint = DEFAULT_MAXIMUM_CODE_POINT;
|
|
|
|
/**
|
|
* Filters for code points.
|
|
*/
|
|
private Set<CharacterPredicate> inclusivePredicates;
|
|
|
|
/**
|
|
* The source of randomness.
|
|
*/
|
|
private TextRandomProvider random;
|
|
|
|
/**
|
|
* The source of provided characters.
|
|
*/
|
|
private List<Character> characterList;
|
|
|
|
/**
|
|
* Builds the {@code RandomStringGenerator} using the properties specified.
|
|
*
|
|
* @return The configured {@code RandomStringGenerator}
|
|
*/
|
|
@Override
|
|
public RandomStringGenerator build() {
|
|
return new RandomStringGenerator(minimumCodePoint, maximumCodePoint, inclusivePredicates,
|
|
random, characterList);
|
|
}
|
|
|
|
/**
|
|
* Limits the characters in the generated string to those that match at
|
|
* least one of the predicates supplied.
|
|
*
|
|
* <p>
|
|
* Passing {@code null} or an empty array to this method will revert to the
|
|
* default behavior of allowing any character. Multiple calls to this
|
|
* method will replace the previously stored predicates.
|
|
* </p>
|
|
*
|
|
* @param predicates
|
|
* the predicates, may be {@code null} or empty
|
|
* @return {@code this}, to allow method chaining
|
|
*/
|
|
public Builder filteredBy(final CharacterPredicate... predicates) {
|
|
if (ArrayUtils.isEmpty(predicates)) {
|
|
inclusivePredicates = null;
|
|
return this;
|
|
}
|
|
|
|
if (inclusivePredicates == null) {
|
|
inclusivePredicates = new HashSet<>();
|
|
} else {
|
|
inclusivePredicates.clear();
|
|
}
|
|
|
|
Collections.addAll(inclusivePredicates, predicates);
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Limits the characters in the generated string to those who match at
|
|
* supplied list of Character.
|
|
*
|
|
* <p>
|
|
* Passing {@code null} or an empty array to this method will revert to the
|
|
* default behavior of allowing any character. Multiple calls to this
|
|
* method will replace the previously stored Character.
|
|
* </p>
|
|
*
|
|
* @param chars set of predefined Characters for random string generation
|
|
* the Character can be, may be {@code null} or empty
|
|
* @return {@code this}, to allow method chaining
|
|
* @since 1.2
|
|
*/
|
|
public Builder selectFrom(final char... chars) {
|
|
characterList = new ArrayList<>();
|
|
for (final char c : chars) {
|
|
characterList.add(c);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Overrides the default source of randomness. It is highly
|
|
* recommended that a random number generator library like
|
|
* <a href="https://commons.apache.org/proper/commons-rng/">Apache Commons RNG</a>
|
|
* be used to provide the random number generation.
|
|
*
|
|
* <p>
|
|
* When using Java 8 or later, {@link TextRandomProvider} is a
|
|
* functional interface and need not be explicitly implemented:
|
|
* </p>
|
|
* <pre>
|
|
* {@code
|
|
* UniformRandomProvider rng = RandomSource.create(...);
|
|
* RandomStringGenerator gen = new RandomStringGenerator.Builder()
|
|
* .usingRandom(rng::nextInt)
|
|
* // additional builder calls as needed
|
|
* .build();
|
|
* }
|
|
* </pre>
|
|
*
|
|
* <p>
|
|
* Passing {@code null} to this method will revert to the default source of
|
|
* randomness.
|
|
* </p>
|
|
*
|
|
* @param random
|
|
* the source of randomness, may be {@code null}
|
|
* @return {@code this}, to allow method chaining
|
|
*/
|
|
public Builder usingRandom(final TextRandomProvider random) {
|
|
this.random = random;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the array of minimum and maximum char allowed in the
|
|
* generated string.
|
|
*
|
|
* For example:
|
|
* <pre>
|
|
* {@code
|
|
* char [][] pairs = {{'0','9'}};
|
|
* char [][] pairs = {{'a','z'}};
|
|
* char [][] pairs = {{'a','z'},{'0','9'}};
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @param pairs array of characters array, expected is to pass min, max pairs through this arg.
|
|
* @return {@code this}, to allow method chaining.
|
|
*/
|
|
public Builder withinRange(final char[]... pairs) {
|
|
characterList = new ArrayList<>();
|
|
for (final char[] pair : pairs) {
|
|
Validate.isTrue(pair.length == 2,
|
|
"Each pair must contain minimum and maximum code point");
|
|
final int minimumCodePoint = pair[0];
|
|
final int maximumCodePoint = pair[1];
|
|
Validate.isTrue(minimumCodePoint <= maximumCodePoint,
|
|
"Minimum code point %d is larger than maximum code point %d", minimumCodePoint, maximumCodePoint);
|
|
|
|
for (int index = minimumCodePoint; index <= maximumCodePoint; index++) {
|
|
characterList.add((char) index);
|
|
}
|
|
}
|
|
return this;
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the minimum and maximum code points allowed in the
|
|
* generated string.
|
|
*
|
|
* @param minimumCodePoint
|
|
* the smallest code point allowed (inclusive)
|
|
* @param maximumCodePoint
|
|
* the largest code point allowed (inclusive)
|
|
* @return {@code this}, to allow method chaining
|
|
* @throws IllegalArgumentException
|
|
* if {@code maximumCodePoint >}
|
|
* {@link Character#MAX_CODE_POINT}
|
|
* @throws IllegalArgumentException
|
|
* if {@code minimumCodePoint < 0}
|
|
* @throws IllegalArgumentException
|
|
* if {@code minimumCodePoint > maximumCodePoint}
|
|
*/
|
|
public Builder withinRange(final int minimumCodePoint, final int maximumCodePoint) {
|
|
Validate.isTrue(minimumCodePoint <= maximumCodePoint,
|
|
"Minimum code point %d is larger than maximum code point %d", minimumCodePoint, maximumCodePoint);
|
|
Validate.isTrue(minimumCodePoint >= 0, "Minimum code point %d is negative", minimumCodePoint);
|
|
Validate.isTrue(maximumCodePoint <= Character.MAX_CODE_POINT,
|
|
"Value %d is larger than Character.MAX_CODE_POINT.", maximumCodePoint);
|
|
|
|
this.minimumCodePoint = minimumCodePoint;
|
|
this.maximumCodePoint = maximumCodePoint;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The smallest allowed code point (inclusive).
|
|
*/
|
|
private final int minimumCodePoint;
|
|
|
|
/**
|
|
* The largest allowed code point (inclusive).
|
|
*/
|
|
private final int maximumCodePoint;
|
|
|
|
/**
|
|
* Filters for code points.
|
|
*/
|
|
private final Set<CharacterPredicate> inclusivePredicates;
|
|
|
|
/**
|
|
* The source of randomness for this generator.
|
|
*/
|
|
private final TextRandomProvider random;
|
|
|
|
/**
|
|
* The source of provided characters.
|
|
*/
|
|
private final List<Character> characterList;
|
|
|
|
/**
|
|
* Constructs the generator.
|
|
*
|
|
* @param minimumCodePoint
|
|
* smallest allowed code point (inclusive)
|
|
* @param maximumCodePoint
|
|
* largest allowed code point (inclusive)
|
|
* @param inclusivePredicates
|
|
* filters for code points
|
|
* @param random
|
|
* source of randomness
|
|
* @param characterList list of predefined set of characters.
|
|
*/
|
|
private RandomStringGenerator(final int minimumCodePoint, final int maximumCodePoint,
|
|
final Set<CharacterPredicate> inclusivePredicates, final TextRandomProvider random,
|
|
final List<Character> characterList) {
|
|
this.minimumCodePoint = minimumCodePoint;
|
|
this.maximumCodePoint = maximumCodePoint;
|
|
this.inclusivePredicates = inclusivePredicates;
|
|
this.random = random;
|
|
this.characterList = characterList;
|
|
}
|
|
|
|
/**
|
|
* Generates a random string, containing the specified number of code points.
|
|
*
|
|
* <p>
|
|
* Code points are randomly selected between the minimum and maximum values defined
|
|
* in the generator.
|
|
* Surrogate and private use characters are not returned, although the
|
|
* resulting string may contain pairs of surrogates that together encode a
|
|
* supplementary character.
|
|
* </p>
|
|
* <p>
|
|
* Note: the number of {@code char} code units generated will exceed
|
|
* {@code length} if the string contains supplementary characters. See the
|
|
* {@link Character} documentation to understand how Java stores Unicode
|
|
* values.
|
|
* </p>
|
|
*
|
|
* @param length
|
|
* the number of code points to generate
|
|
* @return The generated string
|
|
* @throws IllegalArgumentException
|
|
* if {@code length < 0}
|
|
*/
|
|
public String generate(final int length) {
|
|
if (length == 0) {
|
|
return StringUtils.EMPTY;
|
|
}
|
|
Validate.isTrue(length > 0, "Length %d is smaller than zero.", length);
|
|
|
|
final StringBuilder builder = new StringBuilder(length);
|
|
long remaining = length;
|
|
|
|
do {
|
|
final int codePoint;
|
|
if (characterList != null && !characterList.isEmpty()) {
|
|
codePoint = generateRandomNumber(characterList);
|
|
} else {
|
|
codePoint = generateRandomNumber(minimumCodePoint, maximumCodePoint);
|
|
}
|
|
switch (Character.getType(codePoint)) {
|
|
case Character.UNASSIGNED:
|
|
case Character.PRIVATE_USE:
|
|
case Character.SURROGATE:
|
|
continue;
|
|
default:
|
|
}
|
|
|
|
if (inclusivePredicates != null) {
|
|
boolean matchedFilter = false;
|
|
for (final CharacterPredicate predicate : inclusivePredicates) {
|
|
if (predicate.test(codePoint)) {
|
|
matchedFilter = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!matchedFilter) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
builder.appendCodePoint(codePoint);
|
|
remaining--;
|
|
|
|
} while (remaining != 0);
|
|
|
|
return builder.toString();
|
|
}
|
|
|
|
/**
|
|
* Generates a random string, containing between the minimum (inclusive) and the maximum (inclusive)
|
|
* number of code points.
|
|
*
|
|
* @param minLengthInclusive
|
|
* the minimum (inclusive) number of code points to generate
|
|
* @param maxLengthInclusive
|
|
* the maximum (inclusive) number of code points to generate
|
|
* @return The generated string
|
|
* @throws IllegalArgumentException
|
|
* if {@code minLengthInclusive < 0}, or {@code maxLengthInclusive < minLengthInclusive}
|
|
* @see RandomStringGenerator#generate(int)
|
|
* @since 1.2
|
|
*/
|
|
public String generate(final int minLengthInclusive, final int maxLengthInclusive) {
|
|
Validate.isTrue(minLengthInclusive >= 0, "Minimum length %d is smaller than zero.", minLengthInclusive);
|
|
Validate.isTrue(minLengthInclusive <= maxLengthInclusive,
|
|
"Maximum length %d is smaller than minimum length %d.", maxLengthInclusive, minLengthInclusive);
|
|
return generate(generateRandomNumber(minLengthInclusive, maxLengthInclusive));
|
|
}
|
|
|
|
/**
|
|
* Generates a random number within a range, using a {@link ThreadLocalRandom} instance
|
|
* or the user-supplied source of randomness.
|
|
*
|
|
* @param minInclusive
|
|
* the minimum value allowed
|
|
* @param maxInclusive
|
|
* the maximum value allowed
|
|
* @return The random number.
|
|
*/
|
|
private int generateRandomNumber(final int minInclusive, final int maxInclusive) {
|
|
if (random != null) {
|
|
return random.nextInt(maxInclusive - minInclusive + 1) + minInclusive;
|
|
}
|
|
return ThreadLocalRandom.current().nextInt(minInclusive, maxInclusive + 1);
|
|
}
|
|
|
|
/**
|
|
* Generates a random number within a range, using a {@link ThreadLocalRandom} instance
|
|
* or the user-supplied source of randomness.
|
|
*
|
|
* @param characterList predefined char list.
|
|
* @return The random number.
|
|
*/
|
|
private int generateRandomNumber(final List<Character> characterList) {
|
|
final int listSize = characterList.size();
|
|
if (random != null) {
|
|
return String.valueOf(characterList.get(random.nextInt(listSize))).codePointAt(0);
|
|
}
|
|
return String.valueOf(characterList.get(ThreadLocalRandom.current().nextInt(0, listSize))).codePointAt(0);
|
|
}
|
|
}
|