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 }