├── .gitignore ├── .travis.yml ├── README.md ├── pom.xml └── src ├── main └── java │ └── net │ └── moznion │ └── random │ └── string │ ├── DigitLetter.java │ ├── Letter.java │ ├── LowerCaseLetter.java │ ├── RandomLetterPicker.java │ ├── RandomLetterPickers.java │ ├── RandomStringGenerator.java │ ├── RegexNormalizer.java │ ├── SymbolLetter.java │ ├── UpperCaseLetter.java │ └── UserDefinedLetterPickerScanner.java └── test └── java └── net └── moznion └── random └── string └── RandomStringGeneratorTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Java ### 4 | *.class 5 | 6 | # Mobile Tools for Java (J2ME) 7 | .mtj.tmp/ 8 | 9 | # Package Files # 10 | *.jar 11 | *.war 12 | *.ear 13 | 14 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 15 | hs_err_pid* 16 | 17 | /target/ 18 | 19 | /.checkstyle 20 | /.classpath 21 | /.project 22 | /.settings/ 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | install: mvn install -DskipTests=true -Dgpg.skip=true 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | random-string [![Build Status](https://travis-ci.org/moznion/java-random-string.svg)](https://travis-ci.org/moznion/java-random-string) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.moznion/random-string/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.moznion/random-string) [![javadoc.io](https://javadocio-badges.herokuapp.com/net.moznion/random-string/badge.svg)](https://javadocio-badges.herokuapp.com/net.moznion/random-string) 2 | ============= 3 | 4 | Generate random strings based on a pattern. 5 | 6 | Synopsis 7 | --- 8 | 9 | ### From pattern 10 | 11 | ```java 12 | RandomStringGenerator generator = new RandomStringGenerator(); 13 | 14 | // generates random string (e.g. "aB4@X.Ç") 15 | String randomString = generator.generateFromPattern("cCn!.sb"); 16 | ``` 17 | 18 | ### From regex 19 | 20 | ```java 21 | RandomStringGenerator generator = new RandomStringGenerator(); 22 | 23 | // generates random string (e.g. "a5B123 18X") 24 | String randomString = generator.generateByRegex("\\w+\\d*\\s[0-9]{0,3}X"); 25 | ``` 26 | 27 | Description 28 | -- 29 | 30 | Generate random string based on a patter. 31 | This library is port of [String::Random](https://metacpan.org/pod/String::Random) from Perl to Java. 32 | 33 | Methods 34 | -- 35 | 36 | Please refer to javadoc. 37 | 38 | [![javadoc.io](https://javadocio-badges.herokuapp.com/net.moznion/random-string/badge.svg)](https://javadocio-badges.herokuapp.com/net.moznion/random-string) 39 | 40 | Author 41 | -- 42 | 43 | moznion () 44 | 45 | License 46 | -- 47 | 48 | ``` 49 | The MIT License (MIT) 50 | Copyright © 2015 moznion, http://moznion.net/ 51 | 52 | Permission is hereby granted, free of charge, to any person obtaining a copy 53 | of this software and associated documentation files (the “Software”), to deal 54 | in the Software without restriction, including without limitation the rights 55 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 56 | copies of the Software, and to permit persons to whom the Software is 57 | furnished to do so, subject to the following conditions: 58 | 59 | The above copyright notice and this permission notice shall be included in 60 | all copies or substantial portions of the Software. 61 | 62 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 63 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 64 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 65 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 66 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 67 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 68 | THE SOFTWARE. 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | net.moznion 5 | random-string 6 | 1.1.1-SNAPSHOT 7 | jar 8 | 9 | random-string 10 | Generate random strings based on a pattern 11 | https://github.com/moznion/java-random-string 12 | 13 | 14 | UTF-8 15 | UTF-8 16 | 17 | 18 | 19 | 20 | junit 21 | junit 22 | 4.13.1 23 | test 24 | 25 | 26 | org.projectlombok 27 | lombok 28 | 1.14.8 29 | provided 30 | 31 | 32 | com.google.code.findbugs 33 | findbugs 34 | 3.0.0 35 | test 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.apache.maven.plugins 43 | maven-compiler-plugin 44 | 3.1 45 | 46 | 1.8 47 | 1.8 48 | 1.8 49 | UTF-8 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-jar-plugin 56 | 2.5 57 | 58 | 59 | 60 | true 61 | true 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-release-plugin 70 | 2.5.1 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-source-plugin 76 | 2.4 77 | 78 | 79 | attach-sources 80 | 81 | jar 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-javadoc-plugin 90 | 2.10.1 91 | 92 | 93 | attach-javadocs 94 | 95 | jar 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-surefire-plugin 104 | 2.18 105 | 106 | 107 | org.slf4j.simpleLogger.defaultLogLevel 108 | debug 109 | 110 | 111 | 112 | 113 | 114 | org.sonatype.plugins 115 | nexus-staging-maven-plugin 116 | 1.6.5 117 | true 118 | 119 | ossrh 120 | https://oss.sonatype.org/ 121 | true 122 | 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-gpg-plugin 128 | 1.5 129 | 130 | 131 | sign-artifacts 132 | verify 133 | 134 | sign 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | org.codehaus.mojo 146 | findbugs-maven-plugin 147 | 3.0.0 148 | 149 | 150 | 151 | 152 | 153 | 154 | ossrh 155 | https://oss.sonatype.org/content/repositories/snapshots 156 | 157 | 158 | ossrh 159 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 160 | 161 | 162 | 163 | 164 | https://github.com/moznion/java-random-string 165 | scm:git:git@github.com:moznion/java-random-string.git 166 | scm:git:git@github.com:moznion/java-random-string.git 167 | HEAD 168 | 169 | 170 | 171 | 172 | MIT License 173 | http://www.opensource.org/licenses/mit-license.php 174 | repo 175 | 176 | 177 | 178 | 179 | 180 | moznion 181 | Taiki Kawakami 182 | moznion@gmail.com 183 | http://moznion.net 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/DigitLetter.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | enum DigitLetter implements Letter { 4 | ZERO("0"), ONE("1"), TWO("2"), THREE("3"), FOUR("4"), 5 | FIVE("5"), SIX("6"), SEVEN("7"), EIGHT("8"), NINE("9"); 6 | 7 | private final String letter; 8 | 9 | private DigitLetter(String letter) { 10 | this.letter = letter; 11 | } 12 | 13 | @Override 14 | public String getLetter() { 15 | return letter; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/Letter.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | interface Letter { 4 | public String getLetter(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/LowerCaseLetter.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | enum LowerCaseLetter implements Letter { 4 | A("a"), B("b"), C("c"), D("d"), E("e"), F("f"), G("g"), H("h"), 5 | I("i"), J("j"), K("k"), L("l"), M("m"), N("n"), O("o"), P("p"), 6 | Q("q"), R("r"), S("s"), T("t"), U("u"), V("v"), W("w"), X("x"), 7 | Y("y"), Z("z"); 8 | 9 | private final String letter; 10 | 11 | private LowerCaseLetter(String letter) { 12 | this.letter = letter; 13 | } 14 | 15 | @Override 16 | public String getLetter() { 17 | return letter; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/RandomLetterPicker.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Random; 10 | import java.util.stream.Collectors; 11 | 12 | class RandomLetterPicker { 13 | private final List letters; 14 | private final int size; 15 | private final Random random; 16 | 17 | @Getter 18 | public static class Builder { 19 | private List letters; 20 | private Random random; 21 | 22 | public Builder() { 23 | letters = new ArrayList<>(); 24 | random = null; 25 | } 26 | 27 | public & Letter> Builder addAllByEnum(Class enumClass) { 28 | letters.addAll(Arrays.stream(enumClass.getEnumConstants()) 29 | .map(e -> e.getLetter()) 30 | .collect(Collectors.toList())); 31 | return this; 32 | } 33 | 34 | public Builder addAll(List list) { 35 | letters.addAll(list); 36 | return this; 37 | } 38 | 39 | public Builder add(String letter) { 40 | letters.add(letter); 41 | return this; 42 | } 43 | 44 | public Builder remove(String remove) { 45 | letters = letters.stream() 46 | .filter(l -> !l.equals(remove)) 47 | .collect(Collectors.toList()); 48 | return this; 49 | } 50 | 51 | public Builder setRandom(Random random) { 52 | this.random = random; 53 | return this; 54 | } 55 | 56 | public RandomLetterPicker build() { 57 | return new RandomLetterPicker(this); 58 | } 59 | } 60 | 61 | public static Builder builder() { 62 | return new Builder(); 63 | } 64 | 65 | private RandomLetterPicker(Builder b) { 66 | letters = Collections.unmodifiableList(b.getLetters()); 67 | if (b.getRandom() == null) { 68 | random = new Random(); 69 | } else { 70 | random = b.getRandom(); 71 | } 72 | 73 | size = letters.size(); 74 | } 75 | 76 | public String pickRandomLetter() { 77 | return letters.get(random.nextInt(size)); 78 | } 79 | 80 | public static RandomLetterPicker constructByCharacterRange(final List bounds) { 81 | Builder definedPickerBuilder = builder(); 82 | int bufferSize = bounds.size(); 83 | 84 | for (int i = 0; i < bufferSize; i += 2) { 85 | int beginCode = (int) bounds.get(i).charAt(0); 86 | int endCode = (int) bounds.get(i + 1).charAt(0); 87 | 88 | if (beginCode > endCode) { 89 | throw new RuntimeException("Detected invalid character range: [" 90 | + (char) beginCode + "-" + (char) endCode + "]"); 91 | } 92 | 93 | for (int code = beginCode; code <= endCode; code++) { 94 | definedPickerBuilder.add(String.valueOf((char) code)); 95 | } 96 | } 97 | 98 | return definedPickerBuilder.build(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/RandomLetterPickers.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Random; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.IntStream; 8 | 9 | @Getter 10 | class RandomLetterPickers { 11 | private final RandomLetterPicker upperCase; 12 | private final RandomLetterPicker lowerCase; 13 | private final RandomLetterPicker digit; 14 | private final RandomLetterPicker symbol; 15 | private final RandomLetterPicker any; 16 | private final RandomLetterPicker salt; 17 | private final RandomLetterPicker binary; 18 | private final RandomLetterPicker word; 19 | private final RandomLetterPicker notWord; 20 | private final RandomLetterPicker notDigit; 21 | private final RandomLetterPicker space; 22 | 23 | public RandomLetterPickers(Random random) { 24 | upperCase = RandomLetterPicker.builder() 25 | .setRandom(random) 26 | .addAllByEnum(UpperCaseLetter.class) 27 | .build(); 28 | 29 | lowerCase = RandomLetterPicker.builder() 30 | .setRandom(random) 31 | .addAllByEnum(LowerCaseLetter.class) 32 | .build(); 33 | 34 | digit = RandomLetterPicker.builder() 35 | .setRandom(random) 36 | .addAllByEnum(DigitLetter.class) 37 | .build(); 38 | 39 | symbol = RandomLetterPicker.builder() 40 | .setRandom(random) 41 | .addAllByEnum(SymbolLetter.class) 42 | .build(); 43 | 44 | any = RandomLetterPicker.builder() 45 | .setRandom(random) 46 | .addAllByEnum(UpperCaseLetter.class) 47 | .addAllByEnum(LowerCaseLetter.class) 48 | .addAllByEnum(DigitLetter.class) 49 | .addAllByEnum(SymbolLetter.class) 50 | .build(); 51 | 52 | salt = RandomLetterPicker.builder() 53 | .setRandom(random) 54 | .addAllByEnum(UpperCaseLetter.class) 55 | .addAllByEnum(LowerCaseLetter.class) 56 | .addAllByEnum(DigitLetter.class) 57 | .add(".") 58 | .add("/") 59 | .build(); 60 | 61 | binary = RandomLetterPicker.builder() 62 | .setRandom(random) 63 | .addAll(IntStream.range(0, 255) 64 | .mapToObj(i -> Character.toString((char) i)) 65 | .collect(Collectors.toList())) 66 | .build(); 67 | 68 | word = RandomLetterPicker.builder() 69 | .setRandom(random) 70 | .addAllByEnum(UpperCaseLetter.class) 71 | .addAllByEnum(LowerCaseLetter.class) 72 | .addAllByEnum(DigitLetter.class) 73 | .add("_") 74 | .build(); 75 | 76 | notWord = RandomLetterPicker.builder() 77 | .setRandom(random) 78 | .addAllByEnum(SymbolLetter.class) 79 | .remove("_") 80 | .build(); 81 | 82 | notDigit = RandomLetterPicker.builder() 83 | .setRandom(random) 84 | .addAllByEnum(UpperCaseLetter.class) 85 | .addAllByEnum(LowerCaseLetter.class) 86 | .addAllByEnum(SymbolLetter.class) 87 | .build(); 88 | 89 | space = RandomLetterPicker.builder() 90 | .setRandom(random) 91 | .add(" ") 92 | .add("\t") 93 | .build(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/RandomStringGenerator.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | import net.moznion.random.string.UserDefinedLetterPickerScanner.ScannedUserDefinedPicker; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Random; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * Generator of random string. 13 | * 14 | *

