public static String random()

in src/main/java/org/apache/commons/lang3/RandomStringUtils.java [251:378]


    public static String random(int count, int start, int end, final boolean letters, final boolean numbers,
            final char[] chars, final Random random) {
        if (count == 0) {
            return StringUtils.EMPTY;
        }
        if (count < 0) {
            throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
        }
        if (chars != null && chars.length == 0) {
            throw new IllegalArgumentException("The chars array must not be empty");
        }

        if (start == 0 && end == 0) {
            if (chars != null) {
                end = chars.length;
            } else if (!letters && !numbers) {
                end = Character.MAX_CODE_POINT;
            } else {
                end = 'z' + 1;
                start = ' ';
            }
        } else if (end <= start) {
            throw new IllegalArgumentException(
                    "Parameter end (" + end + ") must be greater than start (" + start + ")");
        } else if (start < 0 || end < 0) {
            throw new IllegalArgumentException("Character positions MUST be >= 0");
        }

        if (end > Character.MAX_CODE_POINT) {
            // Technically, it should be `Character.MAX_CODE_POINT+1` as `end` is excluded
            // But the character `Character.MAX_CODE_POINT` is private use, so it would anyway be excluded
            end = Character.MAX_CODE_POINT;
        }

        // Optimizations and tests when chars == null and using ASCII characters (end <= 0x7f)
        if (chars == null && end <= 0x7f) {
            // Optimize generation of full alphanumerical characters
            // Normally, we would need to pick a 7-bit integer, since gap = 'z' - '0' + 1 = 75 > 64
            // In turn, this would make us reject the sampling with probability 1 - 62 / 2^7 > 1 / 2
            // Instead we can pick directly from the right set of 62 characters, which requires
            // picking a 6-bit integer and only rejecting with probability 2 / 64 = 1 / 32
            if (letters && numbers && start <= ASCII_0 && end >= ASCII_z + 1) {
                return random(count, 0, 0, false, false, ALPHANUMERICAL_CHARS, random);
            }

            if (numbers && end <= ASCII_0 || letters && end <= ASCII_A) {
                throw new IllegalArgumentException(
                        "Parameter end (" + end + ") must be greater then (" + ASCII_0 + ") for generating digits "
                                + "or greater then (" + ASCII_A + ") for generating letters.");
            }

            // Optimize start and end when filtering by letters and/or numbers:
            // The range provided may be too large since we filter anyway afterward.
            // Note the use of Math.min/max (as opposed to setting start to '0' for example),
            // since it is possible the range start/end excludes some of the letters/numbers,
            // e.g., it is possible that start already is '1' when numbers = true, and start
            // needs to stay equal to '1' in that case.
            // Note that because of the above test, we will always have start < end
            // even after this optimization.
            if (letters && numbers) {
                start = Math.max(ASCII_0, start);
                end = Math.min(ASCII_z + 1, end);
            } else if (numbers) {
                // just numbers, no letters
                start = Math.max(ASCII_0, start);
                end = Math.min(ASCII_9 + 1, end);
            } else if (letters) {
                // just letters, no numbers
                start = Math.max(ASCII_A, start);
                end = Math.min(ASCII_z + 1, end);
            }
        }

        final StringBuilder builder = new StringBuilder(count);
        final int gap = end - start;
        final int gapBits = Integer.SIZE - Integer.numberOfLeadingZeros(gap);
        // The size of the cache we use is an heuristic:
        // about twice the number of bytes required if no rejection
        // Ideally the cache size depends on multiple factor, including the cost of generating x bytes
        // of randomness as well as the probability of rejection. It is however not easy to know
        // those values programmatically for the general case.
        final CachedRandomBits arb = new CachedRandomBits((count * gapBits + 3) / 5 + 10, random);

        while (count-- != 0) {
            // Generate a random value between start (included) and end (excluded)
            final int randomValue = arb.nextBits(gapBits) + start;
            // Rejection sampling if value too large
            if (randomValue >= end) {
                count++;
                continue;
            }

            final int codePoint;
            if (chars == null) {
                codePoint = randomValue;

                switch (Character.getType(codePoint)) {
                case Character.UNASSIGNED:
                case Character.PRIVATE_USE:
                case Character.SURROGATE:
                    count++;
                    continue;
                }

            } else {
                codePoint = chars[randomValue];
            }

            final int numberOfChars = Character.charCount(codePoint);
            if (count == 0 && numberOfChars > 1) {
                count++;
                continue;
            }

            if (letters && Character.isLetter(codePoint) || numbers && Character.isDigit(codePoint)
                    || !letters && !numbers) {
                builder.appendCodePoint(codePoint);

                if (numberOfChars == 2) {
                    count--;
                }

            } else {
                count++;
            }
        }
        return builder.toString();
    }