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();

Reply via email to