15 | * This class doesn't generate secure strings. So please use SecureRandom class if you want to use 16 | * with such a purpose. 17 | * 18 | * @author moznion 19 | * 20 | */ 21 | public class RandomStringGenerator { 22 | private int numOfUpperLimit; 23 | private final Map userDefinedPickers; 24 | private final Random random; 25 | private final RandomLetterPickers pickers; 26 | 27 | private static final int DEFAULT_NUM_OF_UPPER_LIMIT = 10; 28 | 29 | /** 30 | * Instantiate generator with a default number of upper limit for regex quantifiers (for example 31 | * {@code *}, {@code +} and etc; default value is 10) and a default instance of Random. 32 | */ 33 | public RandomStringGenerator() { 34 | this(new Random(), DEFAULT_NUM_OF_UPPER_LIMIT); 35 | } 36 | 37 | /** 38 | * Instantiate generator with a number of upper limit for regex quantifiers (for example {@code *} 39 | * , {@code +} and etc) and a default instance of Random. 40 | * 41 | * @param numOfUpperLimit Number of upper limit for quantifiers 42 | */ 43 | public RandomStringGenerator(int numOfUpperLimit) { 44 | this(new Random(), numOfUpperLimit); 45 | } 46 | 47 | /** 48 | * Instantiate generator with a default number of upper limit for regex quantifiers (for example 49 | * {@code *}, {@code +} and etc; default value is 10) and an instance of Random. 50 | * 51 | * @param random Instance of Random 52 | */ 53 | public RandomStringGenerator(Random random) { 54 | this(random, DEFAULT_NUM_OF_UPPER_LIMIT); 55 | } 56 | 57 | /** 58 | * Instantiate generator with a number of upper limit for regex quantifiers (for example {@code *} 59 | * , {@code +} and etc) and an instance of Random. 60 | * 61 | * @param random Instance of Random 62 | * @param numOfUpperLimit Number of upper limit for quantifiers 63 | */ 64 | public RandomStringGenerator(Random random, int numOfUpperLimit) { 65 | this.numOfUpperLimit = numOfUpperLimit; 66 | this.random = random; 67 | this.userDefinedPickers = new HashMap<>(); 68 | this.pickers = new RandomLetterPickers(this.random); 69 | } 70 | 71 | /** 72 | * Generate random string from pattern. 73 | * 74 | *

