Justin Lu cd9b1bc820 8358426: Improve lazy computation in Locale
Reviewed-by: naoto, liach
2025-06-09 20:49:33 +00:00

269 lines
9.3 KiB
Java

/*
* Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
*******************************************************************************
* Copyright (C) 2009-2010, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package sun.util.locale;
import jdk.internal.misc.CDS;
import jdk.internal.util.ReferencedKeySet;
import jdk.internal.util.StaticProperty;
import jdk.internal.vm.annotation.Stable;
import java.util.StringJoiner;
import java.util.function.Supplier;
public final class BaseLocale {
public static @Stable BaseLocale[] constantBaseLocales;
public static final byte ROOT = 0,
ENGLISH = 1,
US = 2,
FRENCH = 3,
GERMAN = 4,
ITALIAN = 5,
JAPANESE = 6,
KOREAN = 7,
CHINESE = 8,
SIMPLIFIED_CHINESE = 9,
TRADITIONAL_CHINESE = 10,
FRANCE = 11,
GERMANY = 12,
ITALY = 13,
JAPAN = 14,
KOREA = 15,
UK = 16,
CANADA = 17,
CANADA_FRENCH = 18,
NUM_CONSTANTS = 19;
static {
CDS.initializeFromArchive(BaseLocale.class);
BaseLocale[] baseLocales = constantBaseLocales;
if (baseLocales == null) {
baseLocales = new BaseLocale[NUM_CONSTANTS];
baseLocales[ENGLISH] = createInstance("en", "");
baseLocales[FRENCH] = createInstance("fr", "");
baseLocales[GERMAN] = createInstance("de", "");
baseLocales[ITALIAN] = createInstance("it", "");
baseLocales[JAPANESE] = createInstance("ja", "");
baseLocales[KOREAN] = createInstance("ko", "");
baseLocales[CHINESE] = createInstance("zh", "");
baseLocales[SIMPLIFIED_CHINESE] = createInstance("zh", "CN");
baseLocales[TRADITIONAL_CHINESE] = createInstance("zh", "TW");
baseLocales[FRANCE] = createInstance("fr", "FR");
baseLocales[GERMANY] = createInstance("de", "DE");
baseLocales[ITALY] = createInstance("it", "IT");
baseLocales[JAPAN] = createInstance("ja", "JP");
baseLocales[KOREA] = createInstance("ko", "KR");
baseLocales[UK] = createInstance("en", "GB");
baseLocales[US] = createInstance("en", "US");
baseLocales[CANADA] = createInstance("en", "CA");
baseLocales[CANADA_FRENCH] = createInstance("fr", "CA");
baseLocales[ROOT] = createInstance("", "");
constantBaseLocales = baseLocales;
}
}
// Interned BaseLocale cache
private static final Supplier<ReferencedKeySet<BaseLocale>> CACHE =
StableValue.supplier(new Supplier<>() {
@Override
public ReferencedKeySet<BaseLocale> get() {
return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier());
}
});
public static final String SEP = "_";
private final String language;
private final String script;
private final String region;
private final String variant;
private @Stable int hash;
/**
* Boolean for the old ISO language code compatibility.
* The system property "java.locale.useOldISOCodes" is not security sensitive,
* so no need to ensure privileged access here.
*/
private static final boolean OLD_ISO_CODES = StaticProperty.javaLocaleUseOldISOCodes()
.equalsIgnoreCase("true");
static {
if (OLD_ISO_CODES) {
System.err.println("WARNING: The use of the system property \"java.locale.useOldISOCodes\"" +
" is deprecated. It will be removed in a future release of the JDK.");
}
}
private BaseLocale(String language, String script, String region, String variant) {
this.language = language;
this.script = script;
this.region = region;
this.variant = variant;
}
// Called for creating the Locale.* constants. No argument
// validation is performed.
private static BaseLocale createInstance(String language, String region) {
return new BaseLocale(language, "", region, "");
}
public static BaseLocale getInstance(String language, String script,
String region, String variant) {
if (script == null) {
script = "";
}
if (region == null) {
region = "";
}
if (language == null) {
language = "";
}
if (variant == null) {
variant = "";
}
// Non-allocating for most uses
language = LocaleUtils.toLowerString(language);
region = LocaleUtils.toUpperString(region);
// Check for constant base locales first
if (script.isEmpty() && variant.isEmpty()) {
for (BaseLocale baseLocale : constantBaseLocales) {
if (baseLocale.language.equals(language)
&& baseLocale.region.equals(region)) {
return baseLocale;
}
}
}
// JDK uses deprecated ISO639.1 language codes for he, yi and id
if (!language.isEmpty()) {
language = convertOldISOCodes(language);
}
// Obtain the "interned" BaseLocale from the cache. The returned
// "interned" instance can subsequently be used by the Locale
// instance which guarantees the locale components are properly cased/interned.
return CACHE.get().intern(new BaseLocale(
language.intern(), // guaranteed to be lower-case
LocaleUtils.toTitleString(script).intern(),
region.intern(), // guaranteed to be upper-case
variant.intern()));
}
public static String convertOldISOCodes(String language) {
return switch (language) {
case "he", "iw" -> OLD_ISO_CODES ? "iw" : "he";
case "id", "in" -> OLD_ISO_CODES ? "in" : "id";
case "yi", "ji" -> OLD_ISO_CODES ? "ji" : "yi";
default -> language;
};
}
public String getLanguage() {
return language;
}
public String getScript() {
return script;
}
public String getRegion() {
return region;
}
public String getVariant() {
return variant;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof BaseLocale other) {
return LocaleUtils.caseIgnoreMatch(other.language, language)
&& LocaleUtils.caseIgnoreMatch(other.region, region)
&& LocaleUtils.caseIgnoreMatch(other.script, script)
// variant is case sensitive in JDK!
&& other.variant.equals(variant);
}
return false;
}
@Override
public String toString() {
StringJoiner sj = new StringJoiner(", ");
if (!language.isEmpty()) {
sj.add("language=" + language);
}
if (!script.isEmpty()) {
sj.add("script=" + script);
}
if (!region.isEmpty()) {
sj.add("region=" + region);
}
if (!variant.isEmpty()) {
sj.add("variant=" + variant);
}
return sj.toString();
}
@Override
public int hashCode() {
int h = hash;
if (h == 0) {
int len = language.length();
for (int i = 0; i < len; i++) {
h = 31*h + LocaleUtils.toLower(language.charAt(i));
}
len = script.length();
for (int i = 0; i < len; i++) {
h = 31*h + LocaleUtils.toLower(script.charAt(i));
}
len = region.length();
for (int i = 0; i < len; i++) {
h = 31*h + LocaleUtils.toLower(region.charAt(i));
}
len = variant.length();
for (int i = 0; i < len; i++) {
h = 31*h + variant.charAt(i);
}
if (h != 0) {
hash = h;
}
}
return h;
}
}