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 }