diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java index 376cbca..1a7570f 100644 --- a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java +++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java @@ -24,10 +24,11 @@ import java.security.SecureRandom; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; +import com.google.common.collect.BoundType; +import com.google.common.collect.Range; + /** * 随机工具类 - *

- * 建议调用方自行维护 Random 对象 * * @author ZhouXY108 */ @@ -46,18 +47,41 @@ public final class RandomTools { DEFAULT_SECURE_RANDOM = secureRandom; } + /** + * 大写字母 + */ public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + /** + * 小写字母 + */ public static final String LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"; + /** + * 数字 + */ public static final String NUMBERS = "0123456789"; + /** + * 默认的 {@code SecureRandom} + * + * @return 默认的 {@code SecureRandom} + */ public static SecureRandom defaultSecureRandom() { return DEFAULT_SECURE_RANDOM; } + /** + * 当前线程的 {@code ThreadLocalRandom} + * + * @return 当前线程的 {@code ThreadLocalRandom} + */ public static ThreadLocalRandom currentThreadLocalRandom() { return ThreadLocalRandom.current(); } + // ================================ + // #region - randomStr + // ================================ + /** * 使用传入的随机数生成器,生成指定长度的字符串 * @@ -75,12 +99,26 @@ public final class RandomTools { return randomStrInternal(random, sourceCharacters, length); } + /** + * 使用当前线程的 {@code ThreadLocalRandom},生成指定长度的字符串 + * + * @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空 + * @param length 字符串长度 + * @return 随机字符串 + */ public static String randomStr(char[] sourceCharacters, int length) { checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length); } + /** + * 使用默认的 {@code SecureRandom},生成指定长度的字符串 + * + * @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空 + * @param length 字符串长度 + * @return 随机字符串 + */ public static String secureRandomStr(char[] sourceCharacters, int length) { checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); checkArgument(length >= 0, "The length should be greater than or equal to zero."); @@ -104,18 +142,131 @@ public final class RandomTools { return randomStrInternal(random, sourceCharacters, length); } + /** + * 使用当前线程的 {@code ThreadLocalRandom},生成指定长度的字符串 + * + * @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空 + * @param length 字符串长度 + * @return 随机字符串 + */ public static String randomStr(String sourceCharacters, int length) { checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length); } + /** + * 使用默认的 {@code SecureRandom},生成指定长度的字符串 + * + * @param sourceCharacters 字符池。字符串的字符将在数组中选,不为空 + * @param length 字符串长度 + * @return 随机字符串 + */ public static String secureRandomStr(String sourceCharacters, int length) { checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(DEFAULT_SECURE_RANDOM, sourceCharacters, length); } + // ================================ + // #endregion - randomStr + // ================================ + + // ================================ + // #region - randomInt + // ================================ + + /** + * 使用传入的随机数生成器,生成随机整数 + * + * @param startInclusive 最小值(包含) + * @param endExclusive 最大值(不包含) + * @return 在区间 {@code [min, max)} 内的随机整数 + * + * @since 1.1.0 + */ + public static int randomInt(Random random, int startInclusive, int endExclusive) { + checkArgumentNotNull(random, "Random cannot be null."); + checkArgument(startInclusive < endExclusive, "Start value must be less than end value."); + return randomIntInternal(random, startInclusive, endExclusive); + } + + /** + * 使用当前线程的 {@code ThreadLocalRandom},生成随机整数 + * + * @param startInclusive 最小值(包含) + * @param endExclusive 最大值(不包含) + * @return 在区间 {@code [min, max)} 内的随机整数 + * + * @since 1.1.0 + */ + public static int randomInt(int startInclusive, int endExclusive) { + checkArgument(startInclusive < endExclusive, "Start value must be less than end value."); + return randomIntInternal(ThreadLocalRandom.current(), startInclusive, endExclusive); + } + + /** + * 使用默认的 {@code SecureRandom},生成随机整数 + * + * @param startInclusive 最小值(包含) + * @param endExclusive 最大值(不包含) + * @return 在区间 {@code [min, max)} 内的随机整数 + * + * @since 1.1.0 + */ + public static int secureRandomInt(int startInclusive, int endExclusive) { + checkArgument(startInclusive < endExclusive, "Start value must be less than end value."); + return randomIntInternal(DEFAULT_SECURE_RANDOM, startInclusive, endExclusive); + } + + /** + * 使用传入的随机数生成器,生成随机整数 + * + * @param range 整数区间 + * @return 在指定区间内的随机整数 + * + * @since 1.1.0 + */ + public static int randomInt(Random random, Range range) { + checkArgumentNotNull(random, "Random cannot be null."); + checkArgumentNotNull(range, "Range cannot be null."); + return randomIntInternal(random, range); + } + + /** + * 使用当前线程的 {@code ThreadLocalRandom},生成随机整数 + * + * @param range 整数区间 + * @return 在指定区间内的随机整数 + * + * @since 1.1.0 + */ + public static int randomInt(Range range) { + checkArgumentNotNull(range, "Range cannot be null."); + return randomIntInternal(ThreadLocalRandom.current(), range); + } + + /** + * 使用默认的 {@code SecureRandom},生成随机整数 + * + * @param range 整数区间 + * @return 在指定区间内的随机整数 + * + * @since 1.1.0 + */ + public static int secureRandomInt(Range range) { + checkArgumentNotNull(range, "Range cannot be null."); + return randomIntInternal(DEFAULT_SECURE_RANDOM, range); + } + + // ================================ + // #endregion - randomInt + // ================================ + + // ================================ + // #region - private methods + // ================================ + /** * 使用传入的随机数生成器,生成指定长度的字符串 * @@ -158,6 +309,35 @@ public final class RandomTools { return String.valueOf(result); } + /** + * 使用传入的随机数生成器,生成随机整数 + * + * @param startInclusive 最小值(包含) + * @param endExclusive 最大值(不包含) + * @return 在区间 {@code [min, max)} 内的随机整数 + */ + private static int randomIntInternal(Random random, int startInclusive, int endExclusive) { + return random.nextInt(endExclusive - startInclusive) + startInclusive; + } + + /** + * 使用传入的随机数生成器,生成随机整数 + * + * @param range 整数区间 + * @return 在指定区间内的随机整数 + */ + private static int randomIntInternal(Random random, Range range) { + Integer lowerEndpoint = range.lowerEndpoint(); + Integer upperEndpoint = range.upperEndpoint(); + int min = range.lowerBoundType() == BoundType.CLOSED ? lowerEndpoint : lowerEndpoint + 1; + int max = range.upperBoundType() == BoundType.OPEN ? upperEndpoint : upperEndpoint + 1; + return random.nextInt(max - min) + min; + } + + // ================================ + // #endregion - private methods + // ================================ + private RandomTools() { throw new IllegalStateException("Utility class"); } diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/RandomToolsTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/RandomToolsTests.java index 7a05601..c75756b 100644 --- a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/RandomToolsTests.java +++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/util/RandomToolsTests.java @@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Constructor; import java.security.SecureRandom; @@ -30,6 +31,8 @@ import java.util.Random; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.google.common.collect.Range; + @SuppressWarnings("null") public class RandomToolsTests { @@ -56,13 +59,16 @@ public class RandomToolsTests { @Test public void randomStr_NullSourceCharacters_ThrowsException() { - assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, (char[]) null, 5)); - assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, (String) null, 5)); + assertThrows(IllegalArgumentException.class, + () -> RandomTools.randomStr(random, (char[]) null, 5)); + assertThrows(IllegalArgumentException.class, + () -> RandomTools.randomStr(random, (String) null, 5)); } @Test public void randomStr_NegativeLength_ThrowsException() { - assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, sourceCharactersArray, -1)); + assertThrows(IllegalArgumentException.class, + () -> RandomTools.randomStr(random, sourceCharactersArray, -1)); } @Test @@ -107,6 +113,50 @@ public class RandomToolsTests { assertEquals(5, result.length()); } + @Test + public void randomInt_WithMinAndMax() { + for (int i = 0; i < 1000; i++) { + int r = RandomTools.randomInt(random, -2, 3); + assertTrue(r >= -2 && r < 3); + } + } + + @Test + public void randomInt_WithClosedOpenRange() { + Range co = Range.closedOpen(-2, 3); + for (int i = 0; i < 1000; i++) { + int rco = RandomTools.randomInt(random, co); + assertTrue(rco >= -2 && rco < 3); + } + } + + @Test + public void randomInt_WithClosedRange() { + Range cc = Range.closed(-2, 3); + for (int i = 0; i < 1000; i++) { + int rcc = RandomTools.randomInt(random, cc); + assertTrue(rcc >= -2 && rcc <= 3); + } + } + + @Test + public void randomInt_WithOpenClosedRange() { + Range oc = Range.openClosed(-2, 3); + for (int i = 0; i < 1000; i++) { + int roc = RandomTools.randomInt(random, oc); + assertTrue(roc > -2 && roc <= 3); + } + } + + @Test + public void randomInt_WithOpenRange() { + Range oo = Range.open(-2, 3); + for (int i = 0; i < 1000; i++) { + int roo = RandomTools.randomInt(random, oo); + assertTrue(roo > -2 && roo < 3); + } + } + @Test void test_constructor_isNotAccessible_ThrowsIllegalStateException() { Constructor[] constructors = RandomTools.class.getDeclaredConstructors();