diff --git a/pom.xml b/pom.xml index 5ff2fe4c6b534c809760263d4b6a0c0b3e9bca3d..1a11ca7ebc3afd74923972b866221b264f71dd3b 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,13 @@ test + + dk.brics.automaton + automaton + 1.11-8 + + + diff --git a/src/main/java/com/forte/util/utils/MockUtil.java b/src/main/java/com/forte/util/utils/MockUtil.java index c21e6cbbac3e48d5eedf361adbac7f0e09e9a8c8..519c6464c412b58977efaf05aaf0689f821ff1fd 100644 --- a/src/main/java/com/forte/util/utils/MockUtil.java +++ b/src/main/java/com/forte/util/utils/MockUtil.java @@ -437,6 +437,92 @@ public class MockUtil { return FieldUtils.headUpper(title); } + /** + * 获取指定长度的英文字符串,纯小写 + * + * @param min 最小长度 + * @param max 最大长度 + */ + public static String string(Integer min, Integer max) { + Integer num = RandomUtil.getNumber$right(min, max); + String title = RandomUtil.getRandomString(num); + //全部小写 + return title.toLowerCase(); + } + + /** + * 获取指定长度的英文字符串,纯小写 + * + * @param num + */ + public static String string(Integer num) { + return string(num, num); + } + + /** + * 获取5-10长度的英文字符串,纯小写 + */ + public static String string() { + return string(5, 10); + } + + /** + * 获取指定长度的英文字符串,纯大写 + * + * @param min 最小长度 + * @param max 最大长度 + */ + public static String STRING(Integer min, Integer max) { + Integer num = RandomUtil.getNumber$right(min, max); + String title = RandomUtil.getRandomString(num); + //全部大写 + return title.toUpperCase(); + } + + /** + * 获取指定长度的英文字符串,纯大写 + * + * @param num + */ + public static String STRING(Integer num) { + return STRING(num, num); + } + /** + * 获取5-10长度的英文字符串,纯大写 + */ + public static String STRING() { + return STRING(5, 10); + } + + + /** + * 根据正则规则生成随机字符串 + * 如:[a-z][A-Z][0-9]{32} 生成32位包含大写小写数字的字符串 + * @regex('[a-z][A-Z][0-9]{32}') + * + * @param regex + */ + public static String regex(String regex) { + Xeger xeger = Xeger.getInstance(regex); + return xeger.generate(); + } + + /** + * 获取指定数量num个随机字符串 + * + * @param regex + * @param num 获取数量 + * @return + */ + public static String[] regexs(String regex, Integer num) { + String[] regexs = new String[num]; + for (int i = 0; i < num; i++) { + regexs[i] = regex(regex); + } + return regexs; + } + + /** * 获取一个UUID */ diff --git a/src/main/java/com/forte/util/utils/Xeger.java b/src/main/java/com/forte/util/utils/Xeger.java new file mode 100644 index 0000000000000000000000000000000000000000..05964cfd7c40abc82c7d08c6ee03ea501c8d41df --- /dev/null +++ b/src/main/java/com/forte/util/utils/Xeger.java @@ -0,0 +1,226 @@ +/** + * Copyright 2009 Wilfred Springer + * Copyright 2012 Jason Pell + * Copyright 2013 Antonio García-Domínguez + *

+ * Licensed 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 com.forte.util.utils; + +import dk.brics.automaton.Automaton; +import dk.brics.automaton.RegExp; +import dk.brics.automaton.State; +import dk.brics.automaton.Transition; + +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * An object that will generate text from a regular expression. In a way, it's the opposite of a regular expression + * matcher: an instance of this class will produce text that is guaranteed to match the regular expression passed in. + */ +public class Xeger { + + public static class FailedRandomWalkException extends Exception { + public FailedRandomWalkException(String message) { + super(message); + } + } + + private final Automaton automaton; + private Random random; + + private volatile static Xeger instance; + + /** + * Constructs a new instance, accepting the regular expression and the randomizer. + * + * @param regex The regular expression. (Not null.) + * @param random The object that will randomize the way the String is generated. (Not null.) + * @throws IllegalArgumentException If the regular expression is invalid. + */ + private Xeger(String regex, Random random) { + assert regex != null; + assert random != null; + this.automaton = new RegExp(regex).toAutomaton(); + this.random = random; + } + + /** + * As {@link Xeger (String, Random)}, creating a {@link Random} instance + * implicityly. + */ + private Xeger(String regex) { + this(regex, new Random()); + } + + public static Xeger getInstance(String regex) { + if (instance == null) { + synchronized (Xeger.class) { + if (instance == null) { + instance = new Xeger(regex); + } + } + } + return instance; + } + + + /** + * Generates a random String that is guaranteed to match the regular expression passed to the constructor. + */ + public String generate() { + StringBuilder builder = new StringBuilder(); + generate(builder, automaton.getInitialState()); + return builder.toString(); + } + + /** + * Attempts to generate a random String using a random walk of length between minLength and + * maxLength steps. + *

+ * A target length will be randomly generated within this range, and a random walk of at least that length + * will be attempted. The walk will initially avoid states with no outgoing transitions until the target + * length is reached: from then onwards, it will consider all transitions equally, and stop as soon as an + * accept state has been reached or the maximum walk length has been exceeded. If the minimum length is not + * reached or the maximum walk length has been exceeded, a {@link FailedRandomWalkException} exception will + * be thrown. Callers could catch this exception to try again if desired. + * + * @param minLength Minimum length for the range. + * @param maxLength Maximum length for the range. + * @throws FailedRandomWalkException The minimum random walk length was not reached, or the maximum random walk + * length was exceeded. + */ + public String generate(int minLength, int maxLength) throws FailedRandomWalkException { + final StringBuilder builder = new StringBuilder(); + int walkLength = 0; + State state = automaton.getInitialState(); + + // First get to the uniformly distributed target length + final int targetLength = Xeger.getRandomInt(minLength, maxLength, random); + while (walkLength < targetLength) { + List transitions = state.getSortedTransitions(false); + if (transitions.size() == 0) { + if (walkLength >= minLength) { + assert state.isAccept(); + return builder.toString(); + } else { + throw new FailedRandomWalkException(String.format( + "Reached accept state before minimum length (current = %d < min = %d)", + walkLength, minLength)); + } + } + + // Try to prefer non-final transitions if possible at this first stage + List nonFinalTransitions = transitions.stream() + .filter(t -> !t.getDest().getTransitions().isEmpty()).collect(Collectors.toList()); + if (!nonFinalTransitions.isEmpty()) { + transitions = nonFinalTransitions; + } + + final int option = Xeger.getRandomInt(0, transitions.size() - 1, random); + final Transition transition = transitions.get(option); + appendChoice(builder, transition); + state = transition.getDest(); + ++walkLength; + } + + // Now, get to an accept state + while (!state.isAccept() && walkLength < maxLength) { + List transitions = state.getSortedTransitions(false); + if (transitions.size() == 0) { + assert state.isAccept(); + return builder.toString(); + } + + final int option = Xeger.getRandomInt(0, transitions.size() - 1, random); + final Transition transition = transitions.get(option); + appendChoice(builder, transition); + state = transition.getDest(); + ++walkLength; + } + + if (state.isAccept()) { + return builder.toString(); + } else { + throw new FailedRandomWalkException(String.format( + "Exceeded maximum walk length (%d) before reaching an accept state: " + + "target length was %d (min length = %d)", + maxLength, targetLength, minLength)); + } + } + + private Optional appendRandomChoice(StringBuilder builder, State state, int minLength, int walkLength) throws FailedRandomWalkException { + List transitions = state.getSortedTransitions(false); + if (transitions.size() == 0) { + if (walkLength >= minLength) { + return Optional.empty(); + } else { + throw new FailedRandomWalkException(String.format( + "Reached accept state before minimum length (current = %d < min = %d)", + walkLength, minLength)); + } + } + + final int option = Xeger.getRandomInt(0, transitions.size() - 1, random); + final Transition transition = transitions.get(option); + appendChoice(builder, transition); + return Optional.of(transition); + } + + private void generate(StringBuilder builder, State state) { + List transitions = state.getSortedTransitions(false); + if (transitions.size() == 0) { + assert state.isAccept(); + return; + } + int nroptions = state.isAccept() ? transitions.size() : transitions.size() - 1; + int option = Xeger.getRandomInt(0, nroptions, random); + if (state.isAccept() && option == 0) { // 0 is considered stop + return; + } + // Moving on to next transition + Transition transition = transitions.get(option - (state.isAccept() ? 1 : 0)); + appendChoice(builder, transition); + generate(builder, transition.getDest()); + } + + private void appendChoice(StringBuilder builder, Transition transition) { + char c = (char) Xeger.getRandomInt(transition.getMin(), transition.getMax(), random); + builder.append(c); + } + + public Random getRandom() { + return random; + } + + public void setRandom(Random random) { + this.random = random; + } + + /** + * Generates a random number within the given bounds. + * + * @param min The minimum number (inclusive). + * @param max The maximum number (inclusive). + * @param random The object used as the randomizer. + * @return A random number in the given range. + */ + static int getRandomInt(int min, int max, Random random) { + // Use random.nextInt as it guarantees a uniform distribution + int maxForRandom = max - min + 1; + return random.nextInt(maxForRandom) + min; + } +} \ No newline at end of file