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. 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. 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 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 94 public String getLanguage() { 95 return language; 96 } 97 98 public String getScript() { 99 return script; 100 } 101 102 public String getRegion() { 103 return region; 104 } 105 106 public String getVariant() { 107 return variant; 108 } 109 110 @Override 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 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 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 private static final class Key { 160 /** 161 * Keep a SoftReference to the Key data if normalized (actually used 162 * as a cache key) and not initialized via the constant creation path. 163 * 164 * This allows us to avoid creating SoftReferences on lookup Keys 165 * (which are short-lived) and for Locales created via 166 * Locale#createConstant. 167 */ 168 private final SoftReference<BaseLocale> holderRef; 169 private final BaseLocale holder; 170 171 private final boolean normalized; 172 private final int hash; 173 174 /** 175 * Creates a Key. language and region must be normalized 176 * (intern'ed in the proper case). 177 */ 178 private Key(BaseLocale locale) { 179 this.holder = locale; 180 this.holderRef = null; 181 this.normalized = true; 182 String language = locale.getLanguage(); 183 String region = locale.getRegion(); 184 assert LocaleUtils.toLowerString(language).intern() == language 185 && LocaleUtils.toUpperString(region).intern() == region 186 && locale.getVariant() == "" 187 && locale.getScript() == ""; 188 189 int h = language.hashCode(); 190 if (region != "") { 191 int len = region.length(); 192 for (int i = 0; i < len; i++) { 193 h = 31 * h + LocaleUtils.toLower(region.charAt(i)); 194 } 195 } 196 hash = h; 197 } 198 199 private Key(String language, String script, String region, 200 String variant, boolean normalize) { 201 if (language == null) { 202 language = ""; 203 } 204 if (script == null) { 205 script = ""; 206 } 207 if (region == null) { 208 region = ""; 209 } 210 if (variant == null) { 211 variant = ""; 212 } 213 214 BaseLocale locale = new BaseLocale(language, script, region, variant, normalize); 215 this.normalized = normalize; 216 if (normalized) { 217 this.holderRef = new SoftReference<>(locale); 218 this.holder = null; 219 } else { 220 this.holderRef = null; 221 this.holder = locale; 222 } 223 this.hash = hashCode(locale); 224 } 225 226 public int hashCode() { 227 return hash; 228 } 229 230 private int hashCode(BaseLocale locale) { 231 int h = 0; 232 String lang = locale.getLanguage(); 233 int len = lang.length(); 234 for (int i = 0; i < len; i++) { 235 h = 31*h + LocaleUtils.toLower(lang.charAt(i)); 236 } 237 String scrt = locale.getScript(); 238 len = scrt.length(); 239 for (int i = 0; i < len; i++) { 240 h = 31*h + LocaleUtils.toLower(scrt.charAt(i)); 241 } 242 String regn = locale.getRegion(); 243 len = regn.length(); 244 for (int i = 0; i < len; i++) { 245 h = 31*h + LocaleUtils.toLower(regn.charAt(i)); 246 } 247 String vart = locale.getVariant(); 248 len = vart.length(); 249 for (int i = 0; i < len; i++) { 250 h = 31*h + vart.charAt(i); 251 } 252 return h; 253 } 254 255 private BaseLocale getBaseLocale() { 256 return (holder == null) ? holderRef.get() : holder; 257 } 258 259 @Override 260 public boolean equals(Object obj) { 261 if (this == obj) { 262 return true; 263 } 264 if (obj instanceof Key && this.hash == ((Key)obj).hash) { 265 BaseLocale other = ((Key) obj).getBaseLocale(); 266 BaseLocale locale = this.getBaseLocale(); 267 if (other != null && locale != null 268 && LocaleUtils.caseIgnoreMatch(other.getLanguage(), locale.getLanguage()) 269 && LocaleUtils.caseIgnoreMatch(other.getScript(), locale.getScript()) 270 && LocaleUtils.caseIgnoreMatch(other.getRegion(), locale.getRegion()) 271 // variant is case sensitive in JDK! 272 && other.getVariant().equals(locale.getVariant())) { 273 return true; 274 } 275 } 276 return false; 277 } 278 279 public static Key normalize(Key key) { 280 if (key.normalized) { 281 return key; 282 } 283 284 // Only normalized keys may be softly referencing the data holder 285 assert (key.holder != null && key.holderRef == null); 286 BaseLocale locale = key.holder; 287 return new Key(locale.getLanguage(), locale.getScript(), 288 locale.getRegion(), locale.getVariant(), true); 289 } 290 } 291 292 private static class Cache extends LocaleObjectCache<Key, BaseLocale> { 293 294 public Cache() { 295 } 296 297 @Override 298 protected Key normalizeKey(Key key) { 299 return Key.normalize(key); 300 } 301 302 @Override 303 protected BaseLocale createObject(Key key) { 304 return Key.normalize(key).getBaseLocale(); 305 } 306 } 307 }