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 java.lang.ref.SoftReference; 36 import java.util.StringJoiner; 37 38 public final class BaseLocale { 39 40 public static final String SEP = "_"; 41 42 private static final Cache CACHE = new Cache(); 43 44 private final String language; 45 private final String script; 46 private final String region; 47 private final String variant; 48 49 private volatile int hash; 50 51 // This method must be called with normalize = false only when creating the 52 // Locale.* constants and non-normalized BaseLocale$Keys used for lookup. BaseLocale(String language, String script, String region, String variant, boolean normalize)53 private BaseLocale(String language, String script, String region, String variant, 54 boolean normalize) { 55 if (normalize) { 56 this.language = LocaleUtils.toLowerString(language).intern(); 57 this.script = LocaleUtils.toTitleString(script).intern(); 58 this.region = LocaleUtils.toUpperString(region).intern(); 59 this.variant = variant.intern(); 60 } else { 61 this.language = language; 62 this.script = script; 63 this.region = region; 64 this.variant = variant; 65 } 66 } 67 68 // Called for creating the Locale.* constants. No argument 69 // validation is performed. createInstance(String language, String region)70 public static BaseLocale createInstance(String language, String region) { 71 BaseLocale base = new BaseLocale(language, "", region, "", false); 72 CACHE.put(new Key(base), base); 73 return base; 74 } 75 getInstance(String language, String script, String region, String variant)76 public static BaseLocale getInstance(String language, String script, 77 String region, String variant) { 78 // JDK uses deprecated ISO639.1 language codes for he, yi and id 79 if (language != null) { 80 if (LocaleUtils.caseIgnoreMatch(language, "he")) { 81 language = "iw"; 82 } else if (LocaleUtils.caseIgnoreMatch(language, "yi")) { 83 language = "ji"; 84 } else if (LocaleUtils.caseIgnoreMatch(language, "id")) { 85 language = "in"; 86 } 87 } 88 89 Key key = new Key(language, script, region, variant, false); 90 BaseLocale baseLocale = CACHE.get(key); 91 return baseLocale; 92 } 93 getLanguage()94 public String getLanguage() { 95 return language; 96 } 97 getScript()98 public String getScript() { 99 return script; 100 } 101 getRegion()102 public String getRegion() { 103 return region; 104 } 105 getVariant()106 public String getVariant() { 107 return variant; 108 } 109 110 @Override equals(Object obj)111 public boolean equals(Object obj) { 112 if (this == obj) { 113 return true; 114 } 115 if (!(obj instanceof BaseLocale)) { 116 return false; 117 } 118 BaseLocale other = (BaseLocale)obj; 119 return language == other.language 120 && script == other.script 121 && region == other.region 122 && variant == other.variant; 123 } 124 125 @Override toString()126 public String toString() { 127 StringJoiner sj = new StringJoiner(", "); 128 if (!language.isEmpty()) { 129 sj.add("language=" + language); 130 } 131 if (!script.isEmpty()) { 132 sj.add("script=" + script); 133 } 134 if (!region.isEmpty()) { 135 sj.add("region=" + region); 136 } 137 if (!variant.isEmpty()) { 138 sj.add("variant=" + variant); 139 } 140 return sj.toString(); 141 } 142 143 @Override hashCode()144 public int hashCode() { 145 int h = hash; 146 if (h == 0) { 147 // Generating a hash value from language, script, region and variant 148 h = language.hashCode(); 149 h = 31 * h + script.hashCode(); 150 h = 31 * h + region.hashCode(); 151 h = 31 * h + variant.hashCode(); 152 if (h != 0) { 153 hash = h; 154 } 155 } 156 return h; 157 } 158 159 // BEGIN Android-added: Add a static method to clear the stale entries in Zygote 160 /** 161 * This method cleans the stale entries in BaseLocale.CACHE. This would 162 * be called in Zygote after GC but before fork, and so to avoid the 163 * cleaning of the cache to happen in child processes. 164 * 165 * @hide 166 */ cleanCache()167 public static void cleanCache() { 168 CACHE.cleanStaleEntries(); 169 } 170 // END Android-added: Add a static method to clear the stale entries in Zygote 171 172 private static final class Key { 173 /** 174 * Keep a SoftReference to the Key data if normalized (actually used 175 * as a cache key) and not initialized via the constant creation path. 176 * 177 * This allows us to avoid creating SoftReferences on lookup Keys 178 * (which are short-lived) and for Locales created via 179 * Locale#createConstant. 180 */ 181 private final SoftReference<BaseLocale> holderRef; 182 private final BaseLocale holder; 183 184 private final boolean normalized; 185 private final int hash; 186 187 /** 188 * Creates a Key. language and region must be normalized 189 * (intern'ed in the proper case). 190 */ Key(BaseLocale locale)191 private Key(BaseLocale locale) { 192 this.holder = locale; 193 this.holderRef = null; 194 this.normalized = true; 195 String language = locale.getLanguage(); 196 String region = locale.getRegion(); 197 assert LocaleUtils.toLowerString(language).intern() == language 198 && LocaleUtils.toUpperString(region).intern() == region 199 && locale.getVariant() == "" 200 && locale.getScript() == ""; 201 202 int h = language.hashCode(); 203 if (region != "") { 204 int len = region.length(); 205 for (int i = 0; i < len; i++) { 206 h = 31 * h + LocaleUtils.toLower(region.charAt(i)); 207 } 208 } 209 hash = h; 210 } 211 Key(String language, String script, String region, String variant, boolean normalize)212 private Key(String language, String script, String region, 213 String variant, boolean normalize) { 214 if (language == null) { 215 language = ""; 216 } 217 if (script == null) { 218 script = ""; 219 } 220 if (region == null) { 221 region = ""; 222 } 223 if (variant == null) { 224 variant = ""; 225 } 226 227 BaseLocale locale = new BaseLocale(language, script, region, variant, normalize); 228 this.normalized = normalize; 229 if (normalized) { 230 this.holderRef = new SoftReference<>(locale); 231 this.holder = null; 232 } else { 233 this.holderRef = null; 234 this.holder = locale; 235 } 236 this.hash = hashCode(locale); 237 } 238 hashCode()239 public int hashCode() { 240 return hash; 241 } 242 hashCode(BaseLocale locale)243 private int hashCode(BaseLocale locale) { 244 int h = 0; 245 String lang = locale.getLanguage(); 246 int len = lang.length(); 247 for (int i = 0; i < len; i++) { 248 h = 31*h + LocaleUtils.toLower(lang.charAt(i)); 249 } 250 String scrt = locale.getScript(); 251 len = scrt.length(); 252 for (int i = 0; i < len; i++) { 253 h = 31*h + LocaleUtils.toLower(scrt.charAt(i)); 254 } 255 String regn = locale.getRegion(); 256 len = regn.length(); 257 for (int i = 0; i < len; i++) { 258 h = 31*h + LocaleUtils.toLower(regn.charAt(i)); 259 } 260 String vart = locale.getVariant(); 261 len = vart.length(); 262 for (int i = 0; i < len; i++) { 263 h = 31*h + vart.charAt(i); 264 } 265 return h; 266 } 267 getBaseLocale()268 private BaseLocale getBaseLocale() { 269 return (holder == null) ? holderRef.get() : holder; 270 } 271 272 @Override equals(Object obj)273 public boolean equals(Object obj) { 274 if (this == obj) { 275 return true; 276 } 277 if (obj instanceof Key && this.hash == ((Key)obj).hash) { 278 BaseLocale other = ((Key) obj).getBaseLocale(); 279 BaseLocale locale = this.getBaseLocale(); 280 if (other != null && locale != null 281 && LocaleUtils.caseIgnoreMatch(other.getLanguage(), locale.getLanguage()) 282 && LocaleUtils.caseIgnoreMatch(other.getScript(), locale.getScript()) 283 && LocaleUtils.caseIgnoreMatch(other.getRegion(), locale.getRegion()) 284 // variant is case sensitive in JDK! 285 && other.getVariant().equals(locale.getVariant())) { 286 return true; 287 } 288 } 289 return false; 290 } 291 normalize(Key key)292 public static Key normalize(Key key) { 293 if (key.normalized) { 294 return key; 295 } 296 297 // Only normalized keys may be softly referencing the data holder 298 assert (key.holder != null && key.holderRef == null); 299 BaseLocale locale = key.holder; 300 return new Key(locale.getLanguage(), locale.getScript(), 301 locale.getRegion(), locale.getVariant(), true); 302 } 303 } 304 305 private static class Cache extends LocaleObjectCache<Key, BaseLocale> { 306 Cache()307 public Cache() { 308 } 309 310 @Override normalizeKey(Key key)311 protected Key normalizeKey(Key key) { 312 return Key.normalize(key); 313 } 314 315 @Override createObject(Key key)316 protected BaseLocale createObject(Key key) { 317 return Key.normalize(key).getBaseLocale(); 318 } 319 } 320 } 321