通配符对称冲突检测工具
package com.ldj.springboot.importbean.config;import lombok.extern.slf4j.Slf4j;import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 通配符模糊对称冲突检测工具类* 支持:* - 纯数字: 如 "1001"* - 前缀模糊:如 "1001*"* - 后缀模糊:如 "*10"* - 中间通配:如 "10*1"* - 全匹配: 如 "*"* 不支持:包含多个 '*'*/
@Slf4j
public class WildcardPatternUtil {// 缓存解析结果,提升性能private static final Map<String, PatternInfo> CACHE = new ConcurrentHashMap<>();/*** 判断两个模糊是否可能匹配同一个字符串(即:冲突)*/public static boolean isConflict(String p1, String p2) {if (p1 == null || p2 == null) {return false;}PatternInfo info1 = parsePattern(p1);PatternInfo info2 = parsePattern(p2);boolean prefixOk = isPrefixCompatible(info1.prefix, info2.prefix);boolean suffixOk = isSuffixCompatible(info1.suffix, info2.suffix);log.debug("模糊冲突检测: '{}' vs '{}' -> 前缀:{} 后缀:{} 结果:{}", p1, p2, prefixOk, suffixOk, prefixOk && suffixOk);return prefixOk && suffixOk;}/*** 检查给定模糊 p1 是否与 patterns 列表中的任意一个模糊冲突*/public static boolean isConflictWithAny(String p1, List<String> patterns) {if (p1 == null || patterns == null || patterns.isEmpty()) {return false;}return patterns.stream().anyMatch(p -> isConflict(p1, p));}/*** 解析模糊为前缀和后缀* 缓存结果以提升性能*/private static PatternInfo parsePattern(String pattern) {return CACHE.computeIfAbsent(pattern, k -> {if (k == null || k.isEmpty()) {return new PatternInfo("", "");}int firstStar = k.indexOf('*');int lastStar = k.lastIndexOf('*');// 检查是否包含多个'*'if (firstStar != lastStar) {throw new IllegalArgumentException("Pattern cannot contain multiple '*' : " + k);}if (firstStar == -1) {// 纯数字:精确匹配,前缀和后缀都设为整个字符串return new PatternInfo(k, k);}String prefix = k.substring(0, firstStar);String suffix = (firstStar + 1 < k.length()) ? k.substring(firstStar + 1) : "";return new PatternInfo(prefix, suffix);});}/*** 判断两个前缀是否兼容* 规则:较短的是较长的前缀,或任意一个为空(表示 *xxx)*/private static boolean isPrefixCompatible(String pre1, String pre2) {if (pre1.isEmpty() || pre2.isEmpty()) {return true; // 有一个是 *xxx 形式,总是兼容}return pre1.startsWith(pre2) || pre2.startsWith(pre1);}/*** 判断两个后缀是否兼容* 规则:较短的是较长的后缀,或任意一个为空(表示 xxx*)*/private static boolean isSuffixCompatible(String suf1, String suf2) {if (suf1.isEmpty() || suf2.isEmpty()) {return true; // 有一个是 xxx* 形式,总是兼容}return suf1.endsWith(suf2) || suf2.endsWith(suf1);}// 内部类:存储模糊的前缀和后缀private static class PatternInfo {final String prefix;final String suffix;PatternInfo(String prefix, String suffix) {this.prefix = prefix;this.suffix = suffix;}}// ========== 测试用例 ==========public static void main(String[] args) {String p1 = "10*1";// 测试1: 纯数字 vs 模糊List<String> patterns1 = Collections.singletonList("1001");boolean conflict1 = isConflictWithAny(p1, patterns1);System.out.println("10*1 与 1001 冲突? " + conflict1); // true// 测试2: 1* 是前缀模糊List<String> patterns2 = Collections.singletonList("1*");boolean conflict2 = isConflictWithAny(p1, patterns2);System.out.println("10*1 与 1* 冲突? " + conflict2); // true// 测试3: 20*1 不冲突List<String> patterns3 = Collections.singletonList("20*1");boolean conflict3 = isConflictWithAny(p1, patterns3);System.out.println("10*1 与 20*1 冲突? " + conflict3); // false// 测试4: 1001* 前缀模糊List<String> patterns4 = Collections.singletonList("1001*");boolean conflict4 = isConflictWithAny(p1, patterns4);System.out.println("10*1 与 1001* 冲突? " + conflict4); // true (因为 10011 匹配两者)// 测试5: *10 后缀模糊List<String> patterns5 = Collections.singletonList("*10");boolean conflict5 = isConflictWithAny(p1, patterns5);System.out.println("10*1 与 *10 冲突? " + conflict5); // false// 测试6: 纯数字不冲突List<String> patterns6 = Collections.singletonList("2002");boolean conflict6 = isConflictWithAny(p1, patterns6);System.out.println("10*1 与 2002 冲突? " + conflict6); // false// 测试7: * 全匹配List<String> patterns7 = Collections.singletonList("*");boolean conflict7 = isConflictWithAny(p1, patterns7);System.out.println("10*1 与 * 冲突? " + conflict7); // true// 测试8: * 多个List<String> patterns8 = Collections.singletonList("101");boolean conflict8 = isConflictWithAny("10*1*", patterns8);System.out.println("10*1*与101冲突? " + conflict8); // true}
}
package com.ldj.springboot.importbean.config;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class WildcardPatternUtil {
private static final Map<String, PatternInfo> CACHE = new ConcurrentHashMap<>();
/**
* 判断两个模式是否冲突(即存在至少一个字符串能被两者匹配)
*/
public static boolean isConflict(String p1, String p2) {
if (p1 == null || p2 == null || p1.isEmpty() || p2.isEmpty()) {
return false;
}
PatternInfo a = parsePattern(p1);
PatternInfo b = parsePattern(p2);
// 1. 都是精确匹配
if (!a.hasWildcard && !b.hasWildcard) {
return p1.equals(p2);
}
// 2. 一个是精确,另一个是模糊
if (!a.hasWildcard) {
return matches(b, p1);
}
if (!b.hasWildcard) {
return matches(a, p2);
}
// 3. 两个都是模糊:判断是否有交集
return canIntersect(a, b);
}
// ==================== 解析 ====================
private static PatternInfo parsePattern(String pattern) {
return CACHE.computeIfAbsent(pattern, PatternInfo::new);
}
private static class PatternInfo {
final String prefix; // *之前的部分
final String suffix; // *之后的部分
final boolean hasWildcard;
PatternInfo(String pattern) {
int star = pattern.indexOf('*');
if (star == -1) {
prefix = pattern;
suffix = pattern;
hasWildcard = false;
} else {
if (pattern.lastIndexOf('*') != star) {
throw new IllegalArgumentException("Multiple wildcards not supported: " + pattern);
}
prefix = pattern.substring(0, star);
suffix = (star + 1 < pattern.length()) ? pattern.substring(star + 1) : "";
hasWildcard = true;
}
}
}
// ==================== 精确判断是否匹配 ====================
/**
* 判断模糊模式 info 是否匹配目标字符串 str
*/
private static boolean matches(PatternInfo info, String str) {
if (!str.startsWith(info.prefix)) {
return false;
}
if (!info.suffix.isEmpty() && !str.endsWith(info.suffix)) {
return false;
}
// 检查前后缀是否重叠且一致
int minLen = info.prefix.length() + info.suffix.length();
if (str.length() < minLen) {
return false;
}
// 重叠部分检查
int suffixStart = str.length() - info.suffix.length();
int prefixEnd = info.prefix.length();
if (suffixStart < prefixEnd) {
String overlapStr = str.substring(suffixStart, prefixEnd);
String suffixPrefixPart = info.suffix.substring(0, prefixEnd - suffixStart);
return overlapStr.equals(suffixPrefixPart);
}
return true;
}
// ==================== 两个模糊模式是否有交集 ====================
/**
* 判断两个模糊模式是否可能匹配同一个字符串
*/
private static boolean canIntersect(PatternInfo a, PatternInfo b) {
String ap = a.prefix, as = a.suffix;
String bp = b.prefix, bs = b.suffix;
// 1. 前缀兼容性
if (!isPrefixCompatible(ap, bp)) {
return false;
}
// 2. 后缀兼容性
if (!isSuffixCompatible(as, bs)) {
return false;
}
// 3. 最小长度:是否存在足够长的字符串满足前后缀?
int minLenA = ap.length() + as.length();
int minLenB = bp.length() + bs.length();
int requiredMinLen = Math.max(minLenA, minLenB);
// 4. 构造合并后的前缀和后缀
String mergedPrefix = getLongerOrCommon(ap, bp, true);
String mergedSuffix = getLongerOrCommon(as, bs, false);
// 5. 检查合并后的前后缀是否自身冲突(重叠部分是否一致)
return !isSelfConflicting(mergedPrefix, mergedSuffix);
}
// 前缀是否兼容:一个包含另一个
private static boolean isPrefixCompatible(String a, String b) {
return a.startsWith(b) || b.startsWith(a);
}
// 后缀是否兼容:一个包含另一个
private static boolean isSuffixCompatible(String a, String b) {
return a.endsWith(b) || b.endsWith(a);
}
// 获取更长的(或共同的)前缀或后缀
private static String getLongerOrCommon(String a, String b, boolean isPrefix) {
if (isPrefix) {
return a.startsWith(b) ? a : b.startsWith(a) ? b : null;
} else {
return a.endsWith(b) ? a : b.endsWith(a) ? b : null;
}
}
/**
* 检查 mergedPrefix 和 mergedSuffix 是否自身冲突(即无法共存于同一字符串)
*/
private static boolean isSelfConflicting(String pre, String suf) {
int total = pre.length() + suf.length();
int maxLen = Math.max(pre.length(), suf.length());
if (total <= maxLen) {
// 完全重叠
String shorter = pre.length() < suf.length() ? pre : suf;
String longer = pre.length() < suf.length() ? suf : pre;
String pos = pre.length() < suf.length() ? "suffix" : "prefix";
if ("prefix".equals(pos)) {
return !longer.startsWith(shorter);
} else {
return !longer.endsWith(shorter);
}
} else {
// 部分重叠或无重叠
int sufStart = total - suf.length(); // 在总串中 suffix 的起始位置
int preEnd = pre.length();
if (sufStart < preEnd) {
// 重叠区域
String preOverlap = pre.substring(sufStart, preEnd);
String sufOverlap = suf.substring(0, preEnd - sufStart);
return !preOverlap.equals(sufOverlap);
}
// 无重叠,总是可以构造
return false;
}
}
}