75 | * You can use following characters as pattern. 76 | *

    77 | *
  • {@code c} : Any Latin lower-case character
  • 78 | *
  • {@code C} : Any Latin upper-case character
  • 79 | *
  • {@code n} : Any digit {@code [0-9]}
  • 80 | *
  • {@code !} : A symbol character {@code [~`!@$%^&*()-_+= []|\:;"'.<>?/#,]}
  • 81 | *
  • {@code .} : Any of the above
  • 82 | *
  • {@code s} : A "salt" character {@code [A-Za-z0-9./]}
  • 83 | *
  • {@code b} : An ASCIII character which has code from 0 to 255
  • 84 | *
85 | * 86 | *

87 | * e.g. 88 | * 89 | *

 90 |    * 
 91 |    * RandomStringGenerator generator = new RandomStringGenerator();
 92 |    * 
 93 |    * // generates random string (e.g. "aB4@X.Ç")
 94 |    * String randomString = generator.generateFromPattern("cCn!.sb");
 95 |    * 
 96 |    * 
97 | * 98 | * @param pattern Pattern string 99 | * @return Random string which is generated according to pattern 100 | */ 101 | public String generateFromPattern(final String pattern) { 102 | return Arrays.stream(pattern.split("")).map(patternCharacter -> { 103 | RandomLetterPicker picker; 104 | switch (patternCharacter) { 105 | case "c": 106 | picker = pickers.getLowerCase(); 107 | break; 108 | case "C": 109 | picker = pickers.getUpperCase(); 110 | break; 111 | case "n": 112 | picker = pickers.getDigit(); 113 | break; 114 | case "!": 115 | picker = pickers.getSymbol(); 116 | break; 117 | case ".": 118 | picker = pickers.getAny(); 119 | break; 120 | case "s": 121 | picker = pickers.getSalt(); 122 | break; 123 | case "b": 124 | picker = pickers.getBinary(); 125 | break; 126 | default: 127 | throw new RuntimeException("Detected invalid pattern character: " + patternCharacter); 128 | } 129 | return picker.pickRandomLetter(); 130 | }).collect(Collectors.joining()); 131 | } 132 | 133 | /** 134 | * Generate random string from regular expression. 135 | * 136 | *

137 | * You can use following meta characters as regex. 138 | *

    139 | *
  • {@code \w} : Alphanumeric + "_" {@code [A-Za-z0-9_]}
  • 140 | *
  • {@code \d} : Digits {@code [0-9]}
  • 141 | *
  • {@code \W} : Printable characters other than those in \w
  • 142 | *
  • {@code \D} : Printable characters other than those in \d
  • 143 | *
  • {@code \s} : Whitespace characters {@code [ \t]}
  • 144 | *
  • {@code \S} : Printable characters
  • 145 | *
  • {@code .} : Printable characters
  • 146 | *
  • {@code []} : Character classes (Example of usage {@code [a-zA-Z]})
  • 147 | *
  • {}: Repetition
  • 148 | *
  • {@code *} : Same as {0,}
  • 149 | *
  • {@code ?} : Same as {0,1}
  • 150 | *
  • {@code +} : Same as {1,}
  • 151 | *
152 | * 153 | *

154 | * e.g. 155 | * 156 | *

157 |    * 
158 |    * RandomStringGenerator generator = new RandomStringGenerator();
159 |    * 
160 |    * // generates random string (e.g. "a5B123 18X")
161 |    * String randomString = generator.generateByRegex("\\w+\\d*\\s[0-9]{0,3}X");
162 |    * 
163 |    * 
164 | * 165 | * @param regex Pattern based on regular expression 166 | * @return Random String 167 | */ 168 | public String generateByRegex(final String regex) { 169 | String expanded = new RegexNormalizer(numOfUpperLimit, random).normalizeQuantifiers(regex); 170 | 171 | final String[] regexCharacters = expanded.split(""); 172 | final int length = regexCharacters.length; 173 | 174 | StringBuilder sb = new StringBuilder(); 175 | for (int i = 0; i < length; i++) { 176 | String character = regexCharacters[i]; 177 | RandomLetterPicker picker = null; 178 | String candidateCharacter = null; 179 | switch (character) { 180 | case "\\": 181 | try { 182 | character = regexCharacters[++i]; 183 | } catch (ArrayIndexOutOfBoundsException e) { 184 | throw new RuntimeException("Detected invalid escape character"); 185 | } 186 | 187 | switch (character) { 188 | case "w": 189 | picker = pickers.getWord(); 190 | break; 191 | case "d": 192 | picker = pickers.getDigit(); 193 | break; 194 | case "W": 195 | picker = pickers.getNotWord(); 196 | break; 197 | case "D": 198 | picker = pickers.getNotDigit(); 199 | break; 200 | case "s": 201 | picker = pickers.getSpace(); 202 | break; 203 | case "S": 204 | picker = pickers.getAny(); 205 | break; 206 | default: 207 | candidateCharacter = character; 208 | } 209 | break; 210 | case "[": 211 | try { 212 | ScannedUserDefinedPicker scannedUserDefinedPicker = 213 | UserDefinedLetterPickerScanner.scan(regexCharacters, i); 214 | String key = scannedUserDefinedPicker.getKey(); 215 | i = scannedUserDefinedPicker.getCursor(); 216 | 217 | if (userDefinedPickers.get(key) == null) { 218 | RandomLetterPicker userDefinedPicker = RandomLetterPicker 219 | .constructByCharacterRange(scannedUserDefinedPicker.getBounds()); 220 | userDefinedPickers.put(key, userDefinedPicker); 221 | } 222 | 223 | picker = userDefinedPickers.get(key); 224 | } catch (ArrayIndexOutOfBoundsException e) { 225 | throw new RuntimeException("Occurs parsing error"); 226 | } 227 | break; 228 | case ".": 229 | picker = pickers.getAny(); 230 | break; 231 | default: 232 | candidateCharacter = character; 233 | } 234 | 235 | int repetitionNum = 1; 236 | if (i + 1 < length) { 237 | String nextCharacter = regexCharacters[i + 1]; 238 | if (nextCharacter.equals("{")) { 239 | int j = i + 1; 240 | StringBuilder sbForQuantifier = new StringBuilder(); 241 | try { 242 | while (!(nextCharacter = regexCharacters[++j]).equals("}")) { 243 | sbForQuantifier.append(nextCharacter); 244 | } 245 | try { 246 | repetitionNum = Integer.parseInt(sbForQuantifier.toString(), 10); 247 | i = j; 248 | } catch (RuntimeException e) { 249 | // do nothing 250 | } 251 | } catch (ArrayIndexOutOfBoundsException e) { 252 | // do nothing 253 | } 254 | } 255 | } 256 | 257 | if (picker != null) { 258 | for (int j = 0; j < repetitionNum; j++) { 259 | sb.append(picker.pickRandomLetter()); 260 | } 261 | } else if (candidateCharacter != null) { 262 | for (int j = 0; j < repetitionNum; j++) { 263 | sb.append(candidateCharacter); 264 | } 265 | } else { 266 | throw new RuntimeException("Occurs parsing error"); 267 | } 268 | } 269 | 270 | return sb.toString(); 271 | } 272 | 273 | /** 274 | * Get number of upper limit for regex quantifiers, for example {@code *}, {@code +} and etc. 275 | * 276 | * @return Number of upper limit for quantifiers 277 | */ 278 | public int getNumOfUpperLimit() { 279 | return numOfUpperLimit; 280 | } 281 | 282 | /** 283 | * Set number of upper limit for regex quantifiers, for example {@code *}, {@code +} and etc. 284 | * 285 | * @param numOfUpperLimit Number of upper limit for quantifiers 286 | */ 287 | public void setNumOfUpperLimit(int numOfUpperLimit) { 288 | this.numOfUpperLimit = numOfUpperLimit; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/RegexNormalizer.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | import java.util.Random; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | class RegexNormalizer { 8 | // for repetition quantifier, e.g. {1,4} 9 | private static final Pattern REPETITION_QUANTIFIER_RE = Pattern 10 | .compile("([^\\\\])\\{([0-9]+),([0-9]+)?\\}"); 11 | private static final Pattern ASTERISK_QUANTIFIER_RE = Pattern.compile("([^\\\\])\\*"); 12 | private static final Pattern PLUS_QUANTIFIER_RE = Pattern.compile("([^\\\\])\\+"); 13 | private static final Pattern QUESTION_QUANTIFIER_RE = Pattern.compile("([^\\\\])\\?"); 14 | 15 | private final int numOfUpperLimit; 16 | private final Random random; 17 | 18 | public RegexNormalizer(int numOfUpperLimit, Random random) { 19 | this.numOfUpperLimit = numOfUpperLimit; 20 | this.random = random; 21 | } 22 | 23 | public String normalizeQuantifiers(final String regex) { 24 | String expanded = regex; 25 | 26 | Matcher repetitionMatcher = REPETITION_QUANTIFIER_RE.matcher(expanded); 27 | while (repetitionMatcher.find()) { 28 | int start = Integer.parseInt(repetitionMatcher.group(2), 10); 29 | int end; 30 | if (repetitionMatcher.group(3) == null) { 31 | end = numOfUpperLimit; 32 | } else { 33 | end = Integer.parseInt(repetitionMatcher.group(3), 10); 34 | } 35 | expanded = 36 | repetitionMatcher.replaceFirst(repetitionMatcher.group(1) + "{" 37 | + getRandomNumAsString(start, end) + "}"); 38 | repetitionMatcher = REPETITION_QUANTIFIER_RE.matcher(expanded); 39 | } 40 | 41 | Matcher asteriskMatcher = ASTERISK_QUANTIFIER_RE.matcher(expanded); 42 | while (asteriskMatcher.find()) { 43 | expanded = 44 | asteriskMatcher.replaceFirst(asteriskMatcher.group(1) + "{" 45 | + getRandomNumAsString(0, numOfUpperLimit) + "}"); 46 | asteriskMatcher = ASTERISK_QUANTIFIER_RE.matcher(expanded); 47 | } 48 | 49 | Matcher plusMatcher = PLUS_QUANTIFIER_RE.matcher(expanded); 50 | while (plusMatcher.find()) { 51 | expanded = 52 | plusMatcher.replaceFirst(plusMatcher.group(1) + "{" 53 | + getRandomNumAsString(1, numOfUpperLimit) + "}"); 54 | plusMatcher = PLUS_QUANTIFIER_RE.matcher(expanded); 55 | } 56 | 57 | Matcher questionMatcher = QUESTION_QUANTIFIER_RE.matcher(expanded); 58 | while (questionMatcher.find()) { 59 | expanded = 60 | questionMatcher.replaceFirst(questionMatcher.group(1) + "{" 61 | + getRandomNumAsString(0, 1) + "}"); 62 | questionMatcher = QUESTION_QUANTIFIER_RE.matcher(expanded); 63 | } 64 | 65 | return expanded; 66 | } 67 | 68 | private String getRandomNumAsString(final int start, final int end) { 69 | int bound = end - start; 70 | if (bound < 0) { 71 | throw new RuntimeException("Detected invalid quantifier: " + "{" + start + "," + end + "}"); 72 | } 73 | return Integer.toString(random.nextInt(bound + 1) + start, 10); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/SymbolLetter.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | enum SymbolLetter implements Letter { 4 | TILDE("~"), BACKTICK("`"), EXCLAMATION("!"), AT("@"), DOLLER("$"), PERCENT("%"), CAP("^"), 5 | AND("&"), ASTERISK("*"), LPAREN("("), RPAREN(")"), MINUS("-"), UNDERBAR("_"), PLUS("+"), 6 | EQUAL("="), LBRACE("{"), RBRACE("}"), LBRACKET("["), RBRACKET("]"), PIPE("|"), COLON(":"), 7 | SEMICOLON(";"), SINGLEQUOTE("'"), DOT("."), LANGLE("<"), RANGLE(">"), QUESTION("?"), 8 | SLASH("/"), SHARP("#"), COMMA(","), BACKSLASH("\\"), DOUBLEQUOTE("\""); 9 | 10 | private final String letter; 11 | 12 | private SymbolLetter(String letter) { 13 | this.letter = letter; 14 | } 15 | 16 | @Override 17 | public String getLetter() { 18 | return letter; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/UpperCaseLetter.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | enum UpperCaseLetter implements Letter { 4 | A("A"), B("B"), C("C"), D("D"), E("E"), F("F"), G("G"), H("H"), 5 | I("I"), J("J"), K("K"), L("L"), M("M"), N("N"), O("O"), P("P"), 6 | Q("Q"), R("R"), S("S"), T("T"), U("U"), V("V"), W("W"), X("X"), 7 | Y("Y"), Z("Z"); 8 | 9 | private final String letter; 10 | 11 | private UpperCaseLetter(String letter) { 12 | this.letter = letter; 13 | } 14 | 15 | @Override 16 | public String getLetter() { 17 | return letter; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/moznion/random/string/UserDefinedLetterPickerScanner.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | class UserDefinedLetterPickerScanner { 9 | @Getter 10 | public static class ScannedUserDefinedPicker { 11 | private final int cursor; 12 | private final String key; 13 | private final List bounds; 14 | 15 | public ScannedUserDefinedPicker( 16 | final int cursorForScanning, final String key, final List bounds) { 17 | this.cursor = cursorForScanning; 18 | this.key = key; 19 | this.bounds = bounds; 20 | } 21 | } 22 | 23 | public static ScannedUserDefinedPicker scan(final String[] regexCharacters, final int index) { 24 | String key = ""; 25 | String character; 26 | List bounds = new ArrayList<>(); 27 | 28 | int i = index; 29 | while (!(character = regexCharacters[++i]).equals("]")) { 30 | if (character.equals("-") && !bounds.isEmpty()) { 31 | String beginCharacter = bounds.get(bounds.size() - 1); // take from tail 32 | String endCharacter = regexCharacters[++i]; // take from after "-" 33 | 34 | key += beginCharacter + "-" + endCharacter; 35 | 36 | bounds.add(endCharacter); 37 | } else { 38 | if (String.valueOf(character).matches("\\W")) { 39 | throw new RuntimeException("'" + character + "'" + "will be treated literally inside []"); 40 | } 41 | bounds.add(character); 42 | } 43 | } 44 | 45 | return new ScannedUserDefinedPicker(i, key, bounds); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/net/moznion/random/string/RandomStringGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package net.moznion.random.string; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | import java.util.concurrent.ThreadLocalRandom; 9 | import java.util.regex.Pattern; 10 | 11 | public class RandomStringGeneratorTest { 12 | @Test 13 | public void shouldGenerateRandomStringFromPattern() { 14 | RandomStringGenerator generator = new RandomStringGenerator(); 15 | String randomString = generator.generateFromPattern("cCn!.sb"); 16 | Pattern patternToProve = 17 | Pattern.compile("^[a-z][A-Z][0-9][~`!@$%^&*()\\-_+={}\\[\\]|\\\\:;\"'.<>?/#,]" 18 | + "[a-zA-Z0-9~`!@$%^&*()\\-_+={}\\[\\]|\\\\:;\"'.<>?/#,]" 19 | + "[A-Za-z0-9./].$"); 20 | assertTrue(patternToProve.matcher(randomString).find()); 21 | } 22 | 23 | @Test(expected = RuntimeException.class) 24 | public void shouldOccurExceptionWhenItIsWithInvalidPatternChar() { 25 | new RandomStringGenerator().generateFromPattern("cCn?.sb"); 26 | } 27 | 28 | @Test 29 | public void shouldGenerateRandomStringFromRegex() { 30 | RandomStringGenerator generator = new RandomStringGenerator(); 31 | { 32 | String randomString = 33 | generator.generateByRegex("\\w+\\d*\\W\\D{0,3}a\\{0,3}.\\s\\S[0-9][a-zA-Z]X"); 34 | Pattern patternToProve = 35 | Pattern.compile("^[a-zA-Z0-9_]+[0-9]*[~`!@$%^&*()\\-+={}\\[\\]|\\\\:;\"'.<>?/#,]" 36 | + "[a-zA-Z0-9~`!@$%^&*()\\-_+={}\\[\\]|\\\\:;\"'.<>?/#,]{0,3}" 37 | + "a\\{0,3}.[ \t].[0-9][a-zA-Z]X$"); 38 | assertTrue(patternToProve.matcher(randomString).find()); 39 | } 40 | { 41 | // regression test 42 | String randomString = 43 | generator.generateByRegex("\\w+\\d*\\W\\D{0,3}a\\{0,3}.\\s\\S[0-9][a-zA-Z]X"); 44 | Pattern patternToProve = 45 | Pattern.compile("^[a-zA-Z0-9_]+[0-9]*[~`!@$%^&*()\\-+={}\\[\\]|\\\\:;\"'.<>?/#,]" 46 | + "[a-zA-Z0-9~`!@$%^&*()\\-_+={}\\[\\]|\\\\:;\"'.<>?/#,]{0,3}" 47 | + "a\\{0,3}.[ \t].[0-9][a-zA-Z]X$"); 48 | assertTrue(patternToProve.matcher(randomString).find()); 49 | } 50 | } 51 | 52 | @Test 53 | public void shouldIgnoreInvalidRange() { 54 | RandomStringGenerator generator = new RandomStringGenerator(); 55 | { 56 | String randomString = generator.generateByRegex("a{-1,10}b{foo}"); 57 | assertTrue(randomString.equals("a{-1,10}b{foo}")); 58 | } 59 | { 60 | String randomString = generator.generateByRegex("a{"); 61 | assertTrue(randomString.equals("a{")); 62 | } 63 | { 64 | String randomString = generator.generateByRegex("a{,1}"); 65 | assertTrue(randomString.equals("a{,1}")); 66 | } 67 | } 68 | 69 | @Test 70 | public void notSpecifiedDestinationAtBrace() { 71 | { 72 | RandomStringGenerator generator = new RandomStringGenerator(10); 73 | assertEquals("aaaaaaaaaa", generator.generateByRegex("a{10,}")); 74 | } 75 | { 76 | RandomStringGenerator generator = new RandomStringGenerator(10); 77 | assertTrue(generator.generateByRegex("a{0,}").matches("a{0,10}")); 78 | } 79 | } 80 | 81 | @Test(expected = RuntimeException.class) 82 | public void invalidNotSpecifiedDestionationAtBrace() { 83 | RandomStringGenerator generator = new RandomStringGenerator(10); 84 | generator.generateByRegex("a{100,}"); 85 | } 86 | 87 | @Test(expected = RuntimeException.class) 88 | public void shouldOccurExceptionWhenExistsInvalidRange() { 89 | new RandomStringGenerator().generateByRegex("a{5,0}"); 90 | } 91 | 92 | @Test(expected = RuntimeException.class) 93 | public void shouldOccurExceptionWhenExistsInvalidEscapeCharacter() { 94 | new RandomStringGenerator().generateByRegex("foo\\"); 95 | } 96 | 97 | @Test(expected = RuntimeException.class) 98 | public void shouldOccurExceptionWhenUsingInvalidRangeChar() { 99 | new RandomStringGenerator().generateByRegex("[$-%]"); 100 | } 101 | 102 | @Test(expected = RuntimeException.class) 103 | public void shouldOccurExceptionWhenUsingInvalidRangeOrder() { 104 | new RandomStringGenerator().generateByRegex("[z-a]"); 105 | } 106 | 107 | @Test(expected = RuntimeException.class) 108 | public void shouldOccurExceptionWhenRangeNotationIsNotClosed() { 109 | new RandomStringGenerator().generateByRegex("[a-"); 110 | } 111 | 112 | @Test 113 | public void shouldSetAndGetNumOfUpperLimitSuccessfully() { 114 | RandomStringGenerator generator = new RandomStringGenerator(); 115 | generator.setNumOfUpperLimit(0); 116 | assertEquals(0, generator.getNumOfUpperLimit()); 117 | assertEquals("XX", generator.generateByRegex("X.*X")); 118 | } 119 | 120 | @Test 121 | public void shouldSetNumOfUpperLimitSuccessfullyByConstructor() { 122 | RandomStringGenerator generator = new RandomStringGenerator(0); 123 | assertEquals(0, generator.getNumOfUpperLimit()); 124 | assertEquals("XX", generator.generateByRegex("X.*X")); 125 | } 126 | 127 | @Test 128 | public void forThreadLocalRandom() { 129 | RandomStringGenerator generator = new RandomStringGenerator(ThreadLocalRandom.current()); 130 | String randomString = 131 | generator.generateByRegex("\\w+\\d*\\W\\D{0,3}a\\{0,3}.\\s\\S[0-9][a-zA-Z]X"); 132 | Pattern patternToProve = 133 | Pattern.compile("^[a-zA-Z0-9_]+[0-9]*[~`!@$%^&*()\\-+={}\\[\\]|\\\\:;\"'.<>?/#,]" 134 | + "[a-zA-Z0-9~`!@$%^&*()\\-_+={}\\[\\]|\\\\:;\"'.<>?/#,]{0,3}" 135 | + "a\\{0,3}.[ \t].[0-9][a-zA-Z]X$"); 136 | assertTrue(patternToProve.matcher(randomString).find()); 137 | } 138 | } 139 | --------------------------------------------------------------------------------