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 android.compat.Compatibility; 36 import android.compat.annotation.ChangeId; 37 import android.compat.annotation.EnabledSince; 38 39 import dalvik.annotation.compat.VersionCodes; 40 import dalvik.system.VMRuntime; 41 42 import jdk.internal.vm.annotation.Stable; 43 44 import java.lang.ref.SoftReference; 45 import java.util.StringJoiner; 46 47 public final class BaseLocale { 48 49 public static @Stable BaseLocale[] constantBaseLocales; 50 public static final byte ENGLISH = 0, 51 FRENCH = 1, 52 GERMAN = 2, 53 ITALIAN = 3, 54 JAPANESE = 4, 55 KOREAN = 5, 56 CHINESE = 6, 57 SIMPLIFIED_CHINESE = 7, 58 TRADITIONAL_CHINESE = 8, 59 FRANCE = 9, 60 GERMANY = 10, 61 ITALY = 11, 62 JAPAN = 12, 63 KOREA = 13, 64 UK = 14, 65 US = 15, 66 CANADA = 16, 67 CANADA_FRENCH = 17, 68 ROOT = 18, 69 NUM_CONSTANTS = 19; 70 static { 71 // Android-removed: CDS is not supported. 72 // CDS.initializeFromArchive(BaseLocale.class); 73 BaseLocale[] baseLocales = constantBaseLocales; 74 if (baseLocales == null) { 75 baseLocales = new BaseLocale[NUM_CONSTANTS]; 76 baseLocales[ENGLISH] = createInstance("en", ""); 77 baseLocales[FRENCH] = createInstance("fr", ""); 78 baseLocales[GERMAN] = createInstance("de", ""); 79 baseLocales[ITALIAN] = createInstance("it", ""); 80 baseLocales[JAPANESE] = createInstance("ja", ""); 81 baseLocales[KOREAN] = createInstance("ko", ""); 82 baseLocales[CHINESE] = createInstance("zh", ""); 83 baseLocales[SIMPLIFIED_CHINESE] = createInstance("zh", "CN"); 84 baseLocales[TRADITIONAL_CHINESE] = createInstance("zh", "TW"); 85 baseLocales[FRANCE] = createInstance("fr", "FR"); 86 baseLocales[GERMANY] = createInstance("de", "DE"); 87 baseLocales[ITALY] = createInstance("it", "IT"); 88 baseLocales[JAPAN] = createInstance("ja", "JP"); 89 baseLocales[KOREA] = createInstance("ko", "KR"); 90 baseLocales[UK] = createInstance("en", "GB"); 91 baseLocales[US] = createInstance("en", "US"); 92 baseLocales[CANADA] = createInstance("en", "CA"); 93 baseLocales[CANADA_FRENCH] = createInstance("fr", "CA"); 94 baseLocales[ROOT] = createInstance("", ""); 95 constantBaseLocales = baseLocales; 96 } 97 } 98 99 public static final String SEP = "_"; 100 101 private final String language; 102 private final String script; 103 private final String region; 104 private final String variant; 105 106 private volatile int hash; 107 108 // BEGIN Android-added: flag to control Locale behaviour with legacy locales. 109 /** 110 * On Android V+ when this flag is enabled language codes are no longer 111 * converted to their obsolete forms: iw maps to he, ji maps to yi, and in maps to id. 112 * 113 * @hide 114 */ 115 @ChangeId 116 @EnabledSince(targetSdkVersion = VersionCodes.VANILLA_ICE_CREAM) 117 public static final long USE_NEW_ISO_LOCALE_CODES = 291868760L; 118 useOldIsoCodes()119 private static boolean useOldIsoCodes() { 120 return !(VMRuntime.getSdkVersion() >= VersionCodes.VANILLA_ICE_CREAM && 121 Compatibility.isChangeEnabled(USE_NEW_ISO_LOCALE_CODES)); 122 } 123 // END Android-added: flag to control Locale behaviour with legacy locales. 124 125 // Android-removed: call method instead of using static field value. Otherwise it can't 126 // be tested as value won't be overridden. 127 /* 128 * Boolean for the old ISO language code compatibility. 129 * 130 private static final boolean OLD_ISO_CODES = GetPropertyAction.privilegedGetProperties() 131 .getProperty("java.locale.useOldISOCodes", "false") 132 .equalsIgnoreCase("true"); 133 134 private static final boolean OLD_ISO_CODES = useOldIsoCodes(); 135 */ 136 137 // This method must be called with normalize = false only when creating the 138 // Locale.* constants and non-normalized BaseLocale$Keys used for lookup. BaseLocale(String language, String script, String region, String variant, boolean normalize)139 private BaseLocale(String language, String script, String region, String variant, 140 boolean normalize) { 141 if (normalize) { 142 this.language = LocaleUtils.toLowerString(language).intern(); 143 this.script = LocaleUtils.toTitleString(script).intern(); 144 this.region = LocaleUtils.toUpperString(region).intern(); 145 this.variant = variant.intern(); 146 } else { 147 this.language = language; 148 this.script = script; 149 this.region = region; 150 this.variant = variant; 151 } 152 } 153 154 // Called for creating the Locale.* constants. No argument 155 // validation is performed. createInstance(String language, String region)156 private static BaseLocale createInstance(String language, String region) { 157 return new BaseLocale(language, "", region, "", false); 158 } 159 getInstance(String language, String script, String region, String variant)160 public static BaseLocale getInstance(String language, String script, 161 String region, String variant) { 162 163 if (script == null) { 164 script = ""; 165 } 166 if (region == null) { 167 region = ""; 168 } 169 if (language == null) { 170 language = null; 171 } 172 if (variant == null) { 173 variant = ""; 174 } 175 176 // Non-allocating for most uses 177 language = LocaleUtils.toLowerString(language); 178 region = LocaleUtils.toUpperString(region); 179 180 // Check for constant base locales first 181 if (script.isEmpty() && variant.isEmpty()) { 182 for (BaseLocale baseLocale : constantBaseLocales) { 183 if (baseLocale.getLanguage().equals(language) 184 && baseLocale.getRegion().equals(region)) { 185 return baseLocale; 186 } 187 } 188 } 189 190 // JDK uses deprecated ISO639.1 language codes for he, yi and id 191 if (!language.isEmpty()) { 192 language = convertOldISOCodes(language); 193 } 194 195 Key key = new Key(language, script, region, variant, false); 196 return Cache.CACHE.get(key); 197 } 198 convertOldISOCodes(String language)199 public static String convertOldISOCodes(String language) { 200 return switch (language) { 201 // Android-changed: call method instead of using static field value. Otherwise it can't 202 // be tested as value won't be overridden. 203 /* 204 case "he", "iw" -> OLD_ISO_CODES ? "iw" : "he"; 205 case "id", "in" -> OLD_ISO_CODES ? "in" : "id"; 206 case "yi", "ji" -> OLD_ISO_CODES ? "ji" : "yi"; 207 */ 208 case "he", "iw" -> useOldIsoCodes() ? "iw" : "he"; 209 case "id", "in" -> useOldIsoCodes() ? "in" : "id"; 210 case "yi", "ji" -> useOldIsoCodes() ? "ji" : "yi"; 211 default -> language; 212 }; 213 } 214 getLanguage()215 public String getLanguage() { 216 return language; 217 } 218 getScript()219 public String getScript() { 220 return script; 221 } 222 getRegion()223 public String getRegion() { 224 return region; 225 } 226 getVariant()227 public String getVariant() { 228 return variant; 229 } 230 231 @Override equals(Object obj)232 public boolean equals(Object obj) { 233 if (this == obj) { 234 return true; 235 } 236 if (!(obj instanceof BaseLocale)) { 237 return false; 238 } 239 BaseLocale other = (BaseLocale)obj; 240 return language == other.language 241 && script == other.script 242 && region == other.region 243 && variant == other.variant; 244 } 245 246 @Override toString()247 public String toString() { 248 StringJoiner sj = new StringJoiner(", "); 249 if (!language.isEmpty()) { 250 sj.add("language=" + language); 251 } 252 if (!script.isEmpty()) { 253 sj.add("script=" + script); 254 } 255 if (!region.isEmpty()) { 256 sj.add("region=" + region); 257 } 258 if (!variant.isEmpty()) { 259 sj.add("variant=" + variant); 260 } 261 return sj.toString(); 262 } 263 264 @Override hashCode()265 public int hashCode() { 266 int h = hash; 267 if (h == 0) { 268 // Generating a hash value from language, script, region and variant 269 h = language.hashCode(); 270 h = 31 * h + script.hashCode(); 271 h = 31 * h + region.hashCode(); 272 h = 31 * h + variant.hashCode(); 273 if (h != 0) { 274 hash = h; 275 } 276 } 277 return h; 278 } 279 280 // BEGIN Android-added: Add a static method to clear the stale entries in Zygote 281 /** 282 * This method cleans the stale entries in BaseLocale.CACHE. This would 283 * be called in Zygote after GC but before fork, and so to avoid the 284 * cleaning of the cache to happen in child processes. 285 * 286 * @hide 287 */ cleanCache()288 public static void cleanCache() { 289 Cache.CACHE.cleanStaleEntries(); 290 } 291 // END Android-added: Add a static method to clear the stale entries in Zygote 292 293 294 /** @hide */ 295 // VisibleForTesting 296 public static final class Key { 297 /** 298 * Keep a SoftReference to the Key data if normalized (actually used 299 * as a cache key) and not initialized via the constant creation path. 300 * 301 * This allows us to avoid creating SoftReferences on lookup Keys 302 * (which are short-lived) and for Locales created via 303 * Locale#createConstant. 304 */ 305 private final SoftReference<BaseLocale> holderRef; 306 // Android-changed: Avoid GC-ing BaseLocale and throwing NPE in createObject(). b/348646292 307 // private final BaseLocale holder; 308 private BaseLocale holder; 309 310 private final boolean normalized; 311 private final int hash; 312 313 /** @hide */ 314 // VisibleForTesting Key(String language, String script, String region, String variant, boolean normalize)315 public Key(String language, String script, String region, 316 String variant, boolean normalize) { 317 BaseLocale locale = new BaseLocale(language, script, region, variant, normalize); 318 this.normalized = normalize; 319 if (normalized) { 320 this.holderRef = new SoftReference<>(locale); 321 // Android-changed: Avoid GC-ing BaseLocale and throwing NPE in createObject(). 322 // this.holder = null; 323 this.holder = locale; 324 } else { 325 this.holderRef = null; 326 this.holder = locale; 327 } 328 this.hash = hashCode(locale); 329 } 330 hashCode()331 public int hashCode() { 332 return hash; 333 } 334 hashCode(BaseLocale locale)335 private int hashCode(BaseLocale locale) { 336 int h = 0; 337 String lang = locale.getLanguage(); 338 int len = lang.length(); 339 for (int i = 0; i < len; i++) { 340 h = 31*h + LocaleUtils.toLower(lang.charAt(i)); 341 } 342 String scrt = locale.getScript(); 343 len = scrt.length(); 344 for (int i = 0; i < len; i++) { 345 h = 31*h + LocaleUtils.toLower(scrt.charAt(i)); 346 } 347 String regn = locale.getRegion(); 348 len = regn.length(); 349 for (int i = 0; i < len; i++) { 350 h = 31*h + LocaleUtils.toLower(regn.charAt(i)); 351 } 352 String vart = locale.getVariant(); 353 len = vart.length(); 354 for (int i = 0; i < len; i++) { 355 h = 31*h + vart.charAt(i); 356 } 357 return h; 358 } 359 getBaseLocale()360 private BaseLocale getBaseLocale() { 361 // Android-changed: Avoid GC-ing BaseLocale and throwing NPE in createObject(). 362 // return (holder == null) ? holderRef.get() : holder; 363 BaseLocale baseLocale = holder; 364 if (baseLocale != null) { 365 return baseLocale; 366 } 367 return holderRef.get(); 368 } 369 370 @Override equals(Object obj)371 public boolean equals(Object obj) { 372 if (this == obj) { 373 return true; 374 } 375 if (obj instanceof Key && this.hash == ((Key)obj).hash) { 376 BaseLocale other = ((Key) obj).getBaseLocale(); 377 BaseLocale locale = this.getBaseLocale(); 378 if (other != null && locale != null 379 && LocaleUtils.caseIgnoreMatch(other.getLanguage(), locale.getLanguage()) 380 && LocaleUtils.caseIgnoreMatch(other.getScript(), locale.getScript()) 381 && LocaleUtils.caseIgnoreMatch(other.getRegion(), locale.getRegion()) 382 // variant is case sensitive in JDK! 383 && other.getVariant().equals(locale.getVariant())) { 384 return true; 385 } 386 } 387 return false; 388 } 389 normalize(Key key)390 public static Key normalize(Key key) { 391 if (key.normalized) { 392 return key; 393 } 394 395 // Only normalized keys may be softly referencing the data holder 396 assert (key.holder != null && key.holderRef == null); 397 BaseLocale locale = key.holder; 398 return new Key(locale.getLanguage(), locale.getScript(), 399 locale.getRegion(), locale.getVariant(), true); 400 } 401 402 // Android-added: Avoid GC-ing BaseLocale and throwing NPE in createObject(). b/348646292 softenReferenceIfNormalized()403 private void softenReferenceIfNormalized() { 404 if (normalized) { 405 holder = null; 406 } 407 } 408 } 409 410 /** @hide */ 411 // VisibleForTesting 412 public static class Cache extends LocaleObjectCache<Key, BaseLocale> { 413 414 private static final Cache CACHE = new Cache(); 415 Cache()416 public Cache() { 417 } 418 419 @Override normalizeKey(Key key)420 protected Key normalizeKey(Key key) { 421 return Key.normalize(key); 422 } 423 424 @Override createObject(Key key)425 protected BaseLocale createObject(Key key) { 426 // Android-changed: Avoid GC-ing BaseLocale and throwing NPE here. b/348646292 427 // return Key.normalize(key).getBaseLocale(); 428 key = Key.normalize(key); 429 // LocaleObjectCache holds 2 soft references of a BaseLocale object indirectly, via the 430 // normalized Key and CacheEntry, i.e. the value object. Thus, we have to 431 // soften the reference here, or otherwise, the BaseLocale object will never be GC-ed. 432 // We have to keep a strong reference before key.getBaseLocale() is called here. 433 // Otherwise, key.getBaseLocale() may return null if the BaseLocale has been GC-ed 434 // between the calls of normalizeKey(Key) and this method. 435 BaseLocale baseLocale = key.getBaseLocale(); 436 key.softenReferenceIfNormalized(); 437 return baseLocale; 438 } 439 } 440 } 441