From af66cd238095a9e3a77179372d1a01882956ee80 Mon Sep 17 00:00:00 2001 From: ZhouXY108 Date: Thu, 1 May 2025 02:08:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20`RegexTools`=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=87=8D=E8=BD=BD=E6=96=B9=E6=B3=95=EF=BC=8C=E5=BD=93=E5=B0=86?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E8=A7=86=E4=B8=BA=E6=AD=A3=E5=88=99?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=85=A5=E5=8F=82=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E4=BC=A0=E5=AF=B9=E5=BA=94=E7=9A=84=20`flags?= =?UTF-8?q?`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plusone/commons/util/RegexTools.java | 199 +++++++++++++++--- .../plusone/commons/util/RegexToolsTests.java | 75 +++++-- 2 files changed, 225 insertions(+), 49 deletions(-) diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java index 46250dc..20950cd 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java @@ -18,6 +18,8 @@ package xyz.zhouxy.plusone.commons.util; import java.util.Arrays; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -35,7 +37,8 @@ public final class RegexTools { private static final int DEFAULT_CACHE_INITIAL_CAPACITY = 64; private static final int MAX_CACHE_SIZE = 256; - private static final Map PATTERN_CACHE + private static final int DEFAULT_FLAG = 0; + private static final Map PATTERN_CACHE = new ConcurrentHashMap<>(DEFAULT_CACHE_INITIAL_CAPACITY); /** @@ -46,8 +49,20 @@ public final class RegexTools { * @return {@link Pattern} 实例 */ public static Pattern getPattern(final String pattern, final boolean cachePattern) { + return getPattern(pattern, DEFAULT_FLAG, cachePattern); + } + + /** + * 获取 {@link Pattern} 实例。 + * + * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 + * @param cachePattern 是否缓存 {@link Pattern} 实例 + * @return {@link Pattern} 实例 + */ + public static Pattern getPattern(final String pattern, final int flags, final boolean cachePattern) { AssertTools.checkNotNull(pattern); - return cachePattern ? cacheAndGetPatternInternal(pattern) : getPatternInternal(pattern); + return cachePattern ? cacheAndGetPatternInternal(pattern, flags) : getPatternInternal(pattern, flags); } /** @@ -57,10 +72,30 @@ public final class RegexTools { * @return {@link Pattern} 实例 */ public static Pattern getPattern(final String pattern) { - AssertTools.checkNotNull(pattern); - return getPatternInternal(pattern); + return getPattern(pattern, DEFAULT_FLAG); } + /** + * 获取 {@link Pattern} 实例,不缓存。 + * + * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 + * @return {@link Pattern} 实例 + */ + @Nonnull + public static Pattern getPattern(final String pattern, final int flags) { + AssertTools.checkNotNull(pattern); + return getPatternInternal(pattern, flags); + } + + // ================================ + // #endregion - getPattern + // ================================ + + // ================================ + // #region - matches + // ================================ + /** * 判断 {@code input} 是否匹配 {@code pattern}。 * @@ -107,11 +142,21 @@ public final class RegexTools { */ public static boolean matches(@Nullable final CharSequence input, final String pattern, final boolean cachePattern) { - AssertTools.checkNotNull(pattern); - Pattern p = cachePattern - ? cacheAndGetPatternInternal(pattern) - : getPatternInternal(pattern); - return matchesInternal(input, p); + return matches(input, pattern, DEFAULT_FLAG, cachePattern); + } + + /** + * 判断 {@code input} 是否匹配 {@code pattern}。 + * + * @param input 输入 + * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 + * @param cachePattern 是否缓存 {@link Pattern} 实例 + * @return 判断结果 + */ + public static boolean matches(@Nullable final CharSequence input, final String pattern, final int flags, + final boolean cachePattern) { + return matchesInternal(input, getPattern(pattern, flags, cachePattern)); } /** @@ -122,10 +167,30 @@ public final class RegexTools { * @return 判断结果 */ public static boolean matches(@Nullable final CharSequence input, final String pattern) { - AssertTools.checkNotNull(pattern); - return matchesInternal(input, getPatternInternal(pattern)); + return matches(input, pattern, DEFAULT_FLAG); } + /** + * 判断 {@code input} 是否匹配 {@code pattern}。不缓存 {@link Pattern} 实例。 + * + * @param input 输入 + * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 + * @return 判断结果 + */ + public static boolean matches(@Nullable final CharSequence input, + final String pattern, final int flags) { + return matchesInternal(input, getPattern(pattern, flags)); + } + + // ================================ + // #endregion - matches + // ================================ + + // ================================ + // #region - getMatcher + // ================================ + /** * 生成 Matcher。 * @@ -148,12 +213,21 @@ public final class RegexTools { * @return 结果 */ public static Matcher getMatcher(final CharSequence input, final String pattern, boolean cachePattern) { - AssertTools.checkNotNull(input); - AssertTools.checkNotNull(pattern); - final Pattern p = cachePattern - ? cacheAndGetPatternInternal(pattern) - : getPatternInternal(pattern); - return p.matcher(input); + return getMatcher(input, pattern, DEFAULT_FLAG, cachePattern); + } + + /** + * 生成 Matcher。 + * + * @param input 输入 + * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 + * @param cachePattern 是否缓存 {@link Pattern} 实例 + * @return 结果 + */ + public static Matcher getMatcher(final CharSequence input, + final String pattern, final int flags, boolean cachePattern) { + return getMatcher(input, getPattern(pattern, flags, cachePattern)); } /** @@ -164,44 +238,60 @@ public final class RegexTools { * @return 结果 */ public static Matcher getMatcher(final CharSequence input, final String pattern) { - AssertTools.checkNotNull(input); - AssertTools.checkNotNull(pattern); - return getPatternInternal(pattern).matcher(input); + return getMatcher(input, pattern, DEFAULT_FLAG); } - // ========== internal methods ========== + /** + * 生成 Matcher。不缓存 {@link Pattern} 实例。 + * + * @param input 输入 + * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 + * @return 结果 + */ + public static Matcher getMatcher(final CharSequence input, final String pattern, final int flags) { + AssertTools.checkNotNull(input); + AssertTools.checkNotNull(pattern); + return getPatternInternal(pattern, flags).matcher(input); + } + + // ================================ + // #endregion - getMatcher + // ================================ + + // ================================ + // #region - internal methods + // ================================ /** * 获取 {@link Pattern} 实例。 * * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 * @return {@link Pattern} 实例 */ @Nonnull - private static Pattern cacheAndGetPatternInternal(final String pattern) { + private static Pattern cacheAndGetPatternInternal(final String pattern, final int flags) { + final RegexAndFlags regexAndFlags = new RegexAndFlags(pattern, flags); if (PATTERN_CACHE.size() < MAX_CACHE_SIZE) { - return PATTERN_CACHE.computeIfAbsent(pattern, Pattern::compile); + return PATTERN_CACHE.computeIfAbsent(regexAndFlags, RegexAndFlags::compilePattern); } - Pattern result = PATTERN_CACHE.get(pattern); - if (result != null) { - return result; - } - return Pattern.compile(pattern); + return Optional.ofNullable(PATTERN_CACHE.get(regexAndFlags)) + .orElseGet(regexAndFlags::compilePattern); } /** * 获取 {@link Pattern} 实例,不缓存。 * * @param pattern 正则表达式 + * @param flags 正则表达式匹配标识 * @return {@link Pattern} 实例 */ @Nonnull - private static Pattern getPatternInternal(final String pattern) { - Pattern result = PATTERN_CACHE.get(pattern); - if (result == null) { - result = Pattern.compile(pattern); - } - return result; + private static Pattern getPatternInternal(final String pattern, final int flags) { + final RegexAndFlags regexAndFlags = new RegexAndFlags(pattern, flags); + return Optional.ofNullable(PATTERN_CACHE.get(regexAndFlags)) + .orElseGet(regexAndFlags::compilePattern); } /** @@ -241,8 +331,49 @@ public final class RegexTools { .allMatch(pattern -> pattern.matcher(input).matches()); } + // ================================ + // #endregion - internal methods + // ================================ + private RegexTools() { // 不允许实例化 throw new IllegalStateException("Utility class"); } + + // ================================ + // #region - RegexAndFlags + // ================================ + + private static final class RegexAndFlags { + private final String regex; + private final int flags; + + private RegexAndFlags(String regex, int flags) { + this.regex = regex; + this.flags = flags; + } + + private final Pattern compilePattern() { + return Pattern.compile(regex, flags); + } + + @Override + public int hashCode() { + return Objects.hash(regex, flags); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) + return true; + if (!(obj instanceof RegexAndFlags)) + return false; + RegexAndFlags other = (RegexAndFlags) obj; + return Objects.equals(regex, other.regex) && flags == other.flags; + } + } + + // ================================ + // #endregion - RegexAndFlags + // ================================ } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/RegexToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/RegexToolsTests.java index 70fd6c2..db969cb 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/RegexToolsTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/RegexToolsTests.java @@ -33,19 +33,42 @@ public class RegexToolsTests { @Test - void getPattern_CachePatternTrue_ReturnsCachedPattern() { + void getPattern_SameRegexAndFlag_CachePatternIsTrue_ReturnsCachedPattern() { String pattern = "abc"; Pattern cachedPattern = RegexTools.getPattern(pattern, true); - Pattern patternFromCache = RegexTools.getPattern(pattern, true); + Pattern patternFromCache = RegexTools.getPattern(pattern); assertSame(cachedPattern, patternFromCache, "Pattern should be cached"); + + Pattern cachedPatternWithFlag = RegexTools.getPattern(pattern, Pattern.CASE_INSENSITIVE, true); + Pattern patternFromCacheWithFlag = RegexTools.getPattern(pattern, Pattern.CASE_INSENSITIVE); + assertSame(cachedPatternWithFlag, patternFromCacheWithFlag, "Pattern should be cached"); } @Test - void getPattern_CachePatternFalse_ReturnsNewPattern() { - String pattern = "getPattern_CachePatternFalse_ReturnsNewPattern"; + void getPattern_SameRegexAndFlag_CachePatternFalse_ReturnsNewPattern() { + String pattern = "getPattern_SameRegexAndFlag_CachePatternFalse_ReturnsNewPattern"; Pattern pattern1 = RegexTools.getPattern(pattern, false); Pattern pattern2 = RegexTools.getPattern(pattern, false); + Pattern pattern3 = RegexTools.getPattern(pattern); assertNotSame(pattern1, pattern2, "Pattern should not be cached"); + assertNotSame(pattern1, pattern3, "Pattern should not be cached"); + assertNotSame(pattern2, pattern3, "Pattern should not be cached"); + + Pattern pattern1WithFlag = RegexTools.getPattern(pattern, Pattern.CASE_INSENSITIVE, false); + Pattern pattern2WithFlag = RegexTools.getPattern(pattern, Pattern.CASE_INSENSITIVE, false); + Pattern pattern3WithFlag = RegexTools.getPattern(pattern, Pattern.CASE_INSENSITIVE); + assertNotSame(pattern1WithFlag, pattern2WithFlag, "Pattern should not be cached"); + assertNotSame(pattern1WithFlag, pattern3WithFlag, "Pattern should not be cached"); + assertNotSame(pattern2WithFlag, pattern3WithFlag, "Pattern should not be cached"); + } + + @Test + void getPattern_SameRegexAndDifferentFlag_ReturnsNewPattern() { + String pattern = "getPattern_SameRegexAndDifferentFlag_CachePatternFalse_ReturnsNewPattern"; + + Pattern pattern1WithFlag = RegexTools.getPattern(pattern, Pattern.CASE_INSENSITIVE, true); + Pattern pattern2WithFlag = RegexTools.getPattern(pattern, 0, true); + assertNotSame(pattern1WithFlag, pattern2WithFlag, "Patterns should not be the same"); } @Test @@ -53,27 +76,38 @@ class RegexToolsTests { assertThrows(NullPointerException.class, () -> { RegexTools.getPattern(null, true); }); + assertThrows(NullPointerException.class, () -> { + RegexTools.getPattern(null, Pattern.CASE_INSENSITIVE, true); + }); } @Test void matches_InputMatchesPattern_ReturnsTrue() { String pattern = "abc"; + assertTrue(RegexTools.matches("abc", pattern), "Input should match pattern"); + assertFalse(RegexTools.matches("ABC", pattern), "Input should match pattern"); + assertTrue(RegexTools.matches("ABC", pattern, Pattern.CASE_INSENSITIVE), "Input should match pattern"); + Pattern compiledPattern = Pattern.compile(pattern); assertTrue(RegexTools.matches("abc", compiledPattern), "Input should match pattern"); + assertFalse(RegexTools.matches("ABC", compiledPattern), "Input should match pattern"); + + assertTrue(RegexTools.matches("abc", pattern, true), "Input should match pattern"); + Pattern cachedPattern1 = RegexTools.getPattern(pattern); + Pattern cachedPattern2 = RegexTools.getPattern(pattern); + assertSame(cachedPattern1, cachedPattern2, "Cached pattern should be the same"); } @Test void matches_InputDoesNotMatchPattern_ReturnsFalse() { String pattern = "abc"; - Pattern compiledPattern = Pattern.compile(pattern); - assertFalse(RegexTools.matches("abcd", compiledPattern), "Input should not match pattern"); + assertFalse(RegexTools.matches("abcd", pattern), "Input should not match pattern"); } @Test void matches_NullInput_ReturnsFalse() { String pattern = "abc"; - Pattern compiledPattern = Pattern.compile(pattern); - assertFalse(RegexTools.matches(null, compiledPattern), "Null input should return false"); + assertFalse(RegexTools.matches(null, pattern), "Null input should return false"); } @Test @@ -94,6 +128,7 @@ class RegexToolsTests { compiledPatterns[i] = Pattern.compile(patterns[i]); } assertFalse(RegexTools.matchesOne("xyz", compiledPatterns), "Input should not match any pattern"); + assertFalse(RegexTools.matchesOne(null, compiledPatterns), "Input should not match any pattern"); } @Test @@ -114,30 +149,40 @@ class RegexToolsTests { compiledPatterns[i] = Pattern.compile(patterns[i]); } assertFalse(RegexTools.matchesAll("abc", compiledPatterns), "Input should not match all patterns"); + assertFalse(RegexTools.matchesAll(null, compiledPatterns), "Input should not match all patterns"); } @Test void getMatcher_ValidInputAndPattern_ReturnsMatcher() { String pattern = "abc"; - Pattern compiledPattern = Pattern.compile(pattern); - Matcher matcher = RegexTools.getMatcher("abc", compiledPattern); - assertNotNull(matcher, "Matcher should not be null"); + Matcher matcher1 = RegexTools.getMatcher("abc", pattern); + assertNotNull(matcher1, "Matcher should not be null"); + assertTrue(matcher1.matches(), "Should be matches"); + + Matcher matcher2 = RegexTools.getMatcher("ABC", pattern, true); + assertNotNull(matcher2, "Matcher should not be null"); + assertFalse(matcher2.matches(), "Should be matches"); + + Pattern cachedPattern = RegexTools.getPattern(pattern); + Pattern patternFromCache = RegexTools.getPattern(pattern); + assertSame(cachedPattern, patternFromCache); } @Test void getMatcher_NullInput_ThrowsException() { String pattern = "abc"; - Pattern compiledPattern = Pattern.compile(pattern); assertThrows(NullPointerException.class, () -> { - RegexTools.getMatcher(null, compiledPattern); + RegexTools.getMatcher(null, pattern); }); } @Test void getMatcher_NullPattern_ThrowsException() { - final Pattern pattern = null; assertThrows(NullPointerException.class, () -> { - RegexTools.getMatcher("abc", pattern); + RegexTools.getMatcher("abc", (String) null); + }); + assertThrows(NullPointerException.class, () -> { + RegexTools.getMatcher("abc", (Pattern) null); }); }