1 /*
  2  * Copyright (c) 2010, 2023, 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.CDS;
 36 import jdk.internal.util.StaticProperty;
 37 import jdk.internal.vm.annotation.Stable;
 38 
 39 import java.util.Map;
 40 import java.util.StringJoiner;
 41 import java.util.WeakHashMap;
 42 
 43 public final class BaseLocale {
 44 
 45     public static @Stable BaseLocale[] constantBaseLocales;
 46     public static final byte ENGLISH = 0,
 47             FRENCH = 1,
 48             GERMAN = 2,
 49             ITALIAN = 3,
 50             JAPANESE = 4,
 51             KOREAN = 5,
 52             CHINESE = 6,
 53             SIMPLIFIED_CHINESE = 7,
 54             TRADITIONAL_CHINESE = 8,
 55             FRANCE = 9,
 56             GERMANY = 10,
 57             ITALY = 11,
 58             JAPAN = 12,
 59             KOREA = 13,
 60             UK = 14,
 61             US = 15,
 62             CANADA = 16,
 63             CANADA_FRENCH = 17,
 64             ROOT = 18,
 65             NUM_CONSTANTS = 19;
 66     static {
 67         CDS.initializeFromArchive(BaseLocale.class);
 68         BaseLocale[] baseLocales = constantBaseLocales;
 69         if (baseLocales == null) {
 70             baseLocales = new BaseLocale[NUM_CONSTANTS];
 71             baseLocales[ENGLISH] = createInstance("en", "");
 72             baseLocales[FRENCH] = createInstance("fr", "");
 73             baseLocales[GERMAN] = createInstance("de", "");
 74             baseLocales[ITALIAN] = createInstance("it", "");
 75             baseLocales[JAPANESE] = createInstance("ja", "");
 76             baseLocales[KOREAN] = createInstance("ko", "");
 77             baseLocales[CHINESE] = createInstance("zh", "");
 78             baseLocales[SIMPLIFIED_CHINESE] = createInstance("zh", "CN");
 79             baseLocales[TRADITIONAL_CHINESE] = createInstance("zh", "TW");
 80             baseLocales[FRANCE] = createInstance("fr", "FR");
 81             baseLocales[GERMANY] = createInstance("de", "DE");
 82             baseLocales[ITALY] = createInstance("it", "IT");
 83             baseLocales[JAPAN] = createInstance("ja", "JP");
 84             baseLocales[KOREA] = createInstance("ko", "KR");
 85             baseLocales[UK] = createInstance("en", "GB");
 86             baseLocales[US] = createInstance("en", "US");
 87             baseLocales[CANADA] = createInstance("en", "CA");
 88             baseLocales[CANADA_FRENCH] = createInstance("fr", "CA");
 89             baseLocales[ROOT] = createInstance("", "");
 90             constantBaseLocales = baseLocales;
 91         }
 92     }
 93 
 94     // Non-normalized to normalized BaseLocale cache for saving costly normalizations
 95     private static final Map<BaseLocale, BaseLocale> CACHE = new WeakHashMap<>();
 96 
 97     public static final String SEP = "_";
 98 
 99     private final String language;
100     private final String script;
101     private final String region;
102     private final String variant;
103 
104     private @Stable int hash;
105 
106     /**
107      * Boolean for the old ISO language code compatibility.
108      * The system property "java.locale.useOldISOCodes" is not security sensitive,
109      * so no need to ensure privileged access here.
110      */
111     private static final boolean OLD_ISO_CODES = StaticProperty.javaLocaleUseOldISOCodes()
112             .equalsIgnoreCase("true");
113 
114     private BaseLocale(String language, String script, String region, String variant) {
115         this.language = language;
116         this.script = script;
117         this.region = region;
118         this.variant = variant;
119     }
120 
121     // Called for creating the Locale.* constants. No argument
122     // validation is performed.
123     private static BaseLocale createInstance(String language, String region) {
124         return new BaseLocale(language, "", region, "");
125     }
126 
127     public static BaseLocale getInstance(String language, String script,
128                                          String region, String variant) {
129 
130         if (script == null) {
131             script = "";
132         }
133         if (region == null) {
134             region = "";
135         }
136         if (language == null) {
137             language = "";
138         }
139         if (variant == null) {
140             variant = "";
141         }
142 
143         // Non-allocating for most uses
144         language = LocaleUtils.toLowerString(language);
145         region = LocaleUtils.toUpperString(region);
146 
147         // Check for constant base locales first
148         if (script.isEmpty() && variant.isEmpty()) {
149             for (BaseLocale baseLocale : constantBaseLocales) {
150                 if (baseLocale.getLanguage().equals(language)
151                         && baseLocale.getRegion().equals(region)) {
152                     return baseLocale;
153                 }
154             }
155         }
156 
157         // JDK uses deprecated ISO639.1 language codes for he, yi and id
158         if (!language.isEmpty()) {
159             language = convertOldISOCodes(language);
160         }
161 
162         // Obtain the "normalized" BaseLocale, using un-normalized
163         // BaseLocale as the key. The returned "normalized" instance
164         // can subsequently be used by the Locale instance which
165         // guarantees the locale components are properly cased/interned.
166         return CACHE.computeIfAbsent(new BaseLocale(language, script, region, variant),
167             (b) -> new BaseLocale(
168                 LocaleUtils.toLowerString(b.getLanguage()).intern(),
169                 LocaleUtils.toTitleString(b.getScript()).intern(),
170                 LocaleUtils.toUpperString(b.getRegion()).intern(),
171                 b.getVariant().intern()));
172     }
173 
174     public static String convertOldISOCodes(String language) {
175         return switch (language) {
176             case "he", "iw" -> OLD_ISO_CODES ? "iw" : "he";
177             case "id", "in" -> OLD_ISO_CODES ? "in" : "id";
178             case "yi", "ji" -> OLD_ISO_CODES ? "ji" : "yi";
179             default -> language;
180         };
181     }
182 
183     public String getLanguage() {
184         return language;
185     }
186 
187     public String getScript() {
188         return script;
189     }
190 
191     public String getRegion() {
192         return region;
193     }
194 
195     public String getVariant() {
196         return variant;
197     }
198 
199     @Override
200     public boolean equals(Object obj) {
201         if (this == obj) {
202             return true;
203         }
204         if (obj instanceof BaseLocale other) {
205             return LocaleUtils.caseIgnoreMatch(other.getLanguage(), language)
206                 && LocaleUtils.caseIgnoreMatch(other.getScript(), script)
207                 && LocaleUtils.caseIgnoreMatch(other.getRegion(), region)
208                 // variant is case sensitive in JDK!
209                 && other.getVariant().equals(variant);
210         }
211         return false;
212     }
213 
214     @Override
215     public String toString() {
216         StringJoiner sj = new StringJoiner(", ");
217         if (!language.isEmpty()) {
218             sj.add("language=" + language);
219         }
220         if (!script.isEmpty()) {
221             sj.add("script=" + script);
222         }
223         if (!region.isEmpty()) {
224             sj.add("region=" + region);
225         }
226         if (!variant.isEmpty()) {
227             sj.add("variant=" + variant);
228         }
229         return sj.toString();
230     }
231 
232     @Override
233     public int hashCode() {
234         int h = hash;
235         if (h == 0) {
236             int len = language.length();
237             for (int i = 0; i < len; i++) {
238                 h = 31*h + LocaleUtils.toLower(language.charAt(i));
239             }
240             len = script.length();
241             for (int i = 0; i < len; i++) {
242                 h = 31*h + LocaleUtils.toLower(script.charAt(i));
243             }
244             len = region.length();
245             for (int i = 0; i < len; i++) {
246                 h = 31*h + LocaleUtils.toLower(region.charAt(i));
247             }
248             len = variant.length();
249             for (int i = 0; i < len; i++) {
250                 h = 31*h + variant.charAt(i);
251             }
252             if (h != 0) {
253                 hash = h;
254             }
255         }
256         return h;
257     }
258 }