You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by dj...@apache.org on 2016/12/27 08:45:33 UTC

[text] Rename RandomStringBuilder and make it immutable and thread-safe.

Repository: commons-text
Updated Branches:
  refs/heads/master 6f6da3467 -> 08ac56a50


Rename RandomStringBuilder and make it immutable and thread-safe.

The RandomStringBuilder class was renamed to RandomStringGenerator. The
class is now constructed using an inner Builder class, resulting in an
immutable and thread-safe generator. The default random generator is now
ThreadLocalRandom rather than Random.

The inner CodePointPredicate class was extracted into a separate class
and renamed to CharacterPredicate. The commonly used predicates are now
in a separate enum, CharacterPredicates.

Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/08ac56a5
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/08ac56a5
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/08ac56a5

Branch: refs/heads/master
Commit: 08ac56a502adb0d274d02f9d97f394e4a5c5966e
Parents: 6f6da34
Author: duncan <du...@wortharead.com>
Authored: Tue Dec 27 08:18:52 2016 +0000
Committer: duncan <du...@wortharead.com>
Committed: Tue Dec 27 08:42:48 2016 +0000

----------------------------------------------------------------------
 .../apache/commons/text/CharacterPredicate.java |  37 ++
 .../commons/text/CharacterPredicates.java       |  52 +++
 .../commons/text/RandomStringBuilder.java       | 354 -------------------
 .../commons/text/RandomStringGenerator.java     | 316 +++++++++++++++++
 .../commons/text/CharacterPredicatesTest.java   |  50 +++
 .../commons/text/RandomStringBuilderTest.java   | 235 ------------
 .../commons/text/RandomStringGeneratorTest.java | 206 +++++++++++
 7 files changed, 661 insertions(+), 589 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/CharacterPredicate.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/CharacterPredicate.java b/src/main/java/org/apache/commons/text/CharacterPredicate.java
