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 }