feat: RegexTools 新增重载方法,当将字符串视为正则表达式入参时,允许传对应的 flags

This commit is contained in:
zhouxy108 2025-05-01 02:08:23 +08:00
parent 3b519105bf
commit af66cd2380
2 changed files with 225 additions and 49 deletions

View File

@ -18,6 +18,8 @@ package xyz.zhouxy.plusone.commons.util;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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 DEFAULT_CACHE_INITIAL_CAPACITY = 64;
private static final int MAX_CACHE_SIZE = 256; private static final int MAX_CACHE_SIZE = 256;
private static final Map<String, Pattern> PATTERN_CACHE private static final int DEFAULT_FLAG = 0;
private static final Map<RegexAndFlags, Pattern> PATTERN_CACHE
= new ConcurrentHashMap<>(DEFAULT_CACHE_INITIAL_CAPACITY); = new ConcurrentHashMap<>(DEFAULT_CACHE_INITIAL_CAPACITY);
/** /**
@ -46,8 +49,20 @@ public final class RegexTools {
* @return {@link Pattern} 实例 * @return {@link Pattern} 实例
*/ */
public static Pattern getPattern(final String pattern, final boolean cachePattern) { 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); 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} 实例 * @return {@link Pattern} 实例
*/ */
public static Pattern getPattern(final String pattern) { public static Pattern getPattern(final String pattern) {
AssertTools.checkNotNull(pattern); return getPattern(pattern, DEFAULT_FLAG);
return getPatternInternal(pattern);
} }
/**
* 获取 {@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} * 判断 {@code input} 是否匹配 {@code pattern}
* *
@ -107,11 +142,21 @@ public final class RegexTools {
*/ */
public static boolean matches(@Nullable final CharSequence input, final String pattern, public static boolean matches(@Nullable final CharSequence input, final String pattern,
final boolean cachePattern) { final boolean cachePattern) {
AssertTools.checkNotNull(pattern); return matches(input, pattern, DEFAULT_FLAG, cachePattern);
Pattern p = cachePattern }
? cacheAndGetPatternInternal(pattern)
: getPatternInternal(pattern); /**
return matchesInternal(input, p); * 判断 {@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 判断结果 * @return 判断结果
*/ */
public static boolean matches(@Nullable final CharSequence input, final String pattern) { public static boolean matches(@Nullable final CharSequence input, final String pattern) {
AssertTools.checkNotNull(pattern); return matches(input, pattern, DEFAULT_FLAG);
return matchesInternal(input, getPatternInternal(pattern));
} }
/**
* 判断 {@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 * 生成 Matcher
* *
@ -148,12 +213,21 @@ public final class RegexTools {
* @return 结果 * @return 结果
*/ */
public static Matcher getMatcher(final CharSequence input, final String pattern, boolean cachePattern) { public static Matcher getMatcher(final CharSequence input, final String pattern, boolean cachePattern) {
AssertTools.checkNotNull(input); return getMatcher(input, pattern, DEFAULT_FLAG, cachePattern);
AssertTools.checkNotNull(pattern); }
final Pattern p = cachePattern
? cacheAndGetPatternInternal(pattern) /**
: getPatternInternal(pattern); * 生成 Matcher
return p.matcher(input); *
* @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 结果 * @return 结果
*/ */
public static Matcher getMatcher(final CharSequence input, final String pattern) { public static Matcher getMatcher(final CharSequence input, final String pattern) {
AssertTools.checkNotNull(input); return getMatcher(input, pattern, DEFAULT_FLAG);
AssertTools.checkNotNull(pattern);
return getPatternInternal(pattern).matcher(input);
} }
// ========== 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} 实例 * 获取 {@link Pattern} 实例
* *
* @param pattern 正则表达式 * @param pattern 正则表达式
* @param flags 正则表达式匹配标识
* @return {@link Pattern} 实例 * @return {@link Pattern} 实例
*/ */
@Nonnull @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) { 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); return Optional.ofNullable(PATTERN_CACHE.get(regexAndFlags))
if (result != null) { .orElseGet(regexAndFlags::compilePattern);
return result;
}
return Pattern.compile(pattern);
} }
/** /**
* 获取 {@link Pattern} 实例不缓存 * 获取 {@link Pattern} 实例不缓存
* *
* @param pattern 正则表达式 * @param pattern 正则表达式
* @param flags 正则表达式匹配标识
* @return {@link Pattern} 实例 * @return {@link Pattern} 实例
*/ */
@Nonnull @Nonnull
private static Pattern getPatternInternal(final String pattern) { private static Pattern getPatternInternal(final String pattern, final int flags) {
Pattern result = PATTERN_CACHE.get(pattern); final RegexAndFlags regexAndFlags = new RegexAndFlags(pattern, flags);
if (result == null) { return Optional.ofNullable(PATTERN_CACHE.get(regexAndFlags))
result = Pattern.compile(pattern); .orElseGet(regexAndFlags::compilePattern);
}
return result;
} }
/** /**
@ -241,8 +331,49 @@ public final class RegexTools {
.allMatch(pattern -> pattern.matcher(input).matches()); .allMatch(pattern -> pattern.matcher(input).matches());
} }
// ================================
// #endregion - internal methods
// ================================
private RegexTools() { private RegexTools() {
// 不允许实例化 // 不允许实例化
throw new IllegalStateException("Utility class"); 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
// ================================
} }

View File

@ -33,19 +33,42 @@ public
class RegexToolsTests { class RegexToolsTests {
@Test @Test
void getPattern_CachePatternTrue_ReturnsCachedPattern() { void getPattern_SameRegexAndFlag_CachePatternIsTrue_ReturnsCachedPattern() {
String pattern = "abc"; String pattern = "abc";
Pattern cachedPattern = RegexTools.getPattern(pattern, true); Pattern cachedPattern = RegexTools.getPattern(pattern, true);
Pattern patternFromCache = RegexTools.getPattern(pattern, true); Pattern patternFromCache = RegexTools.getPattern(pattern);
assertSame(cachedPattern, patternFromCache, "Pattern should be cached"); 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 @Test
void getPattern_CachePatternFalse_ReturnsNewPattern() { void getPattern_SameRegexAndFlag_CachePatternFalse_ReturnsNewPattern() {
String pattern = "getPattern_CachePatternFalse_ReturnsNewPattern"; String pattern = "getPattern_SameRegexAndFlag_CachePatternFalse_ReturnsNewPattern";
Pattern pattern1 = RegexTools.getPattern(pattern, false); Pattern pattern1 = RegexTools.getPattern(pattern, false);
Pattern pattern2 = 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, 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 @Test
@ -53,27 +76,38 @@ class RegexToolsTests {
assertThrows(NullPointerException.class, () -> { assertThrows(NullPointerException.class, () -> {
RegexTools.getPattern(null, true); RegexTools.getPattern(null, true);
}); });
assertThrows(NullPointerException.class, () -> {
RegexTools.getPattern(null, Pattern.CASE_INSENSITIVE, true);
});
} }
@Test @Test
void matches_InputMatchesPattern_ReturnsTrue() { void matches_InputMatchesPattern_ReturnsTrue() {
String pattern = "abc"; 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); Pattern compiledPattern = Pattern.compile(pattern);
assertTrue(RegexTools.matches("abc", compiledPattern), "Input should match 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 @Test
void matches_InputDoesNotMatchPattern_ReturnsFalse() { void matches_InputDoesNotMatchPattern_ReturnsFalse() {
String pattern = "abc"; String pattern = "abc";
Pattern compiledPattern = Pattern.compile(pattern); assertFalse(RegexTools.matches("abcd", pattern), "Input should not match pattern");
assertFalse(RegexTools.matches("abcd", compiledPattern), "Input should not match pattern");
} }
@Test @Test
void matches_NullInput_ReturnsFalse() { void matches_NullInput_ReturnsFalse() {
String pattern = "abc"; String pattern = "abc";
Pattern compiledPattern = Pattern.compile(pattern); assertFalse(RegexTools.matches(null, pattern), "Null input should return false");
assertFalse(RegexTools.matches(null, compiledPattern), "Null input should return false");
} }
@Test @Test
@ -94,6 +128,7 @@ class RegexToolsTests {
compiledPatterns[i] = Pattern.compile(patterns[i]); compiledPatterns[i] = Pattern.compile(patterns[i]);
} }
assertFalse(RegexTools.matchesOne("xyz", compiledPatterns), "Input should not match any pattern"); assertFalse(RegexTools.matchesOne("xyz", compiledPatterns), "Input should not match any pattern");
assertFalse(RegexTools.matchesOne(null, compiledPatterns), "Input should not match any pattern");
} }
@Test @Test
@ -114,30 +149,40 @@ class RegexToolsTests {
compiledPatterns[i] = Pattern.compile(patterns[i]); compiledPatterns[i] = Pattern.compile(patterns[i]);
} }
assertFalse(RegexTools.matchesAll("abc", compiledPatterns), "Input should not match all patterns"); assertFalse(RegexTools.matchesAll("abc", compiledPatterns), "Input should not match all patterns");
assertFalse(RegexTools.matchesAll(null, compiledPatterns), "Input should not match all patterns");
} }
@Test @Test
void getMatcher_ValidInputAndPattern_ReturnsMatcher() { void getMatcher_ValidInputAndPattern_ReturnsMatcher() {
String pattern = "abc"; String pattern = "abc";
Pattern compiledPattern = Pattern.compile(pattern); Matcher matcher1 = RegexTools.getMatcher("abc", pattern);
Matcher matcher = RegexTools.getMatcher("abc", compiledPattern); assertNotNull(matcher1, "Matcher should not be null");
assertNotNull(matcher, "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 @Test
void getMatcher_NullInput_ThrowsException() { void getMatcher_NullInput_ThrowsException() {
String pattern = "abc"; String pattern = "abc";
Pattern compiledPattern = Pattern.compile(pattern);
assertThrows(NullPointerException.class, () -> { assertThrows(NullPointerException.class, () -> {
RegexTools.getMatcher(null, compiledPattern); RegexTools.getMatcher(null, pattern);
}); });
} }
@Test @Test
void getMatcher_NullPattern_ThrowsException() { void getMatcher_NullPattern_ThrowsException() {
final Pattern pattern = null;
assertThrows(NullPointerException.class, () -> { assertThrows(NullPointerException.class, () -> {
RegexTools.getMatcher("abc", pattern); RegexTools.getMatcher("abc", (String) null);
});
assertThrows(NullPointerException.class, () -> {
RegexTools.getMatcher("abc", (Pattern) null);
}); });
} }