new file mode 100644
index 0000000..fb23ac4
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/CharacterPredicate.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * A predicate for selecting code points. Implementations of this interface must
+ * be thread safe.
+ * 
+ * @since 1.0
+ */
+public interface CharacterPredicate {
+    
+    /**
+     * Tests the code point with this predicate.
+     * 
+     * @param codePoint
+     *            the code point to test
+     * @return {@code true} if the code point matches the predicate,
+     *         {@code false} otherwise
+     * @since 1.0
+     */
+    boolean test(int codePoint);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/CharacterPredicates.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/CharacterPredicates.java b/src/main/java/org/apache/commons/text/CharacterPredicates.java
new file mode 100644
index 0000000..7f38552
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/CharacterPredicates.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * <p>
+ * Commonly used implementations of {@link CharacterPredicate}. Per the interface
+ * requirements, all implementations are thread safe.
+ * </p>
+ * 
+ * @since 1.0
+ */
+public enum CharacterPredicates implements CharacterPredicate {
+
+    /**
+     * Tests code points against {@link Character#isLetter(int)}
+     * 
+     * @since 1.0
+     */
+    LETTERS {
+        @Override
+        public boolean test(int codePoint) {
+            return Character.isLetter(codePoint);
+        }
+    },
+
+    /**
+     * Tests code points against {@link Character#isDigit(int)}.
+     * 
+     * @since 1.0
+     */
+    DIGITS {
+        @Override
+        public boolean test(int codePoint) {
+            return Character.isDigit(codePoint);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/RandomStringBuilder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/RandomStringBuilder.java b/src/main/java/org/apache/commons/text/RandomStringBuilder.java
deleted file mode 100644
index 38ba974..0000000
--- a/src/main/java/org/apache/commons/text/RandomStringBuilder.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * 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.HashSet;
-import java.util.Random;
-import java.util.Set;
-
-/**
- * <p>
- * Generates a random Unicode string based on properties defined using a builder
- * pattern.
- * </p>
- * <p>
- * Overriding the default properties is optional, however callers will need to
- * define the length of the output string using {@link #ofLength(int)} to avoid
- * generating an empty string.
- * </p>
- * <p>
- * All the property setting methods return the {@code RandomStringBuilder}
- * instance to allow for method chaining:
- * </p>
- * 
- * <pre>
- * // Generates a 20 code point string, using only the letters a-z
- * String random = new RandomStringBuilder().ofLength(20).withinRange('a','z').build();
- * </pre>
- * 
- * <p>
- * The type of code point returned can be filtered using
- * {@link #filteredBy(CodePointPredicate...)}, 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 (e.g. {@link #LETTERS} or
- * {@link #DIGITS}) and others can be created by implementing
- * {@link CodePointPredicate}.
- * </p>
- * 
- * <pre>
- * // Generates a 10 code point string containing only letters
- * 
- * import static org.apache.commons.text.RandomStringBuilder.LETTERS;
- * ...
- * String random = new RandomStringBuilder().ofLength(10).filteredBy(LETTERS).build();
- * </pre>
- * 
- * <p>
- * A {@code RandomStringBuilder} instance can be used multiple times to generate
- * different random strings, however it cannot safely be shared between threads.
- * </p>
- * 
- * @since 1.0
- */
-public class RandomStringBuilder implements Builder<String> {
-
-    /**
-     * Default source of randomness
-     */
-    private static final Random DEFAULT_RANDOM = new Random();
-
-    /**
-     * The default string length produced by this builder: {@value}
-     * 
-     * @since 1.0
-     */
-    public static final int DEFAULT_LENGTH = 0;
-
-    /**
-     * The default minimum code point allowed: {@value}
-     * 
-     * @since 1.0
-     */
-    public static final int DEFAULT_MINIMUM_CODE_POINT = 0;
-
-    /**
-     * The default maximum code point allowed: {@link Character#MAX_CODE_POINT}
-     * ({@value})
-     * 
-     * @since 1.0
-     */
-    public static final int DEFAULT_MAXIMUM_CODE_POINT = Character.MAX_CODE_POINT;
-
-    private int length = 0;
-    private int minimumCodePoint = 0;
-    private int maximumCodePoint = Character.MAX_CODE_POINT;
-    private Set<CodePointPredicate> inclusivePredicates = null;
-    private Random random = null;
-
-    /**
-     * <p>
-     * Constructs a builder with default properties:
-     * </p>
-     *
-     * <ul>
-     * <li>Length: {@value #DEFAULT_LENGTH}</li>
-     * <li>Minimum code point: {@value #DEFAULT_MINIMUM_CODE_POINT}</li>
-     * <li>Maximum code point: {@link Character#MAX_CODE_POINT}</li>
-     * <li>Default source of randomness</li>
-     * <li>No character filters</li>
-     * </ul>
-     * 
-     * @since 1.0
-     */
-    public RandomStringBuilder() {
-    }
-
-    /**
-     * <p>
-     * Specifies how many code points to generate in the random string.
-     * </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 {@code this}, to allow method chaining
-     * @throws IllegalArgumentException
-     *             if {@code length < 0}
-     * @since 1.0
-     */
-    public RandomStringBuilder ofLength(final int length) {
-        if (length < 0) {
-            throw new IllegalArgumentException(String.format("Length %d is smaller than zero.", length));
-        }
-
-        this.length = length;
-        return this;
-    }
-
-    /**
-     * <p>
-     * Specifies the minimum and maximum code points allowed in the generated
-     * string.
-     * </p>
-     * 
-     * @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}
-     * @since 1.0
-     */
-    public RandomStringBuilder withinRange(final int minimumCodePoint, final int maximumCodePoint) {
-        if (minimumCodePoint > maximumCodePoint) {
-            throw new IllegalArgumentException(String.format(
-                    "Minimum code point %d is larger than maximum code point %d", minimumCodePoint, maximumCodePoint));
-        }
-        if (minimumCodePoint < 0) {
-            throw new IllegalArgumentException(String.format("Minimum code point %d is negative", minimumCodePoint));
-        }
-        if (maximumCodePoint > Character.MAX_CODE_POINT) {
-            throw new IllegalArgumentException(
-                    String.format("Value %d is larger than Character.MAX_CODE_POINT.", maximumCodePoint));
-        }
-
-        this.minimumCodePoint = minimumCodePoint;
-        this.maximumCodePoint = maximumCodePoint;
-        return this;
-    }
-
-    /**
-     * <p>
-     * Overrides the default source of randomness.
-     * </p>
-     * 
-     * <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
-     * @since 1.0
-     */
-    public RandomStringBuilder usingRandom(final Random random) {
-        this.random = random;
-        return this;
-    }
-
-    /**
-     * <p>
-     * Limits the characters in the generated string to those that match at
-     * least one of the predicates supplied.
-     * </p>
-     * 
-     * <p>
-     * Passing {@code null} or an empty array to this method will revert to the
-     * default behaviour 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
-     * @since 1.0
-     */
-    public RandomStringBuilder filteredBy(final CodePointPredicate... predicates) {
-        if (predicates == null || predicates.length == 0) {
-            inclusivePredicates = null;
-            return this;
-        }
-
-        if (inclusivePredicates == null) {
-            inclusivePredicates = new HashSet<>();
-        } else {
-            inclusivePredicates.clear();
-        }
-
-        for (CodePointPredicate predicate : predicates) {
-            inclusivePredicates.add(predicate);
-        }
-
-        return this;
-    }
-
-    /**
-     * <p>
-     * Generates a random string using the settings defined in this builder.
-     * Code points are randomly selected between the minimum and maximum values.
-     * 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>
-     * A static {@code Random} instance is used if an alternative wasn't
-     * provided via {@link #usingRandom(Random)}.
-     * </p>
-     * 
-     * @return the randomly generated string
-     * @since 1.0
-     */
-    @Override
-    public String build() {
-        if (length == 0) {
-            return "";
-        }
-
-        if (random == null) {
-            random = DEFAULT_RANDOM;
-        }
-
-        final StringBuilder builder = new StringBuilder(length);
-        long remaining = length;
-
-        do {
-            int codePoint = random.nextInt(maximumCodePoint - minimumCodePoint + 1) + minimumCodePoint;
-
-            switch (Character.getType(codePoint)) {
-            case Character.UNASSIGNED:
-            case Character.PRIVATE_USE:
-            case Character.SURROGATE:
-                continue;
-            }
-
-            if (inclusivePredicates != null) {
-                boolean matchedFilter = false;
-                for (CodePointPredicate predicate : inclusivePredicates) {
-                    if (predicate.test(codePoint)) {
-                        matchedFilter = true;
-                        break;
-                    }
-                }
-                if (!matchedFilter) {
-                    continue;
-                }
-            }
-
-            builder.appendCodePoint(codePoint);
-            remaining--;
-
-        } while (remaining != 0);
-
-        return builder.toString();
-    }
-
-    /**
-     * A predicate for selecting code points.
-     * 
-     * @since 1.0
-     */
-    public static interface CodePointPredicate {
-        /**
-         * Tests the code point with this predicate.
-         * 
-         * @param codePoint
-         *            the code point to test
-         * @return {@code true} if the code point matches the predicate,
-         *         {@code false} otherwise
-         * @since 1.0
-         */
-        boolean test(int codePoint);
-    }
-
-    /**
-     * Tests code points against {@link Character#isLetter(int)}.
-     * 
-     * @since 1.0
-     */
-    public static final CodePointPredicate LETTERS = new LetterPredicate();
-
-    /**
-     * Tests code points against {@link Character#isDigit(int)}.
-     * 
-     * @since 1.0
-     */
-    public static final CodePointPredicate DIGITS = new DigitPredicate();
-
-    /**
-     * Tests whether code points are letters.
-     */
-    private static final class LetterPredicate implements CodePointPredicate {
-        @Override
-        public boolean test(int codePoint) {
-            return Character.isLetter(codePoint);
-        }
-    }
-
-    /**
-     * Tests whether code points are digits.
-     */
-    private static final class DigitPredicate implements CodePointPredicate {
-        @Override
-        public boolean test(int codePoint) {
-            return Character.isDigit(codePoint);
-        }
-    };
-}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/RandomStringGenerator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/RandomStringGenerator.java b/src/main/java/org/apache/commons/text/RandomStringGenerator.java
new file mode 100644
index 0000000..c4411a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/RandomStringGenerator.java
@@ -0,0 +1,316 @@
+/*
+ * 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.HashSet;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * <p>
+ * 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.
+ * </p>
+ * 
+ * <pre>
+ * // Generates a 20 code point string, using only the letters a-z
+ * RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange('a', 'z').build();
+ * String random = generator.generate(20);
+ * </pre>
+ * 
+ * <p>
+ * {@code RandomStringBuilder} instances are immutable and thread-safe.
+ * </p>
+ * 
+ * @since 1.0
+ */
+public final class RandomStringGenerator {
+
+    private final int minimumCodePoint;
+    private final int maximumCodePoint;
+    private final Set<CharacterPredicate> inclusivePredicates;
+    private final Random random;
+
+
+    /**
+     * 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
+     */
+    private RandomStringGenerator(int minimumCodePoint, int maximumCodePoint,
+            Set<CharacterPredicate> inclusivePredicates, Random random) {
+        this.minimumCodePoint = minimumCodePoint;
+        this.maximumCodePoint = maximumCodePoint;
+        this.inclusivePredicates = inclusivePredicates;
+        this.random = random;
+    }
+    
+    /**
+     * 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);
+    }
+
+
+    /**
+     * <p>
+     * Generates a random string, containing the specified number of code points.
+     * </p>
+     * <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}
+     * @since 1.0
+     */
+    public String generate(final int length) {
+        if (length == 0) {
+            return "";
+        }
+        
+        if (length < 0) {
+            throw new IllegalArgumentException(String.format("Length %d is smaller than zero.", length));
+        }
+
+        final StringBuilder builder = new StringBuilder(length);
+        long remaining = length;
+
+        do {
+            int codePoint = generateRandomNumber(minimumCodePoint, maximumCodePoint);
+
+            switch (Character.getType(codePoint)) {
+            case Character.UNASSIGNED:
+            case Character.PRIVATE_USE:
+            case Character.SURROGATE:
+                continue;
+            }
+
+            if (inclusivePredicates != null) {
+                boolean matchedFilter = false;
+                for (CharacterPredicate predicate : inclusivePredicates) {
+                    if (predicate.test(codePoint)) {
+                        matchedFilter = true;
+                        break;
+                    }
+                }
+                if (!matchedFilter) {
+                    continue;
+                }
+            }
+
+            builder.appendCodePoint(codePoint);
+            remaining--;
+
+        } while (remaining != 0);
+
+        return builder.toString();
+    }
+    
+    
+    /**
+     * <p>A builder for generating {@code RandomStringGenerator} instances.</p>
+     * <p>The behaviour 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(Random)}, 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.0
+     */
+    public static class Builder implements org.apache.commons.text.Builder<RandomStringGenerator> {
+        
+        /**
+         * The default maximum code point allowed: {@link Character#MAX_CODE_POINT}
+         * ({@value})
+         * 
+         * @since 1.0
+         */
+        public static final int DEFAULT_MAXIMUM_CODE_POINT = Character.MAX_CODE_POINT;
+        
+        /**
+         * The default string length produced by this builder: {@value}
+         * 
+         * @since 1.0
+         */
+        public static final int DEFAULT_LENGTH = 0;
+
+        /**
+         * The default minimum code point allowed: {@value}
+         * 
+         * @since 1.0
+         */
+        public static final int DEFAULT_MINIMUM_CODE_POINT = 0;
+
+        private int minimumCodePoint = DEFAULT_MINIMUM_CODE_POINT;
+        private int maximumCodePoint = DEFAULT_MAXIMUM_CODE_POINT;
+        private Set<CharacterPredicate> inclusivePredicates;
+        private Random random;
+        
+        
+        /**
+         * <p>
+         * Specifies the minimum and maximum code points allowed in the generated
+         * string.
+         * </p>
+         * 
+         * @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}
+         * @since 1.0
+         */
+        public Builder withinRange(final int minimumCodePoint, final int maximumCodePoint) {
+            if (minimumCodePoint > maximumCodePoint) {
+                throw new IllegalArgumentException(String.format(
+                        "Minimum code point %d is larger than maximum code point %d", minimumCodePoint, maximumCodePoint));
+            }
+            if (minimumCodePoint < 0) {
+                throw new IllegalArgumentException(String.format("Minimum code point %d is negative", minimumCodePoint));
+            }
+            if (maximumCodePoint > Character.MAX_CODE_POINT) {
+                throw new IllegalArgumentException(
+                        String.format("Value %d is larger than Character.MAX_CODE_POINT.", maximumCodePoint));
+            }
+
+            this.minimumCodePoint = minimumCodePoint;
+            this.maximumCodePoint = maximumCodePoint;
+            return this;
+        }
+        
+        /**
+         * <p>
+         * Limits the characters in the generated string to those that match at
+         * least one of the predicates supplied.
+         * </p>
+         * 
+         * <p>
+         * Passing {@code null} or an empty array to this method will revert to the
+         * default behaviour 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
+         * @since 1.0
+         */
+        public Builder filteredBy(final CharacterPredicate... predicates) {
+            if (predicates == null || predicates.length == 0) {
+                inclusivePredicates = null;
+                return this;
+            }
+
+            if (inclusivePredicates == null) {
+                inclusivePredicates = new HashSet<>();
+            } else {
+                inclusivePredicates.clear();
+            }
+
+            for (CharacterPredicate predicate : predicates) {
+                inclusivePredicates.add(predicate);
+            }
+
+            return this;
+        }
+        
+        /**
+         * <p>
+         * Overrides the default source of randomness.
+         * </p>
+         * 
+         * <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
+         * @since 1.0
+         */
+        public Builder usingRandom(final Random random) {
+            this.random = random;
+            return this;
+        }
+
+        /**
+         * <p>Builds the {@code RandomStringGenerator} using the properties specified.</p>
+         */
+        @Override
+        public RandomStringGenerator build() {
+            return new RandomStringGenerator(minimumCodePoint, maximumCodePoint, inclusivePredicates, random);
+        }
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java b/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java
new file mode 100644
index 0000000..3ff8b74
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Tests for implementations in the {@link CharacterPredicates} enum.
+ */
+public class CharacterPredicatesTest {
+    @Test
+    public void testDigitPredicate() throws Exception {
+        String str = new RandomStringGenerator.Builder().filteredBy(CharacterPredicates.DIGITS).build().generate(5000);
+
+        int i = 0;
+        do {
+            int codePoint = str.codePointAt(i);
+            assertTrue(Character.isDigit(codePoint));
+            i += Character.charCount(codePoint);
+        } while (i < str.length());
+    }
+
+    @Test
+    public void testLetterPredicate() throws Exception {
+        String str = new RandomStringGenerator.Builder().filteredBy(CharacterPredicates.LETTERS).build().generate(5000);
+
+        int i = 0;
+        do {
+            int codePoint = str.codePointAt(i);
+            assertTrue(Character.isLetter(codePoint));
+            i += Character.charCount(codePoint);
+        } while (i < str.length());
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java b/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java
deleted file mode 100644
index e6f9f81..0000000
--- a/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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 static org.apache.commons.text.RandomStringBuilder.LETTERS;
-import static org.junit.Assert.*;
-
-import java.util.Random;
-
-import org.apache.commons.text.RandomStringBuilder.CodePointPredicate;
-import org.junit.Test;
-
-/**
- * Tests for {@link RandomStringBuilder}
- */
-public class RandomStringBuilderTest {
-
-    private static int codePointLength(String s) {
-        return s.codePointCount(0, s.length());
-    }
-
-    private static final CodePointPredicate A_FILTER = new CodePointPredicate() {
-        @Override
-        public boolean test(int codePoint) {
-            return codePoint == 'a';
-        }
-    };
-
-    private static final CodePointPredicate B_FILTER = new CodePointPredicate() {
-        @Override
-        public boolean test(int codePoint) {
-            return codePoint == 'b';
-        }
-    };
-
-    @Test
-    public void testDefaultLength() throws Exception {
-        String str = new RandomStringBuilder().build();
-        assertEquals(RandomStringBuilder.DEFAULT_LENGTH, codePointLength(str));
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testInvalidLength() throws Exception {
-        new RandomStringBuilder().ofLength(-1);
-    }
-
-    @Test
-    public void testSetLength() throws Exception {
-        final int length = 99;
-        String str = new RandomStringBuilder().ofLength(length).build();
-        assertEquals(length, codePointLength(str));
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBadMinimumCodePoint() throws Exception {
-        new RandomStringBuilder().withinRange(-1, 1);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBadMaximumCodePoint() throws Exception {
-        new RandomStringBuilder().withinRange(0, Character.MAX_CODE_POINT + 1);
-    }
-
-    @Test
-        public void testWithinRange() throws Exception {
-            final int length = 5000;
-            final int minimumCodePoint = 'a';
-            final int maximumCodePoint = 'z';
-            String str = new RandomStringBuilder().ofLength(length).withinRange(minimumCodePoint,maximumCodePoint).build();
-    
-            int i = 0;
-            do {
-                int codePoint = str.codePointAt(i);
-                assertTrue(codePoint >= minimumCodePoint && codePoint <= maximumCodePoint);
-                i += Character.charCount(codePoint);
-            } while (i < str.length());
-    
-        }
-
-    @Test
-    public void testNoLoneSurrogates() throws Exception {
-        final int length = 5000;
-        String str = new RandomStringBuilder().ofLength(length).build();
-
-        char lastChar = str.charAt(0);
-        for (int i = 1; i < str.length(); i++) {
-            char c = str.charAt(i);
-
-            if (Character.isLowSurrogate(c)) {
-                assertTrue(Character.isHighSurrogate(lastChar));
-            }
-
-            if (Character.isHighSurrogate(lastChar)) {
-                assertTrue(Character.isLowSurrogate(c));
-            }
-
-            if (Character.isHighSurrogate(c)) {
-                // test this isn't the last character in the string
-                assertTrue(i + 1 < str.length());
-            }
-
-            lastChar = c;
-        }
-    }
-
-    @Test
-    public void testUsingRandom() throws Exception {
-        final char testChar = 'a';
-        final Random testRandom = new Random() {
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            public int nextInt(int n) {
-                return testChar;
-            }
-        };
-
-        String str = new RandomStringBuilder().ofLength(100).usingRandom(testRandom).build();
-        for (char c : str.toCharArray()) {
-            assertEquals(testChar, c);
-        }
-    }
-
-    @Test
-    public void testLetterPredicate() throws Exception {
-        String str = new RandomStringBuilder().ofLength(5000).filteredBy(LETTERS).build();
-
-        int i = 0;
-        do {
-            int codePoint = str.codePointAt(i);
-            assertTrue(Character.isLetter(codePoint));
-            i += Character.charCount(codePoint);
-        } while (i < str.length());
-    }
-
-    @Test
-    public void testDigitPredicate() throws Exception {
-        String str = new RandomStringBuilder().ofLength(5000).filteredBy(RandomStringBuilder.DIGITS).build();
-
-        int i = 0;
-        do {
-            int codePoint = str.codePointAt(i);
-            assertTrue(Character.isDigit(codePoint));
-            i += Character.charCount(codePoint);
-        } while (i < str.length());
-    }
-
-    @Test
-    public void testMultipleFilters() throws Exception {
-        String str = new RandomStringBuilder().ofLength(5000).withinRange('a','d')
-                .filteredBy(A_FILTER, B_FILTER).build();
-
-        boolean aFound = false;
-        boolean bFound = false;
-
-        for (char c : str.toCharArray()) {
-            if (c == 'a') {
-                aFound = true;
-            } else if (c == 'b') {
-                bFound = true;
-            } else {
-                fail("Invalid character");
-            }
-        }
-
-        assertTrue(aFound && bFound);
-    }
-
-    @Test
-    public void testNoPrivateCharacters() throws Exception {
-        final int startOfPrivateBMPChars = 0xE000;
-
-        // Request a string in an area of the Basic Multilingual Plane that is
-        // largely
-        // occupied by private characters
-        String str = new RandomStringBuilder().ofLength(5000).withinRange(startOfPrivateBMPChars, 
-                Character.MIN_SUPPLEMENTARY_CODE_POINT - 1).build();
-
-        int i = 0;
-        do {
-            int codePoint = str.codePointAt(i);
-            assertFalse(Character.getType(codePoint) == Character.PRIVATE_USE);
-            i += Character.charCount(codePoint);
-        } while (i < str.length());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testBadMinAndMax() throws Exception {
-        new RandomStringBuilder().withinRange(2, 1);
-    }
-
-    @Test
-    public void testRemoveFilters() throws Exception {
-
-        RandomStringBuilder builder = new RandomStringBuilder().ofLength(100).withinRange('a', 'z')
-                .filteredBy(A_FILTER);
-
-        builder.filteredBy();
-
-        String str = builder.build();
-        for (char c : str.toCharArray()) {
-            if (c != 'a') {
-                // filter was successfully removed
-                return;
-            }
-        }
-
-        fail("Filter appears to have remained in place");
-    }
-
-    @Test
-    public void testChangeOfFilter() throws Exception {
-        RandomStringBuilder builder = new RandomStringBuilder().ofLength(100).withinRange('a', 'z')
-                .filteredBy(A_FILTER);
-        String str = builder.filteredBy(B_FILTER).build();
-
-        for (char c : str.toCharArray()) {
-            assertTrue(c == 'b');
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java b/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java
new file mode 100644
index 0000000..2ff7a67
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.util.Random;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link RandomStringGenerator}
+ */
+public class RandomStringGeneratorTest {
+
+    private static int codePointLength(String s) {
+        return s.codePointCount(0, s.length());
+    }
+
+    private static final CharacterPredicate A_FILTER = new CharacterPredicate() {
+        @Override
+        public boolean test(int codePoint) {
+            return codePoint == 'a';
+        }
+    };
+
+    private static final CharacterPredicate B_FILTER = new CharacterPredicate() {
+        @Override
+        public boolean test(int codePoint) {
+            return codePoint == 'b';
+        }
+    };
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLength() throws Exception {
+        RandomStringGenerator generator = new RandomStringGenerator.Builder().build();
+        generator.generate(-1);
+    }
+
+    @Test
+    public void testSetLength() throws Exception {
+        final int length = 99;
+        RandomStringGenerator generator = new RandomStringGenerator.Builder().build();
+        String str = generator.generate(length);
+        assertEquals(length, codePointLength(str));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBadMinimumCodePoint() throws Exception {
+        new RandomStringGenerator.Builder().withinRange(-1, 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBadMaximumCodePoint() throws Exception {
+        new RandomStringGenerator.Builder().withinRange(0, Character.MAX_CODE_POINT + 1);
+    }
+
+    @Test
+        public void testWithinRange() throws Exception {
+            final int length = 5000;
+            final int minimumCodePoint = 'a';
+            final int maximumCodePoint = 'z';
+            RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange(minimumCodePoint,maximumCodePoint).build();
+            String str = generator.generate(length);
+    
+            int i = 0;
+            do {
+                int codePoint = str.codePointAt(i);
+                assertTrue(codePoint >= minimumCodePoint && codePoint <= maximumCodePoint);
+                i += Character.charCount(codePoint);
+            } while (i < str.length());
+    
+        }
+
+    @Test
+    public void testNoLoneSurrogates() throws Exception {
+        final int length = 5000;
+        String str = new RandomStringGenerator.Builder().build().generate(length);
+
+        char lastChar = str.charAt(0);
+        for (int i = 1; i < str.length(); i++) {
+            char c = str.charAt(i);
+
+            if (Character.isLowSurrogate(c)) {
+                assertTrue(Character.isHighSurrogate(lastChar));
+            }
+
+            if (Character.isHighSurrogate(lastChar)) {
+                assertTrue(Character.isLowSurrogate(c));
+            }
+
+            if (Character.isHighSurrogate(c)) {
+                // test this isn't the last character in the string
+                assertTrue(i + 1 < str.length());
+            }
+
+            lastChar = c;
+        }
+    }
+
+    @Test
+    public void testUsingRandom() throws Exception {
+        final char testChar = 'a';
+        final Random testRandom = new Random() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public int nextInt(int n) {
+                return testChar;
+            }
+        };
+
+        String str = new RandomStringGenerator.Builder().usingRandom(testRandom).build().generate(10);
+        for (char c : str.toCharArray()) {
+            assertEquals(testChar, c);
+        }
+    }
+
+    @Test
+    public void testMultipleFilters() throws Exception {
+        String str = new RandomStringGenerator.Builder().withinRange('a','d')
+                .filteredBy(A_FILTER, B_FILTER).build().generate(5000);
+
+        boolean aFound = false;
+        boolean bFound = false;
+
+        for (char c : str.toCharArray()) {
+            if (c == 'a') {
+                aFound = true;
+            } else if (c == 'b') {
+                bFound = true;
+            } else {
+                fail("Invalid character");
+            }
+        }
+
+        assertTrue(aFound && bFound);
+    }
+
+    @Test
+    public void testNoPrivateCharacters() throws Exception {
+        final int startOfPrivateBMPChars = 0xE000;
+
+        // Request a string in an area of the Basic Multilingual Plane that is
+        // largely
+        // occupied by private characters
+        String str = new RandomStringGenerator.Builder().withinRange(startOfPrivateBMPChars, 
+                Character.MIN_SUPPLEMENTARY_CODE_POINT - 1).build().generate(5000);
+
+        int i = 0;
+        do {
+            int codePoint = str.codePointAt(i);
+            assertFalse(Character.getType(codePoint) == Character.PRIVATE_USE);
+            i += Character.charCount(codePoint);
+        } while (i < str.length());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBadMinAndMax() throws Exception {
+        new RandomStringGenerator.Builder().withinRange(2, 1);
+    }
+
+    @Test
+    public void testRemoveFilters() throws Exception {
+
+        RandomStringGenerator.Builder builder = new RandomStringGenerator.Builder().withinRange('a', 'z')
+                .filteredBy(A_FILTER);
+
+        builder.filteredBy();
+
+        String str = builder.build().generate(100);
+        for (char c : str.toCharArray()) {
+            if (c != 'a') {
+                // filter was successfully removed
+                return;
+            }
+        }
+
+        fail("Filter appears to have remained in place");
+    }
+
+    @Test
+    public void testChangeOfFilter() throws Exception {
+        RandomStringGenerator.Builder builder = new RandomStringGenerator.Builder().withinRange('a', 'z')
+                .filteredBy(A_FILTER);
+        String str = builder.filteredBy(B_FILTER).build().generate(100);
+
+        for (char c : str.toCharArray()) {
+            assertTrue(c == 'b');
+        }
+    }
+}


Re: [text] Rename RandomStringBuilder and make it immutable and thread-safe.

Posted by Duncan Jones <du...@wortharead.com>.
> On 27 Dec 2016, at 08:45, djones@apache.org wrote:
> 
> Repository: commons-text
> Updated Branches:
>  refs/heads/master 6f6da3467 -> 08ac56a50
> 
> 
> Rename RandomStringBuilder and make it immutable and thread-safe.
> 
> The RandomStringBuilder class was renamed to RandomStringGenerator. The
> class is now constructed using an inner Builder class, resulting in an
> immutable and thread-safe generator. The default random generator is now
> ThreadLocalRandom rather than Random.
> 
> The inner CodePointPredicate class was extracted into a separate class
> and renamed to CharacterPredicate. The commonly used predicates are now
> in a separate enum, CharacterPredicates.
> 
> Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
> Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/08ac56a5
> Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/08ac56a5
> Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/08ac56a5
> 
> Branch: refs/heads/master
> Commit: 08ac56a502adb0d274d02f9d97f394e4a5c5966e
> Parents: 6f6da34
> Author: duncan <du...@wortharead.com>
> Authored: Tue Dec 27 08:18:52 2016 +0000
> Committer: duncan <du...@wortharead.com>
> Committed: Tue Dec 27 08:42:48 2016 +0000
> 
> ----------------------------------------------------------------------
> .../apache/commons/text/CharacterPredicate.java |  37 ++
> .../commons/text/CharacterPredicates.java       |  52 +++
> .../commons/text/RandomStringBuilder.java       | 354 -------------------
> .../commons/text/RandomStringGenerator.java     | 316 +++++++++++++++++
> .../commons/text/CharacterPredicatesTest.java   |  50 +++
> .../commons/text/RandomStringBuilderTest.java   | 235 ------------
> .../commons/text/RandomStringGeneratorTest.java | 206 +++++++++++
> 7 files changed, 661 insertions(+), 589 deletions(-)
> ———————————————————————————————————

Apologies, should have split this into multiple commits but I got too far into it before I reached that conclusion!

> 
> 
> http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/CharacterPredicate.java
> ----------------------------------------------------------------------
> diff --git a/src/main/java/org/apache/commons/text/CharacterPredicate.java b/src/main/java/org/apache/commons/text/CharacterPredicate.java
> new file mode 100644
> index 0000000..fb23ac4
> --- /dev/null
> +++ b/src/main/java/org/apache/commons/text/CharacterPredicate.java
> @@ -0,0 +1,37 @@
> +/*
> + * 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;
> +
> +/**
> + * A predicate for selecting code points. Implementations of this interface must
> + * be thread safe.
> + * 
> + * @since 1.0
> + */
> +public interface CharacterPredicate {
> +    
> +    /**
> +     * Tests the code point with this predicate.
> +     * 
> +     * @param codePoint
> +     *            the code point to test
> +     * @return {@code true} if the code point matches the predicate,
> +     *         {@code false} otherwise
> +     * @since 1.0
> +     */
> +    boolean test(int codePoint);
> +}
> \ No newline at end of file
> 
> http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/CharacterPredicates.java
> ----------------------------------------------------------------------
> diff --git a/src/main/java/org/apache/commons/text/CharacterPredicates.java b/src/main/java/org/apache/commons/text/CharacterPredicates.java
> new file mode 100644
> index 0000000..7f38552
> --- /dev/null
> +++ b/src/main/java/org/apache/commons/text/CharacterPredicates.java
> @@ -0,0 +1,52 @@
> +/*
> + * 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;
> +
> +/**
> + * <p>
> + * Commonly used implementations of {@link CharacterPredicate}. Per the interface
> + * requirements, all implementations are thread safe.
> + * </p>
> + * 
> + * @since 1.0
> + */
> +public enum CharacterPredicates implements CharacterPredicate {
> +
> +    /**
> +     * Tests code points against {@link Character#isLetter(int)}
> +     * 
> +     * @since 1.0
> +     */
> +    LETTERS {
> +        @Override
> +        public boolean test(int codePoint) {
> +            return Character.isLetter(codePoint);
> +        }
> +    },
> +
> +    /**
> +     * Tests code points against {@link Character#isDigit(int)}.
> +     * 
> +     * @since 1.0
> +     */
> +    DIGITS {
> +        @Override
> +        public boolean test(int codePoint) {
> +            return Character.isDigit(codePoint);
> +        }
> +    }
> +}
> 
> http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/RandomStringBuilder.java
> ----------------------------------------------------------------------
> diff --git a/src/main/java/org/apache/commons/text/RandomStringBuilder.java b/src/main/java/org/apache/commons/text/RandomStringBuilder.java
> deleted file mode 100644
> index 38ba974..0000000
> --- a/src/main/java/org/apache/commons/text/RandomStringBuilder.java
> +++ /dev/null
> @@ -1,354 +0,0 @@
> -/*
> - * 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.HashSet;
> -import java.util.Random;
> -import java.util.Set;
> -
> -/**
> - * <p>
> - * Generates a random Unicode string based on properties defined using a builder
> - * pattern.
> - * </p>
> - * <p>
> - * Overriding the default properties is optional, however callers will need to
> - * define the length of the output string using {@link #ofLength(int)} to avoid
> - * generating an empty string.
> - * </p>
> - * <p>
> - * All the property setting methods return the {@code RandomStringBuilder}
> - * instance to allow for method chaining:
> - * </p>
> - * 
> - * <pre>
> - * // Generates a 20 code point string, using only the letters a-z
> - * String random = new RandomStringBuilder().ofLength(20).withinRange('a','z').build();
> - * </pre>
> - * 
> - * <p>
> - * The type of code point returned can be filtered using
> - * {@link #filteredBy(CodePointPredicate...)}, 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 (e.g. {@link #LETTERS} or
> - * {@link #DIGITS}) and others can be created by implementing
> - * {@link CodePointPredicate}.
> - * </p>
> - * 
> - * <pre>
> - * // Generates a 10 code point string containing only letters
> - * 
> - * import static org.apache.commons.text.RandomStringBuilder.LETTERS;
> - * ...
> - * String random = new RandomStringBuilder().ofLength(10).filteredBy(LETTERS).build();
> - * </pre>
> - * 
> - * <p>
> - * A {@code RandomStringBuilder} instance can be used multiple times to generate
> - * different random strings, however it cannot safely be shared between threads.
> - * </p>
> - * 
> - * @since 1.0
> - */
> -public class RandomStringBuilder implements Builder<String> {
> -
> -    /**
> -     * Default source of randomness
> -     */
> -    private static final Random DEFAULT_RANDOM = new Random();
> -
> -    /**
> -     * The default string length produced by this builder: {@value}
> -     * 
> -     * @since 1.0
> -     */
> -    public static final int DEFAULT_LENGTH = 0;
> -
> -    /**
> -     * The default minimum code point allowed: {@value}
> -     * 
> -     * @since 1.0
> -     */
> -    public static final int DEFAULT_MINIMUM_CODE_POINT = 0;
> -
> -    /**
> -     * The default maximum code point allowed: {@link Character#MAX_CODE_POINT}
> -     * ({@value})
> -     * 
> -     * @since 1.0
> -     */
> -    public static final int DEFAULT_MAXIMUM_CODE_POINT = Character.MAX_CODE_POINT;
> -
> -    private int length = 0;
> -    private int minimumCodePoint = 0;
> -    private int maximumCodePoint = Character.MAX_CODE_POINT;
> -    private Set<CodePointPredicate> inclusivePredicates = null;
> -    private Random random = null;
> -
> -    /**
> -     * <p>
> -     * Constructs a builder with default properties:
> -     * </p>
> -     *
> -     * <ul>
> -     * <li>Length: {@value #DEFAULT_LENGTH}</li>
> -     * <li>Minimum code point: {@value #DEFAULT_MINIMUM_CODE_POINT}</li>
> -     * <li>Maximum code point: {@link Character#MAX_CODE_POINT}</li>
> -     * <li>Default source of randomness</li>
> -     * <li>No character filters</li>
> -     * </ul>
> -     * 
> -     * @since 1.0
> -     */
> -    public RandomStringBuilder() {
> -    }
> -
> -    /**
> -     * <p>
> -     * Specifies how many code points to generate in the random string.
> -     * </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 {@code this}, to allow method chaining
> -     * @throws IllegalArgumentException
> -     *             if {@code length < 0}
> -     * @since 1.0
> -     */
> -    public RandomStringBuilder ofLength(final int length) {
> -        if (length < 0) {
> -            throw new IllegalArgumentException(String.format("Length %d is smaller than zero.", length));
> -        }
> -
> -        this.length = length;
> -        return this;
> -    }
> -
> -    /**
> -     * <p>
> -     * Specifies the minimum and maximum code points allowed in the generated
> -     * string.
> -     * </p>
> -     * 
> -     * @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}
> -     * @since 1.0
> -     */
> -    public RandomStringBuilder withinRange(final int minimumCodePoint, final int maximumCodePoint) {
> -        if (minimumCodePoint > maximumCodePoint) {
> -            throw new IllegalArgumentException(String.format(
> -                    "Minimum code point %d is larger than maximum code point %d", minimumCodePoint, maximumCodePoint));
> -        }
> -        if (minimumCodePoint < 0) {
> -            throw new IllegalArgumentException(String.format("Minimum code point %d is negative", minimumCodePoint));
> -        }
> -        if (maximumCodePoint > Character.MAX_CODE_POINT) {
> -            throw new IllegalArgumentException(
> -                    String.format("Value %d is larger than Character.MAX_CODE_POINT.", maximumCodePoint));
> -        }
> -
> -        this.minimumCodePoint = minimumCodePoint;
> -        this.maximumCodePoint = maximumCodePoint;
> -        return this;
> -    }
> -
> -    /**
> -     * <p>
> -     * Overrides the default source of randomness.
> -     * </p>
> -     * 
> -     * <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
> -     * @since 1.0
> -     */
> -    public RandomStringBuilder usingRandom(final Random random) {
> -        this.random = random;
> -        return this;
> -    }
> -
> -    /**
> -     * <p>
> -     * Limits the characters in the generated string to those that match at
> -     * least one of the predicates supplied.
> -     * </p>
> -     * 
> -     * <p>
> -     * Passing {@code null} or an empty array to this method will revert to the
> -     * default behaviour 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
> -     * @since 1.0
> -     */
> -    public RandomStringBuilder filteredBy(final CodePointPredicate... predicates) {
> -        if (predicates == null || predicates.length == 0) {
> -            inclusivePredicates = null;
> -            return this;
> -        }
> -
> -        if (inclusivePredicates == null) {
> -            inclusivePredicates = new HashSet<>();
> -        } else {
> -            inclusivePredicates.clear();
> -        }
> -
> -        for (CodePointPredicate predicate : predicates) {
> -            inclusivePredicates.add(predicate);
> -        }
> -
> -        return this;
> -    }
> -
> -    /**
> -     * <p>
> -     * Generates a random string using the settings defined in this builder.
> -     * Code points are randomly selected between the minimum and maximum values.
> -     * 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>
> -     * A static {@code Random} instance is used if an alternative wasn't
> -     * provided via {@link #usingRandom(Random)}.
> -     * </p>
> -     * 
> -     * @return the randomly generated string
> -     * @since 1.0
> -     */
> -    @Override
> -    public String build() {
> -        if (length == 0) {
> -            return "";
> -        }
> -
> -        if (random == null) {
> -            random = DEFAULT_RANDOM;
> -        }
> -
> -        final StringBuilder builder = new StringBuilder(length);
> -        long remaining = length;
> -
> -        do {
> -            int codePoint = random.nextInt(maximumCodePoint - minimumCodePoint + 1) + minimumCodePoint;
> -
> -            switch (Character.getType(codePoint)) {
> -            case Character.UNASSIGNED:
> -            case Character.PRIVATE_USE:
> -            case Character.SURROGATE:
> -                continue;
> -            }
> -
> -            if (inclusivePredicates != null) {
> -                boolean matchedFilter = false;
> -                for (CodePointPredicate predicate : inclusivePredicates) {
> -                    if (predicate.test(codePoint)) {
> -                        matchedFilter = true;
> -                        break;
> -                    }
> -                }
> -                if (!matchedFilter) {
> -                    continue;
> -                }
> -            }
> -
> -            builder.appendCodePoint(codePoint);
> -            remaining--;
> -
> -        } while (remaining != 0);
> -
> -        return builder.toString();
> -    }
> -
> -    /**
> -     * A predicate for selecting code points.
> -     * 
> -     * @since 1.0
> -     */
> -    public static interface CodePointPredicate {
> -        /**
> -         * Tests the code point with this predicate.
> -         * 
> -         * @param codePoint
> -         *            the code point to test
> -         * @return {@code true} if the code point matches the predicate,
> -         *         {@code false} otherwise
> -         * @since 1.0
> -         */
> -        boolean test(int codePoint);
> -    }
> -
> -    /**
> -     * Tests code points against {@link Character#isLetter(int)}.
> -     * 
> -     * @since 1.0
> -     */
> -    public static final CodePointPredicate LETTERS = new LetterPredicate();
> -
> -    /**
> -     * Tests code points against {@link Character#isDigit(int)}.
> -     * 
> -     * @since 1.0
> -     */
> -    public static final CodePointPredicate DIGITS = new DigitPredicate();
> -
> -    /**
> -     * Tests whether code points are letters.
> -     */
> -    private static final class LetterPredicate implements CodePointPredicate {
> -        @Override
> -        public boolean test(int codePoint) {
> -            return Character.isLetter(codePoint);
> -        }
> -    }
> -
> -    /**
> -     * Tests whether code points are digits.
> -     */
> -    private static final class DigitPredicate implements CodePointPredicate {
> -        @Override
> -        public boolean test(int codePoint) {
> -            return Character.isDigit(codePoint);
> -        }
> -    };
> -}
> 
> http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/main/java/org/apache/commons/text/RandomStringGenerator.java
> ----------------------------------------------------------------------
> diff --git a/src/main/java/org/apache/commons/text/RandomStringGenerator.java b/src/main/java/org/apache/commons/text/RandomStringGenerator.java
> new file mode 100644
> index 0000000..c4411a2
> --- /dev/null
> +++ b/src/main/java/org/apache/commons/text/RandomStringGenerator.java
> @@ -0,0 +1,316 @@
> +/*
> + * 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.HashSet;
> +import java.util.Random;
> +import java.util.Set;
> +import java.util.concurrent.ThreadLocalRandom;
> +
> +/**
> + * <p>
> + * 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.
> + * </p>
> + * 
> + * <pre>
> + * // Generates a 20 code point string, using only the letters a-z
> + * RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange('a', 'z').build();
> + * String random = generator.generate(20);
> + * </pre>
> + * 
> + * <p>
> + * {@code RandomStringBuilder} instances are immutable and thread-safe.
> + * </p>
> + * 
> + * @since 1.0
> + */
> +public final class RandomStringGenerator {
> +
> +    private final int minimumCodePoint;
> +    private final int maximumCodePoint;
> +    private final Set<CharacterPredicate> inclusivePredicates;
> +    private final Random random;
> +
> +
> +    /**
> +     * 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
> +     */
> +    private RandomStringGenerator(int minimumCodePoint, int maximumCodePoint,
> +            Set<CharacterPredicate> inclusivePredicates, Random random) {
> +        this.minimumCodePoint = minimumCodePoint;
> +        this.maximumCodePoint = maximumCodePoint;
> +        this.inclusivePredicates = inclusivePredicates;
> +        this.random = random;
> +    }
> +    
> +    /**
> +     * 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);
> +    }
> +
> +
> +    /**
> +     * <p>
> +     * Generates a random string, containing the specified number of code points.
> +     * </p>
> +     * <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}
> +     * @since 1.0
> +     */
> +    public String generate(final int length) {
> +        if (length == 0) {
> +            return "";
> +        }
> +        
> +        if (length < 0) {
> +            throw new IllegalArgumentException(String.format("Length %d is smaller than zero.", length));
> +        }
> +
> +        final StringBuilder builder = new StringBuilder(length);
> +        long remaining = length;
> +
> +        do {
> +            int codePoint = generateRandomNumber(minimumCodePoint, maximumCodePoint);
> +
> +            switch (Character.getType(codePoint)) {
> +            case Character.UNASSIGNED:
> +            case Character.PRIVATE_USE:
> +            case Character.SURROGATE:
> +                continue;
> +            }
> +
> +            if (inclusivePredicates != null) {
> +                boolean matchedFilter = false;
> +                for (CharacterPredicate predicate : inclusivePredicates) {
> +                    if (predicate.test(codePoint)) {
> +                        matchedFilter = true;
> +                        break;
> +                    }
> +                }
> +                if (!matchedFilter) {
> +                    continue;
> +                }
> +            }
> +
> +            builder.appendCodePoint(codePoint);
> +            remaining--;
> +
> +        } while (remaining != 0);
> +
> +        return builder.toString();
> +    }
> +    
> +    
> +    /**
> +     * <p>A builder for generating {@code RandomStringGenerator} instances.</p>
> +     * <p>The behaviour 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(Random)}, 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.0
> +     */
> +    public static class Builder implements org.apache.commons.text.Builder<RandomStringGenerator> {
> +        
> +        /**
> +         * The default maximum code point allowed: {@link Character#MAX_CODE_POINT}
> +         * ({@value})
> +         * 
> +         * @since 1.0
> +         */
> +        public static final int DEFAULT_MAXIMUM_CODE_POINT = Character.MAX_CODE_POINT;
> +        
> +        /**
> +         * The default string length produced by this builder: {@value}
> +         * 
> +         * @since 1.0
> +         */
> +        public static final int DEFAULT_LENGTH = 0;
> +
> +        /**
> +         * The default minimum code point allowed: {@value}
> +         * 
> +         * @since 1.0
> +         */
> +        public static final int DEFAULT_MINIMUM_CODE_POINT = 0;
> +
> +        private int minimumCodePoint = DEFAULT_MINIMUM_CODE_POINT;
> +        private int maximumCodePoint = DEFAULT_MAXIMUM_CODE_POINT;
> +        private Set<CharacterPredicate> inclusivePredicates;
> +        private Random random;
> +        
> +        
> +        /**
> +         * <p>
> +         * Specifies the minimum and maximum code points allowed in the generated
> +         * string.
> +         * </p>
> +         * 
> +         * @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}
> +         * @since 1.0
> +         */
> +        public Builder withinRange(final int minimumCodePoint, final int maximumCodePoint) {
> +            if (minimumCodePoint > maximumCodePoint) {
> +                throw new IllegalArgumentException(String.format(
> +                        "Minimum code point %d is larger than maximum code point %d", minimumCodePoint, maximumCodePoint));
> +            }
> +            if (minimumCodePoint < 0) {
> +                throw new IllegalArgumentException(String.format("Minimum code point %d is negative", minimumCodePoint));
> +            }
> +            if (maximumCodePoint > Character.MAX_CODE_POINT) {
> +                throw new IllegalArgumentException(
> +                        String.format("Value %d is larger than Character.MAX_CODE_POINT.", maximumCodePoint));
> +            }
> +
> +            this.minimumCodePoint = minimumCodePoint;
> +            this.maximumCodePoint = maximumCodePoint;
> +            return this;
> +        }
> +        
> +        /**
> +         * <p>
> +         * Limits the characters in the generated string to those that match at
> +         * least one of the predicates supplied.
> +         * </p>
> +         * 
> +         * <p>
> +         * Passing {@code null} or an empty array to this method will revert to the
> +         * default behaviour 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
> +         * @since 1.0
> +         */
> +        public Builder filteredBy(final CharacterPredicate... predicates) {
> +            if (predicates == null || predicates.length == 0) {
> +                inclusivePredicates = null;
> +                return this;
> +            }
> +
> +            if (inclusivePredicates == null) {
> +                inclusivePredicates = new HashSet<>();
> +            } else {
> +                inclusivePredicates.clear();
> +            }
> +
> +            for (CharacterPredicate predicate : predicates) {
> +                inclusivePredicates.add(predicate);
> +            }
> +
> +            return this;
> +        }
> +        
> +        /**
> +         * <p>
> +         * Overrides the default source of randomness.
> +         * </p>
> +         * 
> +         * <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
> +         * @since 1.0
> +         */
> +        public Builder usingRandom(final Random random) {
> +            this.random = random;
> +            return this;
> +        }
> +
> +        /**
> +         * <p>Builds the {@code RandomStringGenerator} using the properties specified.</p>
> +         */
> +        @Override
> +        public RandomStringGenerator build() {
> +            return new RandomStringGenerator(minimumCodePoint, maximumCodePoint, inclusivePredicates, random);
> +        }
> +        
> +    }
> +}
> 
> http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java
> ----------------------------------------------------------------------
> diff --git a/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java b/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java
> new file mode 100644
> index 0000000..3ff8b74
> --- /dev/null
> +++ b/src/test/java/org/apache/commons/text/CharacterPredicatesTest.java
> @@ -0,0 +1,50 @@
> +/*
> + * 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 static org.junit.Assert.assertTrue;
> +
> +import org.junit.Test;
> +
> +/**
> + * Tests for implementations in the {@link CharacterPredicates} enum.
> + */
> +public class CharacterPredicatesTest {
> +    @Test
> +    public void testDigitPredicate() throws Exception {
> +        String str = new RandomStringGenerator.Builder().filteredBy(CharacterPredicates.DIGITS).build().generate(5000);
> +
> +        int i = 0;
> +        do {
> +            int codePoint = str.codePointAt(i);
> +            assertTrue(Character.isDigit(codePoint));
> +            i += Character.charCount(codePoint);
> +        } while (i < str.length());
> +    }
> +
> +    @Test
> +    public void testLetterPredicate() throws Exception {
> +        String str = new RandomStringGenerator.Builder().filteredBy(CharacterPredicates.LETTERS).build().generate(5000);
> +
> +        int i = 0;
> +        do {
> +            int codePoint = str.codePointAt(i);
> +            assertTrue(Character.isLetter(codePoint));
> +            i += Character.charCount(codePoint);
> +        } while (i < str.length());
> +    }
> +}
> 
> http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java
> ----------------------------------------------------------------------
> diff --git a/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java b/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java
> deleted file mode 100644
> index e6f9f81..0000000
> --- a/src/test/java/org/apache/commons/text/RandomStringBuilderTest.java
> +++ /dev/null
> @@ -1,235 +0,0 @@
> -/*
> - * 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 static org.apache.commons.text.RandomStringBuilder.LETTERS;
> -import static org.junit.Assert.*;
> -
> -import java.util.Random;
> -
> -import org.apache.commons.text.RandomStringBuilder.CodePointPredicate;
> -import org.junit.Test;
> -
> -/**
> - * Tests for {@link RandomStringBuilder}
> - */
> -public class RandomStringBuilderTest {
> -
> -    private static int codePointLength(String s) {
> -        return s.codePointCount(0, s.length());
> -    }
> -
> -    private static final CodePointPredicate A_FILTER = new CodePointPredicate() {
> -        @Override
> -        public boolean test(int codePoint) {
> -            return codePoint == 'a';
> -        }
> -    };
> -
> -    private static final CodePointPredicate B_FILTER = new CodePointPredicate() {
> -        @Override
> -        public boolean test(int codePoint) {
> -            return codePoint == 'b';
> -        }
> -    };
> -
> -    @Test
> -    public void testDefaultLength() throws Exception {
> -        String str = new RandomStringBuilder().build();
> -        assertEquals(RandomStringBuilder.DEFAULT_LENGTH, codePointLength(str));
> -    }
> -
> -    @Test(expected = IllegalArgumentException.class)
> -    public void testInvalidLength() throws Exception {
> -        new RandomStringBuilder().ofLength(-1);
> -    }
> -
> -    @Test
> -    public void testSetLength() throws Exception {
> -        final int length = 99;
> -        String str = new RandomStringBuilder().ofLength(length).build();
> -        assertEquals(length, codePointLength(str));
> -    }
> -
> -    @Test(expected = IllegalArgumentException.class)
> -    public void testBadMinimumCodePoint() throws Exception {
> -        new RandomStringBuilder().withinRange(-1, 1);
> -    }
> -
> -    @Test(expected = IllegalArgumentException.class)
> -    public void testBadMaximumCodePoint() throws Exception {
> -        new RandomStringBuilder().withinRange(0, Character.MAX_CODE_POINT + 1);
> -    }
> -
> -    @Test
> -        public void testWithinRange() throws Exception {
> -            final int length = 5000;
> -            final int minimumCodePoint = 'a';
> -            final int maximumCodePoint = 'z';
> -            String str = new RandomStringBuilder().ofLength(length).withinRange(minimumCodePoint,maximumCodePoint).build();
> -    
> -            int i = 0;
> -            do {
> -                int codePoint = str.codePointAt(i);
> -                assertTrue(codePoint >= minimumCodePoint && codePoint <= maximumCodePoint);
> -                i += Character.charCount(codePoint);
> -            } while (i < str.length());
> -    
> -        }
> -
> -    @Test
> -    public void testNoLoneSurrogates() throws Exception {
> -        final int length = 5000;
> -        String str = new RandomStringBuilder().ofLength(length).build();
> -
> -        char lastChar = str.charAt(0);
> -        for (int i = 1; i < str.length(); i++) {
> -            char c = str.charAt(i);
> -
> -            if (Character.isLowSurrogate(c)) {
> -                assertTrue(Character.isHighSurrogate(lastChar));
> -            }
> -
> -            if (Character.isHighSurrogate(lastChar)) {
> -                assertTrue(Character.isLowSurrogate(c));
> -            }
> -
> -            if (Character.isHighSurrogate(c)) {
> -                // test this isn't the last character in the string
> -                assertTrue(i + 1 < str.length());
> -            }
> -
> -            lastChar = c;
> -        }
> -    }
> -
> -    @Test
> -    public void testUsingRandom() throws Exception {
> -        final char testChar = 'a';
> -        final Random testRandom = new Random() {
> -            private static final long serialVersionUID = 1L;
> -
> -            @Override
> -            public int nextInt(int n) {
> -                return testChar;
> -            }
> -        };
> -
> -        String str = new RandomStringBuilder().ofLength(100).usingRandom(testRandom).build();
> -        for (char c : str.toCharArray()) {
> -            assertEquals(testChar, c);
> -        }
> -    }
> -
> -    @Test
> -    public void testLetterPredicate() throws Exception {
> -        String str = new RandomStringBuilder().ofLength(5000).filteredBy(LETTERS).build();
> -
> -        int i = 0;
> -        do {
> -            int codePoint = str.codePointAt(i);
> -            assertTrue(Character.isLetter(codePoint));
> -            i += Character.charCount(codePoint);
> -        } while (i < str.length());
> -    }
> -
> -    @Test
> -    public void testDigitPredicate() throws Exception {
> -        String str = new RandomStringBuilder().ofLength(5000).filteredBy(RandomStringBuilder.DIGITS).build();
> -
> -        int i = 0;
> -        do {
> -            int codePoint = str.codePointAt(i);
> -            assertTrue(Character.isDigit(codePoint));
> -            i += Character.charCount(codePoint);
> -        } while (i < str.length());
> -    }
> -
> -    @Test
> -    public void testMultipleFilters() throws Exception {
> -        String str = new RandomStringBuilder().ofLength(5000).withinRange('a','d')
> -                .filteredBy(A_FILTER, B_FILTER).build();
> -
> -        boolean aFound = false;
> -        boolean bFound = false;
> -
> -        for (char c : str.toCharArray()) {
> -            if (c == 'a') {
> -                aFound = true;
> -            } else if (c == 'b') {
> -                bFound = true;
> -            } else {
> -                fail("Invalid character");
> -            }
> -        }
> -
> -        assertTrue(aFound && bFound);
> -    }
> -
> -    @Test
> -    public void testNoPrivateCharacters() throws Exception {
> -        final int startOfPrivateBMPChars = 0xE000;
> -
> -        // Request a string in an area of the Basic Multilingual Plane that is
> -        // largely
> -        // occupied by private characters
> -        String str = new RandomStringBuilder().ofLength(5000).withinRange(startOfPrivateBMPChars, 
> -                Character.MIN_SUPPLEMENTARY_CODE_POINT - 1).build();
> -
> -        int i = 0;
> -        do {
> -            int codePoint = str.codePointAt(i);
> -            assertFalse(Character.getType(codePoint) == Character.PRIVATE_USE);
> -            i += Character.charCount(codePoint);
> -        } while (i < str.length());
> -    }
> -
> -    @Test(expected = IllegalArgumentException.class)
> -    public void testBadMinAndMax() throws Exception {
> -        new RandomStringBuilder().withinRange(2, 1);
> -    }
> -
> -    @Test
> -    public void testRemoveFilters() throws Exception {
> -
> -        RandomStringBuilder builder = new RandomStringBuilder().ofLength(100).withinRange('a', 'z')
> -                .filteredBy(A_FILTER);
> -
> -        builder.filteredBy();
> -
> -        String str = builder.build();
> -        for (char c : str.toCharArray()) {
> -            if (c != 'a') {
> -                // filter was successfully removed
> -                return;
> -            }
> -        }
> -
> -        fail("Filter appears to have remained in place");
> -    }
> -
> -    @Test
> -    public void testChangeOfFilter() throws Exception {
> -        RandomStringBuilder builder = new RandomStringBuilder().ofLength(100).withinRange('a', 'z')
> -                .filteredBy(A_FILTER);
> -        String str = builder.filteredBy(B_FILTER).build();
> -
> -        for (char c : str.toCharArray()) {
> -            assertTrue(c == 'b');
> -        }
> -    }
> -}
> 
> http://git-wip-us.apache.org/repos/asf/commons-text/blob/08ac56a5/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java
> ----------------------------------------------------------------------
> diff --git a/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java b/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java
> new file mode 100644
> index 0000000..2ff7a67
> --- /dev/null
> +++ b/src/test/java/org/apache/commons/text/RandomStringGeneratorTest.java
> @@ -0,0 +1,206 @@
> +/*
> + * 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 static org.junit.Assert.*;
> +
> +import java.util.Random;
> +
> +import org.junit.Test;
> +
> +/**
> + * Tests for {@link RandomStringGenerator}
> + */
> +public class RandomStringGeneratorTest {
> +
> +    private static int codePointLength(String s) {
> +        return s.codePointCount(0, s.length());
> +    }
> +
> +    private static final CharacterPredicate A_FILTER = new CharacterPredicate() {
> +        @Override
> +        public boolean test(int codePoint) {
> +            return codePoint == 'a';
> +        }
> +    };
> +
> +    private static final CharacterPredicate B_FILTER = new CharacterPredicate() {
> +        @Override
> +        public boolean test(int codePoint) {
> +            return codePoint == 'b';
> +        }
> +    };
> +
> +    @Test(expected = IllegalArgumentException.class)
> +    public void testInvalidLength() throws Exception {
> +        RandomStringGenerator generator = new RandomStringGenerator.Builder().build();
> +        generator.generate(-1);
> +    }
> +
> +    @Test
> +    public void testSetLength() throws Exception {
> +        final int length = 99;
> +        RandomStringGenerator generator = new RandomStringGenerator.Builder().build();
> +        String str = generator.generate(length);
> +        assertEquals(length, codePointLength(str));
> +    }
> +
> +    @Test(expected = IllegalArgumentException.class)
> +    public void testBadMinimumCodePoint() throws Exception {
> +        new RandomStringGenerator.Builder().withinRange(-1, 1);
> +    }
> +
> +    @Test(expected = IllegalArgumentException.class)
> +    public void testBadMaximumCodePoint() throws Exception {
> +        new RandomStringGenerator.Builder().withinRange(0, Character.MAX_CODE_POINT + 1);
> +    }
> +
> +    @Test
> +        public void testWithinRange() throws Exception {
> +            final int length = 5000;
> +            final int minimumCodePoint = 'a';
> +            final int maximumCodePoint = 'z';
> +            RandomStringGenerator generator = new RandomStringGenerator.Builder().withinRange(minimumCodePoint,maximumCodePoint).build();
> +            String str = generator.generate(length);
> +    
> +            int i = 0;
> +            do {
> +                int codePoint = str.codePointAt(i);
> +                assertTrue(codePoint >= minimumCodePoint && codePoint <= maximumCodePoint);
> +                i += Character.charCount(codePoint);
> +            } while (i < str.length());
> +    
> +        }
> +
> +    @Test
> +    public void testNoLoneSurrogates() throws Exception {
> +        final int length = 5000;
> +        String str = new RandomStringGenerator.Builder().build().generate(length);
> +
> +        char lastChar = str.charAt(0);
> +        for (int i = 1; i < str.length(); i++) {
> +            char c = str.charAt(i);
> +
> +            if (Character.isLowSurrogate(c)) {
> +                assertTrue(Character.isHighSurrogate(lastChar));
> +            }
> +
> +            if (Character.isHighSurrogate(lastChar)) {
> +                assertTrue(Character.isLowSurrogate(c));
> +            }
> +
> +            if (Character.isHighSurrogate(c)) {
> +                // test this isn't the last character in the string
> +                assertTrue(i + 1 < str.length());
> +            }
> +
> +            lastChar = c;
> +        }
> +    }
> +
> +    @Test
> +    public void testUsingRandom() throws Exception {
> +        final char testChar = 'a';
> +        final Random testRandom = new Random() {
> +            private static final long serialVersionUID = 1L;
> +
> +            @Override
> +            public int nextInt(int n) {
> +                return testChar;
> +            }
> +        };
> +
> +        String str = new RandomStringGenerator.Builder().usingRandom(testRandom).build().generate(10);
> +        for (char c : str.toCharArray()) {
> +            assertEquals(testChar, c);
> +        }
> +    }
> +
> +    @Test
> +    public void testMultipleFilters() throws Exception {
> +        String str = new RandomStringGenerator.Builder().withinRange('a','d')
> +                .filteredBy(A_FILTER, B_FILTER).build().generate(5000);
> +
> +        boolean aFound = false;
> +        boolean bFound = false;
> +
> +        for (char c : str.toCharArray()) {
> +            if (c == 'a') {
> +                aFound = true;
> +            } else if (c == 'b') {
> +                bFound = true;
> +            } else {
> +                fail("Invalid character");
> +            }
> +        }
> +
> +        assertTrue(aFound && bFound);
> +    }
> +
> +    @Test
> +    public void testNoPrivateCharacters() throws Exception {
> +        final int startOfPrivateBMPChars = 0xE000;
> +
> +        // Request a string in an area of the Basic Multilingual Plane that is
> +        // largely
> +        // occupied by private characters
> +        String str = new RandomStringGenerator.Builder().withinRange(startOfPrivateBMPChars, 
> +                Character.MIN_SUPPLEMENTARY_CODE_POINT - 1).build().generate(5000);
> +
> +        int i = 0;
> +        do {
> +            int codePoint = str.codePointAt(i);
> +            assertFalse(Character.getType(codePoint) == Character.PRIVATE_USE);
> +            i += Character.charCount(codePoint);
> +        } while (i < str.length());
> +    }
> +
> +    @Test(expected = IllegalArgumentException.class)
> +    public void testBadMinAndMax() throws Exception {
> +        new RandomStringGenerator.Builder().withinRange(2, 1);
> +    }
> +
> +    @Test
> +    public void testRemoveFilters() throws Exception {
> +
> +        RandomStringGenerator.Builder builder = new RandomStringGenerator.Builder().withinRange('a', 'z')
> +                .filteredBy(A_FILTER);
> +
> +        builder.filteredBy();
> +
> +        String str = builder.build().generate(100);
> +        for (char c : str.toCharArray()) {
> +            if (c != 'a') {
> +                // filter was successfully removed
> +                return;
> +            }
> +        }
> +
> +        fail("Filter appears to have remained in place");
> +    }
> +
> +    @Test
> +    public void testChangeOfFilter() throws Exception {
> +        RandomStringGenerator.Builder builder = new RandomStringGenerator.Builder().withinRange('a', 'z')
> +                .filteredBy(A_FILTER);
> +        String str = builder.filteredBy(B_FILTER).build().generate(100);
> +
> +        for (char c : str.toCharArray()) {
> +            assertTrue(c == 'b');
> +        }
> +    }
> +}
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org