001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.text; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Set; 024import java.util.concurrent.ThreadLocalRandom; 025 026import org.apache.commons.lang3.ArrayUtils; 027import org.apache.commons.lang3.StringUtils; 028import org.apache.commons.lang3.Validate; 029 030/** 031 * Generates random Unicode strings containing the specified number of code points. 032 * Instances are created using a builder class, which allows the 033 * callers to define the properties of the generator. See the documentation for the 034 * {@link Builder} class to see available properties. 035 * 036 * <pre> 037 * // Generates a 20 code point string, using only the letters a-z 038 * RandomStringGenerator generator = RandomStringGenerator.builder() 039 * .withinRange('a', 'z').build(); 040 * String randomLetters = generator.generate(20); 041 * </pre> 042 * <pre> 043 * // Using Apache Commons RNG for randomness 044 * UniformRandomProvider rng = RandomSource.create(...); 045 * // Generates a 20 code point string, using only the letters a-z 046 * RandomStringGenerator generator = RandomStringGenerator.builder() 047 * .withinRange('a', 'z') 048 * .usingRandom(rng::nextInt) // uses Java 8 syntax 049 * .build(); 050 * String randomLetters = generator.generate(20); 051 * </pre> 052 * <p> 053 * {@code RandomStringGenerator} instances are thread-safe when using the 054 * default random number generator (RNG). If a custom RNG is set by calling the method 055 * {@link Builder#usingRandom(TextRandomProvider) Builder.usingRandom(TextRandomProvider)}, thread-safety 056 * must be ensured externally. 057 * </p> 058 * @since 1.1 059 */ 060public final class RandomStringGenerator { 061 062 /** 063 * A builder for generating {@code RandomStringGenerator} instances. 064 * 065 * <p>The behavior of a generator is controlled by properties set by this 066 * builder. Each property has a default value, which can be overridden by 067 * calling the methods defined in this class, prior to calling {@link #build()}.</p> 068 * 069 * <p>All the property setting methods return the {@code Builder} instance to allow for method chaining.</p> 070 * 071 * <p>The minimum and maximum code point values are defined using {@link #withinRange(int, int)}. The 072 * default values are {@code 0} and {@link Character#MAX_CODE_POINT} respectively.</p> 073 * 074 * <p>The source of randomness can be set using {@link #usingRandom(TextRandomProvider)}, 075 * otherwise {@link ThreadLocalRandom} is used.</p> 076 * 077 * <p>The type of code points returned can be filtered using {@link #filteredBy(CharacterPredicate...)}, 078 * which defines a collection of tests that are applied to the randomly generated code points. 079 * The code points will only be included in the result if they pass at least one of the tests. 080 * Some commonly used predicates are provided by the {@link CharacterPredicates} enum.</p> 081 * 082 * <p>This class is not thread safe.</p> 083 * @since 1.1 084 */ 085 public static class Builder implements org.apache.commons.text.Builder<RandomStringGenerator> { 086 087 /** 088 * The default maximum code point allowed: {@link Character#MAX_CODE_POINT} 089 * ({@value}). 090 */ 091 public static final int DEFAULT_MAXIMUM_CODE_POINT = Character.MAX_CODE_POINT; 092 093 /** 094 * The default string length produced by this builder: {@value}. 095 */ 096 public static final int DEFAULT_LENGTH = 0; 097 098 /** 099 * The default minimum code point allowed: {@value}. 100 */ 101 public static final int DEFAULT_MINIMUM_CODE_POINT = 0; 102 103 /** 104 * The minimum code point allowed. 105 */ 106 private int minimumCodePoint = DEFAULT_MINIMUM_CODE_POINT; 107 108 /** 109 * The maximum code point allowed. 110 */ 111 private int maximumCodePoint = DEFAULT_MAXIMUM_CODE_POINT; 112 113 /** 114 * Filters for code points. 115 */ 116 private Set<CharacterPredicate> inclusivePredicates; 117 118 /** 119 * The source of randomness. 120 */ 121 private TextRandomProvider random; 122 123 /** 124 * The source of provided characters. 125 */ 126 private List<Character> characterList; 127 128 /** 129 * Builds a new {@code RandomStringGenerator}. 130 * 131 * @return A new {@code RandomStringGenerator} 132 * @deprecated Use {@link #get()}. 133 */ 134 @Deprecated 135 @Override 136 public RandomStringGenerator build() { 137 return get(); 138 } 139 140 /** 141 * Limits the characters in the generated string to those that match at 142 * least one of the predicates supplied. 143 * 144 * <p> 145 * Passing {@code null} or an empty array to this method will revert to the 146 * default behavior of allowing any character. Multiple calls to this 147 * method will replace the previously stored predicates. 148 * </p> 149 * 150 * @param predicates 151 * the predicates, may be {@code null} or empty 152 * @return {@code this}, to allow method chaining 153 */ 154 public Builder filteredBy(final CharacterPredicate... predicates) { 155 if (ArrayUtils.isEmpty(predicates)) { 156 inclusivePredicates = null; 157 return this; 158 } 159 if (inclusivePredicates == null) { 160 inclusivePredicates = new HashSet<>(); 161 } else { 162 inclusivePredicates.clear(); 163 } 164 Collections.addAll(inclusivePredicates, predicates); 165 return this; 166 } 167 168 /** 169 * Builds a new {@code RandomStringGenerator}. 170 * 171 * @return A new {@code RandomStringGenerator} 172 * @since 1.12.0 173 */ 174 @Override 175 public RandomStringGenerator get() { 176 return new RandomStringGenerator(minimumCodePoint, maximumCodePoint, inclusivePredicates, 177 random, characterList); 178 } 179 180 /** 181 * Limits the characters in the generated string to those who match at 182 * supplied list of Character. 183 * 184 * <p> 185 * Passing {@code null} or an empty array to this method will revert to the 186 * default behavior of allowing any character. Multiple calls to this 187 * method will replace the previously stored Character. 188 * </p> 189 * 190 * @param chars set of predefined Characters for random string generation 191 * the Character can be, may be {@code null} or empty 192 * @return {@code this}, to allow method chaining 193 * @since 1.2 194 */ 195 public Builder selectFrom(final char... chars) { 196 characterList = new ArrayList<>(); 197 if (chars != null) { 198 for (final char c : chars) { 199 characterList.add(c); 200 } 201 } 202 return this; 203 } 204 205 /** 206 * Overrides the default source of randomness. It is highly 207 * recommended that a random number generator library like 208 * <a href="https://commons.apache.org/proper/commons-rng/">Apache Commons RNG</a> 209 * be used to provide the random number generation. 210 * 211 * <p> 212 * When using Java 8 or later, {@link TextRandomProvider} is a 213 * functional interface and need not be explicitly implemented: 214 * </p> 215 * <pre> 216 * {@code 217 * UniformRandomProvider rng = RandomSource.create(...); 218 * RandomStringGenerator gen = RandomStringGenerator.builder() 219 * .usingRandom(rng::nextInt) 220 * // additional builder calls as needed 221 * .build(); 222 * } 223 * </pre> 224 * 225 * <p> 226 * Passing {@code null} to this method will revert to the default source of 227 * randomness. 228 * </p> 229 * 230 * @param random 231 * the source of randomness, may be {@code null} 232 * @return {@code this}, to allow method chaining 233 */ 234 public Builder usingRandom(final TextRandomProvider random) { 235 this.random = random; 236 return this; 237 } 238 239 /** 240 * Sets the array of minimum and maximum char allowed in the 241 * generated string. 242 * 243 * For example: 244 * <pre> 245 * {@code 246 * char [][] pairs = {{'0','9'}}; 247 * char [][] pairs = {{'a','z'}}; 248 * char [][] pairs = {{'a','z'},{'0','9'}}; 249 * } 250 * </pre> 251 * 252 * @param pairs array of characters array, expected is to pass min, max pairs through this arg. 253 * @return {@code this}, to allow method chaining. 254 */ 255 public Builder withinRange(final char[]... pairs) { 256 characterList = new ArrayList<>(); 257 if (pairs != null) { 258 for (final char[] pair : pairs) { 259 Validate.isTrue(pair.length == 2, "Each pair must contain minimum and maximum code point"); 260 final int minimumCodePoint = pair[0]; 261 final int maximumCodePoint = pair[1]; 262 Validate.isTrue(minimumCodePoint <= maximumCodePoint, "Minimum code point %d is larger than maximum code point %d", minimumCodePoint, 263 maximumCodePoint); 264 265 for (int index = minimumCodePoint; index <= maximumCodePoint; index++) { 266 characterList.add((char) index); 267 } 268 } 269 } 270 return this; 271 272 } 273 274 /** 275 * Sets the minimum and maximum code points allowed in the 276 * generated string. 277 * 278 * @param minimumCodePoint 279 * the smallest code point allowed (inclusive) 280 * @param maximumCodePoint 281 * the largest code point allowed (inclusive) 282 * @return {@code this}, to allow method chaining 283 * @throws IllegalArgumentException 284 * if {@code maximumCodePoint >} 285 * {@link Character#MAX_CODE_POINT} 286 * @throws IllegalArgumentException 287 * if {@code minimumCodePoint < 0} 288 * @throws IllegalArgumentException 289 * if {@code minimumCodePoint > maximumCodePoint} 290 */ 291 public Builder withinRange(final int minimumCodePoint, final int maximumCodePoint) { 292 Validate.isTrue(minimumCodePoint <= maximumCodePoint, 293 "Minimum code point %d is larger than maximum code point %d", minimumCodePoint, maximumCodePoint); 294 Validate.isTrue(minimumCodePoint >= 0, "Minimum code point %d is negative", minimumCodePoint); 295 Validate.isTrue(maximumCodePoint <= Character.MAX_CODE_POINT, 296 "Value %d is larger than Character.MAX_CODE_POINT.", maximumCodePoint); 297 this.minimumCodePoint = minimumCodePoint; 298 this.maximumCodePoint = maximumCodePoint; 299 return this; 300 } 301 } 302 303 /** 304 * Constructs a new builder. 305 * @return a new builder. 306 * 307 * @since 1.11.0 308 */ 309 public static Builder builder() { 310 return new Builder(); 311 } 312 313 /** 314 * The smallest allowed code point (inclusive). 315 */ 316 private final int minimumCodePoint; 317 318 /** 319 * The largest allowed code point (inclusive). 320 */ 321 private final int maximumCodePoint; 322 323 /** 324 * Filters for code points. 325 */ 326 private final Set<CharacterPredicate> inclusivePredicates; 327 328 /** 329 * The source of randomness for this generator. 330 */ 331 private final TextRandomProvider random; 332 333 /** 334 * The source of provided characters. 335 */ 336 private final List<Character> characterList; 337 338 /** 339 * Constructs the generator. 340 * 341 * @param minimumCodePoint 342 * smallest allowed code point (inclusive) 343 * @param maximumCodePoint 344 * largest allowed code point (inclusive) 345 * @param inclusivePredicates 346 * filters for code points 347 * @param random 348 * source of randomness 349 * @param characterList list of predefined set of characters. 350 */ 351 private RandomStringGenerator(final int minimumCodePoint, final int maximumCodePoint, 352 final Set<CharacterPredicate> inclusivePredicates, final TextRandomProvider random, 353 final List<Character> characterList) { 354 this.minimumCodePoint = minimumCodePoint; 355 this.maximumCodePoint = maximumCodePoint; 356 this.inclusivePredicates = inclusivePredicates; 357 this.random = random; 358 this.characterList = characterList; 359 } 360 361 /** 362 * Generates a random string, containing the specified number of code points. 363 * 364 * <p> 365 * Code points are randomly selected between the minimum and maximum values defined 366 * in the generator. 367 * Surrogate and private use characters are not returned, although the 368 * resulting string may contain pairs of surrogates that together encode a 369 * supplementary character. 370 * </p> 371 * <p> 372 * Note: the number of {@code char} code units generated will exceed 373 * {@code length} if the string contains supplementary characters. See the 374 * {@link Character} documentation to understand how Java stores Unicode 375 * values. 376 * </p> 377 * 378 * @param length 379 * the number of code points to generate 380 * @return The generated string 381 * @throws IllegalArgumentException 382 * if {@code length < 0} 383 */ 384 public String generate(final int length) { 385 if (length == 0) { 386 return StringUtils.EMPTY; 387 } 388 Validate.isTrue(length > 0, "Length %d is smaller than zero.", length); 389 final StringBuilder builder = new StringBuilder(length); 390 long remaining = length; 391 do { 392 final int codePoint; 393 if (characterList != null && !characterList.isEmpty()) { 394 codePoint = generateRandomNumber(characterList); 395 } else { 396 codePoint = generateRandomNumber(minimumCodePoint, maximumCodePoint); 397 } 398 switch (Character.getType(codePoint)) { 399 case Character.UNASSIGNED: 400 case Character.PRIVATE_USE: 401 case Character.SURROGATE: 402 continue; 403 default: 404 } 405 if (inclusivePredicates != null) { 406 boolean matchedFilter = false; 407 for (final CharacterPredicate predicate : inclusivePredicates) { 408 if (predicate.test(codePoint)) { 409 matchedFilter = true; 410 break; 411 } 412 } 413 if (!matchedFilter) { 414 continue; 415 } 416 } 417 builder.appendCodePoint(codePoint); 418 remaining--; 419 } while (remaining != 0); 420 return builder.toString(); 421 } 422 423 /** 424 * Generates a random string, containing between the minimum (inclusive) and the maximum (inclusive) 425 * number of code points. 426 * 427 * @param minLengthInclusive 428 * the minimum (inclusive) number of code points to generate 429 * @param maxLengthInclusive 430 * the maximum (inclusive) number of code points to generate 431 * @return The generated string 432 * @throws IllegalArgumentException 433 * if {@code minLengthInclusive < 0}, or {@code maxLengthInclusive < minLengthInclusive} 434 * @see RandomStringGenerator#generate(int) 435 * @since 1.2 436 */ 437 public String generate(final int minLengthInclusive, final int maxLengthInclusive) { 438 Validate.isTrue(minLengthInclusive >= 0, "Minimum length %d is smaller than zero.", minLengthInclusive); 439 Validate.isTrue(minLengthInclusive <= maxLengthInclusive, 440 "Maximum length %d is smaller than minimum length %d.", maxLengthInclusive, minLengthInclusive); 441 return generate(generateRandomNumber(minLengthInclusive, maxLengthInclusive)); 442 } 443 444 /** 445 * Generates a random number within a range, using a {@link ThreadLocalRandom} instance 446 * or the user-supplied source of randomness. 447 * 448 * @param minInclusive 449 * the minimum value allowed 450 * @param maxInclusive 451 * the maximum value allowed 452 * @return The random number. 453 */ 454 private int generateRandomNumber(final int minInclusive, final int maxInclusive) { 455 if (random != null) { 456 return random.nextInt(maxInclusive - minInclusive + 1) + minInclusive; 457 } 458 return ThreadLocalRandom.current().nextInt(minInclusive, maxInclusive + 1); 459 } 460 461 /** 462 * Generates a random number within a range, using a {@link ThreadLocalRandom} instance 463 * or the user-supplied source of randomness. 464 * 465 * @param characterList predefined char list. 466 * @return The random number. 467 */ 468 private int generateRandomNumber(final List<Character> characterList) { 469 final int listSize = characterList.size(); 470 if (random != null) { 471 return String.valueOf(characterList.get(random.nextInt(listSize))).codePointAt(0); 472 } 473 return String.valueOf(characterList.get(ThreadLocalRandom.current().nextInt(0, listSize))).codePointAt(0); 474 } 475}