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