This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-text.git
commit 91e7eac4b51c3dc08262285a83e6d4831f8d2410 Author: Gary D. Gregory <garydgreg...@gmail.com> AuthorDate: Sun Jul 20 08:41:32 2025 -0400 Add org.apache.commons.text.RandomStringGenerator.Builder.setAccumulate(boolean) This is instead of breaking compatibility with https://github.com/apache/commons-text/pull/125 --- src/changes/changes.xml | 1 + .../apache/commons/text/RandomStringGenerator.java | 63 +++++++++++++++++----- .../commons/text/RandomStringGeneratorTest.java | 54 +++++++++++++++++-- 3 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 272f878d..302829a0 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -61,6 +61,7 @@ The <action> type attribute can be add,update,fix,remove. <action type="add" dev="ggregory" due-to="Gary Gregory">Interface TextRandomProvider extends IntUnaryOperator.</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add RandomStringGenerator.Builder.usingRandom(IntUnaryOperator).</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add PMD check to default Maven goal.</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add org.apache.commons.text.RandomStringGenerator.Builder.setAccumulate(boolean).</action> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Dependabot, Gary Gregory">Bump org.apache.commons:commons-parent from 81 to 85 #668.</action> <action type="update" dev="ggregory" due-to="Gary Gregory">Bump commons-io:commons-io from 2.18.0 to 2.20.0.</action> diff --git a/src/main/java/org/apache/commons/text/RandomStringGenerator.java b/src/main/java/org/apache/commons/text/RandomStringGenerator.java index 3ec27887..f39212b7 100644 --- a/src/main/java/org/apache/commons/text/RandomStringGenerator.java +++ b/src/main/java/org/apache/commons/text/RandomStringGenerator.java @@ -125,7 +125,12 @@ public final class RandomStringGenerator { /** * The source of provided characters. */ - private List<Character> characterList; + private Set<Character> characterSet = new HashSet<>(); + + /** + * Whether calls accumulates the source of provided characters. The default is {@code false}. + */ + private boolean accumulate; /** * Creates a new instance. @@ -155,7 +160,7 @@ public final class RandomStringGenerator { * </p> * * @param predicates the predicates, may be {@code null} or empty. - * @return {@code this}, to allow method chaining. + * @return {@code this} instance. */ public Builder filteredBy(final CharacterPredicate... predicates) { if (ArrayUtils.isEmpty(predicates)) { @@ -182,6 +187,12 @@ public final class RandomStringGenerator { return new RandomStringGenerator(this); } + private void initCharList() { + if (!accumulate) { + characterSet = new HashSet<>(); + } + } + /** * Limits the characters in the generated string to those who match at supplied list of Character. * @@ -191,19 +202,43 @@ public final class RandomStringGenerator { * </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. + * @return {@code this} instance. * @since 1.2 */ public Builder selectFrom(final char... chars) { - characterList = new ArrayList<>(); + initCharList(); if (chars != null) { for (final char c : chars) { - characterList.add(c); + characterSet.add(c); } } return this; } + /** + * Sets whether calls accumulates the source of provided characters. The default is {@code false}. + * + * <pre> + * {@code + * RandomStringGenerator gen = RandomStringGenerator.builder() + * .setAccumulate(true) + * .withinRange(new char[][] { { 'a', 'z' }, { 'A', 'Z' }, { '0', '9' } }) + * .selectFrom('!', '"', '#', '$', '&', '\'', '(', ')', ',', '.', ':', ';', '?', '@', '[', + * '\\', ']', '^', '_', '`', '{', '|', '}', '~') // punctuation + * // additional builder calls as needed + * .build(); + * } + * </pre> + * + * @param accumulate whether calls accumulates the source of provided characters. The default is {@code false}. + * @return {@code this} instance. + * @since 1.14.0 + */ + public Builder setAccumulate(final boolean accumulate) { + this.accumulate = accumulate; + 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. @@ -227,7 +262,7 @@ public final class RandomStringGenerator { * </p> * * @param random the source of randomness, may be {@code null}. - * @return {@code this}, to allow method chaining. + * @return {@code this} instance. * @since 1.14.0 */ public Builder usingRandom(final IntUnaryOperator random) { @@ -258,7 +293,7 @@ public final class RandomStringGenerator { * </p> * * @param random the source of randomness, may be {@code null}. - * @return {@code this}, to allow method chaining. + * @return {@code this} instance. */ public Builder usingRandom(final TextRandomProvider random) { this.random = random; @@ -272,7 +307,6 @@ public final class RandomStringGenerator { * * <pre> * {@code - * * char[][] pairs = { { '0', '9' } }; * char[][] pairs = { { 'a', 'z' } }; * char[][] pairs = { { 'a', 'z' }, { '0', '9' } }; @@ -280,10 +314,10 @@ public final class RandomStringGenerator { * </pre> * * @param pairs array of characters array, expected is to pass min, max pairs through this arg. - * @return {@code this}, to allow method chaining. + * @return {@code this} instance. */ public Builder withinRange(final char[]... pairs) { - characterList = new ArrayList<>(); + initCharList(); if (pairs != null) { for (final char[] pair : pairs) { Validate.isTrue(pair.length == 2, "Each pair must contain minimum and maximum code point"); @@ -292,19 +326,20 @@ public final class RandomStringGenerator { 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); + characterSet.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. + * @return {@code this} instance. * @throws IllegalArgumentException if {@code maximumCodePoint >} {@link Character#MAX_CODE_POINT}. * @throws IllegalArgumentException if {@code minimumCodePoint < 0}. * @throws IllegalArgumentException if {@code minimumCodePoint > maximumCodePoint}. @@ -362,14 +397,14 @@ public final class RandomStringGenerator { * @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. + * @param characterSet list of predefined set of characters. */ private RandomStringGenerator(final Builder builder) { this.minimumCodePoint = builder.minimumCodePoint; this.maximumCodePoint = builder.maximumCodePoint; this.inclusivePredicates = builder.inclusivePredicates; this.random = builder.random; - this.characterList = builder.characterList; + this.characterList = new ArrayList<>(builder.characterSet); } /** diff --git a/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java b/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java index 9daea44b..23527557 100644 --- a/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java +++ b/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java @@ -23,13 +23,17 @@ import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import java.util.Arrays; import java.util.function.IntUnaryOperator; +import org.apache.commons.lang3.ArraySorter; import org.apache.commons.text.RandomStringGenerator.Builder; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** - * Tests for {@link RandomStringGenerator} + * Tests for {@link RandomStringGenerator}. */ class RandomStringGeneratorTest { @@ -159,6 +163,25 @@ class RandomStringGeneratorTest { } while (i < str.length()); } + @Test + void testPasswordExample() { + final char[] punctuation = ArraySorter + .sort(new char[] { '!', '"', '#', '$', '&', '\'', '(', ')', ',', '.', ':', ';', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~' }); + // @formatter:off + final RandomStringGenerator generator = RandomStringGenerator.builder() + .setAccumulate(true) + .withinRange('a', 'z') + .withinRange('A', 'Z') + .withinRange('0', '9') + .selectFrom(punctuation) + .get(); + // @formatter:on + final String randomText = generator.generate(10); + for (final char c : randomText.toCharArray()) { + assertTrue(Character.isLetter(c) || Character.isDigit(c) || Arrays.binarySearch(punctuation, c) >= 0); + } + } + @Test void testRemoveFilters() { final RandomStringGenerator.Builder builder = RandomStringGenerator.builder().withinRange('a', 'z').filteredBy(A_FILTER); @@ -194,17 +217,19 @@ class RandomStringGeneratorTest { } } - @Test - void testSelectFromCharVarargs2() { + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testSelectFromCharVarargs2(final boolean accumulate) { final String str = "abcde"; // @formatter:off final RandomStringGenerator generator = RandomStringGenerator.builder() + .setAccumulate(accumulate) .selectFrom() .selectFrom(null) .selectFrom('a', 'b') .selectFrom('a', 'b', 'c') .selectFrom('a', 'b', 'c', 'd') - .selectFrom('a', 'b', 'c', 'd', 'e') // only this last call matters + .selectFrom('a', 'b', 'c', 'd', 'e') // only this last call matters when accumulate is false .build(); // @formatter:on final String randomText = generator.generate(10); @@ -213,6 +238,27 @@ class RandomStringGeneratorTest { } } + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testSelectFromCharVarargs3(final boolean accumulate) { + final String str = "abcde"; + // @formatter:off + final RandomStringGenerator generator = RandomStringGenerator.builder() + .setAccumulate(accumulate) + .selectFrom('a', 'b', 'c', 'd', 'e') + .selectFrom('a', 'b', 'c', 'd') + .selectFrom('a', 'b', 'c') + .selectFrom('a', 'b') + .selectFrom(null) + .selectFrom() + .get(); + // @formatter:on + final String randomText = generator.generate(10); + for (final char c : randomText.toCharArray()) { + assertEquals(accumulate, str.indexOf(c) != -1); + } + } + @Test void testSelectFromCharVarargSize1() { final RandomStringGenerator generator = RandomStringGenerator.builder().selectFrom('a').build();