1 /* 2 * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 ******************************************************************************* 28 * Copyright (C) 2009-2010, International Business Machines Corporation and * 29 * others. All Rights Reserved. * 30 ******************************************************************************* 31 */ 32 33 package sun.util.locale; 34 35 import jdk.internal.misc.VM; 36 import jdk.internal.vm.annotation.Stable; 37 38 import java.lang.ref.SoftReference; 39 import java.util.StringJoiner; 40 41 public final class BaseLocale { 42 43 public static @Stable BaseLocale[] constantBaseLocales; 44 public static final byte ENGLISH = 0, 45 FRENCH = 1, 46 GERMAN = 2, 47 ITALIAN = 3, 48 JAPANESE = 4, 49 KOREAN = 5, 50 CHINESE = 6, 51 SIMPLIFIED_CHINESE = 7, 52 TRADITIONAL_CHINESE = 8, 53 FRANCE = 9, 54 GERMANY = 10, 55 ITALY = 11, 56 JAPAN = 12, 57 KOREA = 13, 58 UK = 14, 59 US = 15, 60 CANADA = 16, 61 CANADA_FRENCH = 17, 62 ROOT = 18, 63 NUM_CONSTANTS = 19; 64 static { 65 VM.initializeFromArchive(BaseLocale.class); 66 BaseLocale[] baseLocales = constantBaseLocales; 67 if (baseLocales == null) { 68 baseLocales = new BaseLocale[NUM_CONSTANTS]; 69 baseLocales[ENGLISH] = createInstance("en", ""); 70 baseLocales[FRENCH] = createInstance("fr", ""); 71 baseLocales[GERMAN] = createInstance("de", ""); 72 baseLocales[ITALIAN] = createInstance("it", ""); 73 baseLocales[JAPANESE] = createInstance("ja", ""); 74 baseLocales[KOREAN] = createInstance("ko", ""); 75 baseLocales[CHINESE] = createInstance("zh", ""); 76 baseLocales[SIMPLIFIED_CHINESE] = createInstance("zh", "CN"); 77 baseLocales[TRADITIONAL_CHINESE] = createInstance("zh", "TW"); 78 baseLocales[FRANCE] = createInstance("fr", "FR"); 79 baseLocales[GERMANY] = createInstance("de", "DE"); 80 baseLocales[ITALY] = createInstance("it", "IT"); 81 baseLocales[JAPAN] = createInstance("ja", "JP"); 82 baseLocales[KOREA] = createInstance("ko", "KR"); 83 baseLocales[UK] = createInstance("en", "GB"); 84 baseLocales[US] = createInstance("en", "US"); 85 baseLocales[CANADA] = createInstance("en", "CA"); 86 baseLocales[CANADA_FRENCH] = createInstance("fr", "CA"); 87 baseLocales[ROOT] = createInstance("", ""); 88 constantBaseLocales = baseLocales; 89 } 90 } 91 92 public static final String SEP = "_"; 93 94 private final String language; 95 private final String script; 96 private final String region; 97 private final String variant; 98 99 private volatile int hash; 100 101 // This method must be called with normalize = false only when creating the 102 // Locale.* constants and non-normalized BaseLocale$Keys used for lookup. 103 private BaseLocale(String language, String script, String region, String variant, 104 boolean normalize) { 105 if (normalize) { 106 this.language = LocaleUtils.toLowerString(language).intern(); 107 this.script = LocaleUtils.toTitleString(script).intern(); 108 this.region = LocaleUtils.toUpperString(region).intern(); 109 this.variant = variant.intern(); 110 } else { 111 this.language = language; 112 this.script = script; 113 this.region = region; 114 this.variant = variant; 115 } 116 } 117 118 // Called for creating the Locale.* constants. No argument 119 // validation is performed. 120 private static BaseLocale createInstance(String language, String region) { 121 return new BaseLocale(language, "", region, "", false); 122 } 123 124 public static BaseLocale getInstance(String language, String script, 125 String region, String variant) { 126 127 if (script == null) { 128 script = ""; 129 } 130 if (region == null) { 131 region = ""; 132 } 133 if (language == null) { 134 language = null; 135 } 136 if (variant == null) { 137 variant = ""; 138 } 139 140 // Non-allocating for most uses 141 language = LocaleUtils.toLowerString(language); 142 region = LocaleUtils.toUpperString(region); 143 144 // Check for constant base locales first 145 if (script.isEmpty() && variant.isEmpty()) { 146 for (BaseLocale baseLocale : constantBaseLocales) { 147 if (baseLocale.getLanguage().equals(language) 148 && baseLocale.getRegion().equals(region)) { 149 return baseLocale; 150 } 151 } 152 } 153 154 // JDK uses deprecated ISO639.1 language codes for he, yi and id 155 if (!language.isEmpty()) { 156 if (language.equals("he")) { 157 language = "iw"; 158 } else if (language.equals("yi")) { 159 language = "ji"; 160 } else if (language.equals("id")) { 161 language = "in"; 162 } 163 } 164 165 Key key = new Key(language, script, region, variant, false); 166 return Cache.CACHE.get(key); 167 } 168 169 public String getLanguage() { 170 return language; 171 } 172 173 public String getScript() { 174 return script; 175 } 176 177 public String getRegion() { 178 return region; 179 } 180 181 public String getVariant() { 182 return variant; 183 } 184 185 @Override 186 public boolean equals(Object obj) { 187 if (this == obj) { 188 return true; 189 } 190 if (!(obj instanceof BaseLocale)) { 191 return false; 192 } 193 BaseLocale other = (BaseLocale)obj; 194 return language == other.language 195 && script == other.script 196 && region == other.region 197 && variant == other.variant; 198 } 199 200 @Override 201 public String toString() { 202 StringJoiner sj = new StringJoiner(", "); 203 if (!language.isEmpty()) { 204 sj.add("language=" + language); 205 } 206 if (!script.isEmpty()) { 207 sj.add("script=" + script); 208 } 209 if (!region.isEmpty()) { 210 sj.add("region=" + region); 211 } 212 if (!variant.isEmpty()) { 213 sj.add("variant=" + variant); 214 } 215 return sj.toString(); 216 } 217 218 @Override 219 public int hashCode() { 220 int h = hash; 221 if (h == 0) { 222 // Generating a hash value from language, script, region and variant 223 h = language.hashCode(); 224 h = 31 * h + script.hashCode(); 225 h = 31 * h + region.hashCode(); 226 h = 31 * h + variant.hashCode(); 227 if (h != 0) { 228 hash = h; 229 } 230 } 231 return h; 232 } 233 234 private static final class Key { 235 /** 236 * Keep a SoftReference to the Key data if normalized (actually used 237 * as a cache key) and not initialized via the constant creation path. 238 * 239 * This allows us to avoid creating SoftReferences on lookup Keys 240 * (which are short-lived) and for Locales created via 241 * Locale#createConstant. 242 */ 243 private final SoftReference<BaseLocale> holderRef; 244 private final BaseLocale holder; 245 246 private final boolean normalized; 247 private final int hash; 248 249 private Key(String language, String script, String region, 250 String variant, boolean normalize) { 251 BaseLocale locale = new BaseLocale(language, script, region, variant, normalize); 252 this.normalized = normalize; 253 if (normalized) { 254 this.holderRef = new SoftReference<>(locale); 255 this.holder = null; 256 } else { 257 this.holderRef = null; 258 this.holder = locale; 259 } 260 this.hash = hashCode(locale); 261 } 262 263 public int hashCode() { 264 return hash; 265 } 266 267 private int hashCode(BaseLocale locale) { 268 int h = 0; 269 String lang = locale.getLanguage(); 270 int len = lang.length(); 271 for (int i = 0; i < len; i++) { 272 h = 31*h + LocaleUtils.toLower(lang.charAt(i)); 273 } 274 String scrt = locale.getScript(); 275 len = scrt.length(); 276 for (int i = 0; i < len; i++) { 277 h = 31*h + LocaleUtils.toLower(scrt.charAt(i)); 278 } 279 String regn = locale.getRegion(); 280 len = regn.length(); 281 for (int i = 0; i < len; i++) { 282 h = 31*h + LocaleUtils.toLower(regn.charAt(i)); 283 } 284 String vart = locale.getVariant(); 285 len = vart.length(); 286 for (int i = 0; i < len; i++) { 287 h = 31*h + vart.charAt(i); 288 } 289 return h; 290 } 291 292 private BaseLocale getBaseLocale() { 293 return (holder == null) ? holderRef.get() : holder; 294 } 295 296 @Override 297 public boolean equals(Object obj) { 298 if (this == obj) { 299 return true; 300 } 301 if (obj instanceof Key && this.hash == ((Key)obj).hash) { 302 BaseLocale other = ((Key) obj).getBaseLocale(); 303 BaseLocale locale = this.getBaseLocale(); 304 if (other != null && locale != null 305 && LocaleUtils.caseIgnoreMatch(other.getLanguage(), locale.getLanguage()) 306 && LocaleUtils.caseIgnoreMatch(other.getScript(), locale.getScript()) 307 && LocaleUtils.caseIgnoreMatch(other.getRegion(), locale.getRegion()) 308 // variant is case sensitive in JDK! 309 && other.getVariant().equals(locale.getVariant())) { 310 return true; 311 } 312 } 313 return false; 314 } 315 316 public static Key normalize(Key key) { 317 if (key.normalized) { 318 return key; 319 } 320 321 // Only normalized keys may be softly referencing the data holder 322 assert (key.holder != null && key.holderRef == null); 323 BaseLocale locale = key.holder; 324 return new Key(locale.getLanguage(), locale.getScript(), 325 locale.getRegion(), locale.getVariant(), true); 326 } 327 } 328 329 private static class Cache extends LocaleObjectCache<Key, BaseLocale> { 330 331 private static final Cache CACHE = new Cache(); 332 333 public Cache() { 334 } 335 336 @Override 337 protected Key normalizeKey(Key key) { 338 return Key.normalize(key); 339 } 340 341 @Override 342 protected BaseLocale createObject(Key key) { 343 return Key.normalize(key).getBaseLocale(); 344 } 345 } 346 }