001package gudusoft.gsqlparser.sqlenv; 002 003import java.text.Collator; 004import java.util.Locale; 005import java.util.concurrent.ConcurrentHashMap; 006 007/** 008 * Collator 提供者(SQL Server COLLATION_BASED 专用) 009 * 010 * <p><strong>关键设计:</strong> 011 * <ul> 012 * <li>{@link Collator} 非线程安全,使用 {@link ThreadLocal} 缓存 013 * <li>按 collation 名称缓存实例(每个 collation 一个 ThreadLocal) 014 * <li>支持常见 SQL Server collation(CI_AS, CS_AS 等) 015 * </ul> 016 * 017 * <p>使用示例: 018 * <pre> 019 * CollatorProvider provider = new CollatorProvider(); 020 * 021 * // 获取 Collator(自动 ThreadLocal 缓存) 022 * Collator collator = provider.getCollator("SQL_Latin1_General_CP1_CI_AS"); 023 * 024 * // 比较标识符 025 * int result = collator.compare("MyTable", "MYTABLE"); 026 * // CI (Case Insensitive): result == 0 027 * // CS (Case Sensitive): result != 0 028 * </pre> 029 * 030 * <p><strong>性能特性:</strong> 031 * <ul> 032 * <li>首次访问:创建 Collator 实例(~100μs) 033 * <li>后续访问:ThreadLocal.get()(~10ns) 034 * <li>内存开销:每线程每 collation 一个实例(~1KB) 035 * </ul> 036 * 037 * @since 3.1.0.9 038 */ 039public class CollatorProvider { 040 041 // Per-collation 的 ThreadLocal 缓存 042 // Key: collation 名称(如 "SQL_Latin1_General_CP1_CI_AS") 043 // Value: ThreadLocal<Collator>(每线程独立实例) 044 private final ConcurrentHashMap<String, ThreadLocal<Collator>> collatorCache = 045 new ConcurrentHashMap<>(); 046 047 // ===== 公共接口 ===== 048 049 /** 050 * 获取 Collator 实例(ThreadLocal 缓存) 051 * 052 * <p>此方法线程安全,每个线程获得独立的 Collator 实例。 053 * 054 * @param collationName SQL Server collation 名称(例如:SQL_Latin1_General_CP1_CI_AS) 055 * @return Collator 实例(线程本地) 056 */ 057 public Collator getCollator(String collationName) { 058 if (collationName == null || collationName.isEmpty()) { 059 // 默认:当前 Locale 的 Collator 060 return Collator.getInstance(); 061 } 062 063 // 为每个 collation 名称创建一个 ThreadLocal 064 ThreadLocal<Collator> tl = collatorCache.computeIfAbsent(collationName, key -> 065 ThreadLocal.withInitial(() -> createCollator(key)) 066 ); 067 068 return tl.get(); 069 } 070 071 /** 072 * 清理缓存(用于测试或内存管理) 073 * 074 * <p>警告:调用此方法后,所有线程的 Collator 实例将失效,下次访问时重新创建。 075 */ 076 public void clearCache() { 077 collatorCache.clear(); 078 } 079 080 // ===== 内部实现 ===== 081 082 /** 083 * 根据 SQL Server collation 名称创建 Collator 084 * 085 * <p>SQL Server collation 名称格式: 086 * <pre> 087 * {Language}_{Region}_CP{CodePage}_{CaseSensitivity}_{AccentSensitivity} 088 * 089 * 例如: 090 * - SQL_Latin1_General_CP1_CI_AS (Case Insensitive, Accent Sensitive) 091 * - SQL_Latin1_General_CP1_CS_AS (Case Sensitive, Accent Sensitive) 092 * - Chinese_PRC_CI_AS (简体中文, Case Insensitive) 093 * </pre> 094 * 095 * @param collationName SQL Server collation 名称 096 * @return 配置好的 Collator 实例 097 */ 098 private Collator createCollator(String collationName) { 099 // 解析 collation 名称 100 Locale locale = parseLocale(collationName); 101 Collator collator = Collator.getInstance(locale); 102 103 // 设置强度(Strength) 104 if (collationName.contains("_CI_") || collationName.contains("_CI")) { 105 // Case Insensitive(CI) 106 collator.setStrength(Collator.SECONDARY); // 忽略大小写 107 } else if (collationName.contains("_CS_") || collationName.contains("_CS")) { 108 // Case Sensitive(CS) 109 collator.setStrength(Collator.TERTIARY); // 区分大小写 110 } else { 111 // 默认:根据 Locale 决定(通常是 TERTIARY) 112 collator.setStrength(Collator.TERTIARY); 113 } 114 115 // 设置分解模式(Decomposition) 116 if (collationName.contains("_AS")) { 117 // Accent Sensitive(AS) 118 collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); 119 } else if (collationName.contains("_AI")) { 120 // Accent Insensitive(AI) 121 collator.setDecomposition(Collator.NO_DECOMPOSITION); 122 } else { 123 // 默认:Canonical Decomposition 124 collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); 125 } 126 127 return collator; 128 } 129 130 /** 131 * 从 collation 名称解析 Locale 132 * 133 * <p>这是简化实现,仅支持常见 collation。 134 * 生产环境需要完整的 SQL Server collation → Java Locale 映射表。 135 * 136 * @param collationName SQL Server collation 名称 137 * @return 对应的 Java Locale 138 */ 139 private Locale parseLocale(String collationName) { 140 String lowerName = collationName.toLowerCase(); 141 142 // SQL Server 常见 collation 映射 143 if (lowerName.contains("latin1") || lowerName.contains("sql_")) { 144 // Latin1 / SQL_ 前缀 → 英语(美国) 145 return new Locale("en", "US"); 146 } else if (lowerName.contains("chinese_prc") || lowerName.contains("chinese_simplified")) { 147 // 简体中文 148 return new Locale("zh", "CN"); 149 } else if (lowerName.contains("chinese_taiwan") || lowerName.contains("chinese_traditional")) { 150 // 繁体中文(台湾) 151 return new Locale("zh", "TW"); 152 } else if (lowerName.contains("chinese_hong_kong")) { 153 // 繁体中文(香港) 154 return new Locale("zh", "HK"); 155 } else if (lowerName.contains("japanese")) { 156 // 日语 157 return new Locale("ja", "JP"); 158 } else if (lowerName.contains("korean")) { 159 // 韩语 160 return new Locale("ko", "KR"); 161 } else if (lowerName.contains("arabic")) { 162 // 阿拉伯语 163 return new Locale("ar"); 164 } else if (lowerName.contains("french")) { 165 // 法语 166 return new Locale("fr", "FR"); 167 } else if (lowerName.contains("german")) { 168 // 德语 169 return new Locale("de", "DE"); 170 } else if (lowerName.contains("spanish")) { 171 // 西班牙语 172 return new Locale("es", "ES"); 173 } else if (lowerName.contains("italian")) { 174 // 意大利语 175 return new Locale("it", "IT"); 176 } else if (lowerName.contains("russian")) { 177 // 俄语 178 return new Locale("ru", "RU"); 179 } else { 180 // 默认:英语(美国) 181 return Locale.US; 182 } 183 184 // 生产环境实现: 185 // 1. 维护完整的 SQL Server collation → Locale 映射表 186 // 2. 解析 collation 名称的各个组成部分 187 // 3. 根据 Language 和 Region 构造 Locale 188 // 4. 参考:https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support 189 } 190 191 // ===== 调试方法 ===== 192 193 /** 194 * 获取缓存大小(不同 collation 的数量) 195 * 196 * @return 缓存的 collation 数量 197 */ 198 public int getCacheSize() { 199 return collatorCache.size(); 200 } 201 202 /** 203 * 测试 collation 比较(用于调试) 204 * 205 * @param collationName collation 名称 206 * @param str1 字符串 1 207 * @param str2 字符串 2 208 * @return 比较结果(0: 相等, <0: str1 < str2, >0: str1 > str2) 209 */ 210 public int testCompare(String collationName, String str1, String str2) { 211 Collator collator = getCollator(collationName); 212 return collator.compare(str1, str2); 213 } 214}