8358426: Improve lazy computation in Locale
Reviewed-by: naoto, liach
This commit is contained in:
parent
fcb68ea22d
commit
cd9b1bc820
@ -48,8 +48,8 @@ import java.io.Serializable;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.spi.LocaleNameProvider;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -733,58 +733,24 @@ public final class Locale implements Cloneable, Serializable {
|
||||
* @see #getISOCountries(Locale.IsoCountryCode)
|
||||
* @since 9
|
||||
*/
|
||||
public static enum IsoCountryCode {
|
||||
public enum IsoCountryCode {
|
||||
/**
|
||||
* PART1_ALPHA2 is used to represent the ISO3166-1 alpha-2 two letter
|
||||
* country codes.
|
||||
*/
|
||||
PART1_ALPHA2 {
|
||||
@Override
|
||||
Set<String> createCountryCodeSet() {
|
||||
return Set.of(Locale.getISOCountries());
|
||||
}
|
||||
},
|
||||
PART1_ALPHA2,
|
||||
|
||||
/**
|
||||
*
|
||||
* PART1_ALPHA3 is used to represent the ISO3166-1 alpha-3 three letter
|
||||
* country codes.
|
||||
*/
|
||||
PART1_ALPHA3 {
|
||||
@Override
|
||||
Set<String> createCountryCodeSet() {
|
||||
return LocaleISOData.computeISO3166_1Alpha3Countries();
|
||||
}
|
||||
},
|
||||
PART1_ALPHA3,
|
||||
|
||||
/**
|
||||
* PART3 is used to represent the ISO3166-3 four letter country codes.
|
||||
*/
|
||||
PART3 {
|
||||
@Override
|
||||
Set<String> createCountryCodeSet() {
|
||||
return Set.of(LocaleISOData.ISO3166_3);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Concrete implementation of this method attempts to compute value
|
||||
* for iso3166CodesMap for each IsoCountryCode type key.
|
||||
*/
|
||||
abstract Set<String> createCountryCodeSet();
|
||||
|
||||
/**
|
||||
* Map to hold country codes for each ISO3166 part.
|
||||
*/
|
||||
private static final Map<IsoCountryCode, Set<String>> iso3166CodesMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* This method is called from Locale class to retrieve country code set
|
||||
* for getISOCountries(type)
|
||||
*/
|
||||
static Set<String> retrieveISOCountryCodes(IsoCountryCode type) {
|
||||
return iso3166CodesMap.computeIfAbsent(type, IsoCountryCode::createCountryCodeSet);
|
||||
}
|
||||
PART3
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1004,30 +970,28 @@ public final class Locale implements Cloneable, Serializable {
|
||||
return getInstance(baseloc, extensions);
|
||||
}
|
||||
|
||||
|
||||
static Locale getInstance(BaseLocale baseloc, LocaleExtensions extensions) {
|
||||
if (extensions == null) {
|
||||
Locale locale = CONSTANT_LOCALES.get(baseloc);
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
}
|
||||
return LocaleCache.cache(baseloc);
|
||||
return LOCALE_CACHE.get().computeIfAbsent(baseloc, LOCALE_CREATOR);
|
||||
} else {
|
||||
LocaleKey key = new LocaleKey(baseloc, extensions);
|
||||
return LocaleCache.cache(key);
|
||||
return LOCALE_CACHE.get().computeIfAbsent(key, LOCALE_CREATOR);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LocaleCache implements Function<Object, Locale> {
|
||||
private static final ReferencedKeyMap<Object, Locale> LOCALE_CACHE
|
||||
= ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier());
|
||||
|
||||
private static final Function<Object, Locale> LOCALE_CREATOR = new LocaleCache();
|
||||
|
||||
public static Locale cache(Object key) {
|
||||
return LOCALE_CACHE.computeIfAbsent(key, LOCALE_CREATOR);
|
||||
}
|
||||
private static final Supplier<ReferencedKeyMap<Object, Locale>> LOCALE_CACHE =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
@Override
|
||||
public ReferencedKeyMap<Object, Locale> get() {
|
||||
return ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier());
|
||||
}
|
||||
});
|
||||
|
||||
private static final Function<Object, Locale> LOCALE_CREATOR = new Function<>() {
|
||||
@Override
|
||||
public Locale apply(Object key) {
|
||||
if (key instanceof BaseLocale base) {
|
||||
@ -1036,7 +1000,7 @@ public final class Locale implements Cloneable, Serializable {
|
||||
LocaleKey lk = (LocaleKey)key;
|
||||
return new Locale(lk.base, lk.exts);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static final class LocaleKey {
|
||||
|
||||
@ -1301,12 +1265,8 @@ public final class Locale implements Cloneable, Serializable {
|
||||
* @return An array of ISO 3166 two-letter country codes.
|
||||
*/
|
||||
public static String[] getISOCountries() {
|
||||
if (isoCountries == null) {
|
||||
isoCountries = getISO2Table(LocaleISOData.isoCountryTable);
|
||||
}
|
||||
String[] result = new String[isoCountries.length];
|
||||
System.arraycopy(isoCountries, 0, result, 0, isoCountries.length);
|
||||
return result;
|
||||
String[] countries = LocaleISOData.ISO_3166_1_ALPHA2.get();
|
||||
return Arrays.copyOf(countries, countries.length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1319,7 +1279,11 @@ public final class Locale implements Cloneable, Serializable {
|
||||
*/
|
||||
public static Set<String> getISOCountries(IsoCountryCode type) {
|
||||
Objects.requireNonNull(type);
|
||||
return IsoCountryCode.retrieveISOCountryCodes(type);
|
||||
return switch (type) {
|
||||
case PART1_ALPHA2 -> Set.of(LocaleISOData.ISO_3166_1_ALPHA2.get());
|
||||
case PART1_ALPHA3 -> LocaleISOData.ISO_3166_1_ALPHA3.get();
|
||||
case PART3 -> LocaleISOData.ISO_3166_3.get();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1339,22 +1303,8 @@ public final class Locale implements Cloneable, Serializable {
|
||||
* @return An array of ISO 639 two-letter language codes.
|
||||
*/
|
||||
public static String[] getISOLanguages() {
|
||||
String[] languages = Locale.isoLanguages;
|
||||
if (languages == null) {
|
||||
Locale.isoLanguages = languages = getISO2Table(LocaleISOData.isoLanguageTable);
|
||||
}
|
||||
String[] result = new String[languages.length];
|
||||
System.arraycopy(languages, 0, result, 0, languages.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String[] getISO2Table(String table) {
|
||||
int len = table.length() / 5;
|
||||
String[] isoTable = new String[len];
|
||||
for (int i = 0, j = 0; i < len; i++, j += 5) {
|
||||
isoTable[i] = table.substring(j, j + 2);
|
||||
}
|
||||
return isoTable;
|
||||
String[] languages = LocaleISOData.ISO_639.get();
|
||||
return Arrays.copyOf(languages, languages.length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1683,61 +1633,54 @@ public final class Locale implements Cloneable, Serializable {
|
||||
* @since 1.7
|
||||
*/
|
||||
public String toLanguageTag() {
|
||||
String lTag = this.languageTag;
|
||||
if (lTag != null) {
|
||||
return lTag;
|
||||
}
|
||||
return languageTag.get();
|
||||
}
|
||||
|
||||
private String computeLanguageTag() {
|
||||
LanguageTag tag = LanguageTag.parseLocale(baseLocale, localeExtensions);
|
||||
StringBuilder buf = new StringBuilder();
|
||||
StringBuilder bldr = new StringBuilder();
|
||||
|
||||
String subtag = tag.language();
|
||||
if (!subtag.isEmpty()) {
|
||||
buf.append(LanguageTag.canonicalizeLanguage(subtag));
|
||||
bldr.append(LanguageTag.canonicalizeLanguage(subtag));
|
||||
}
|
||||
|
||||
subtag = tag.script();
|
||||
if (!subtag.isEmpty()) {
|
||||
buf.append(LanguageTag.SEP);
|
||||
buf.append(LanguageTag.canonicalizeScript(subtag));
|
||||
bldr.append(LanguageTag.SEP);
|
||||
bldr.append(LanguageTag.canonicalizeScript(subtag));
|
||||
}
|
||||
|
||||
subtag = tag.region();
|
||||
if (!subtag.isEmpty()) {
|
||||
buf.append(LanguageTag.SEP);
|
||||
buf.append(LanguageTag.canonicalizeRegion(subtag));
|
||||
bldr.append(LanguageTag.SEP);
|
||||
bldr.append(LanguageTag.canonicalizeRegion(subtag));
|
||||
}
|
||||
|
||||
List<String>subtags = tag.variants();
|
||||
for (String s : subtags) {
|
||||
buf.append(LanguageTag.SEP);
|
||||
bldr.append(LanguageTag.SEP);
|
||||
// preserve casing
|
||||
buf.append(s);
|
||||
bldr.append(s);
|
||||
}
|
||||
|
||||
subtags = tag.extensions();
|
||||
for (String s : subtags) {
|
||||
buf.append(LanguageTag.SEP);
|
||||
buf.append(LanguageTag.canonicalizeExtension(s));
|
||||
bldr.append(LanguageTag.SEP);
|
||||
bldr.append(LanguageTag.canonicalizeExtension(s));
|
||||
}
|
||||
|
||||
subtag = tag.privateuse();
|
||||
if (!subtag.isEmpty()) {
|
||||
if (buf.length() > 0) {
|
||||
buf.append(LanguageTag.SEP);
|
||||
if (bldr.length() > 0) {
|
||||
bldr.append(LanguageTag.SEP);
|
||||
}
|
||||
buf.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP);
|
||||
bldr.append(LanguageTag.PRIVATEUSE).append(LanguageTag.SEP);
|
||||
// preserve casing
|
||||
buf.append(subtag);
|
||||
bldr.append(subtag);
|
||||
}
|
||||
|
||||
String langTag = buf.toString();
|
||||
synchronized (this) {
|
||||
if (this.languageTag == null) {
|
||||
this.languageTag = langTag;
|
||||
}
|
||||
}
|
||||
return langTag;
|
||||
return bldr.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1961,7 +1904,7 @@ public final class Locale implements Cloneable, Serializable {
|
||||
return lang;
|
||||
}
|
||||
|
||||
String language3 = getISO3Code(lang, LocaleISOData.isoLanguageTable);
|
||||
String language3 = LocaleISOData.getISO3LangCode(lang);
|
||||
if (language3 == null) {
|
||||
throw new MissingResourceException("Couldn't find 3-letter language code for "
|
||||
+ lang, "FormatData_" + toString(), "ShortLanguage");
|
||||
@ -1983,7 +1926,7 @@ public final class Locale implements Cloneable, Serializable {
|
||||
* three-letter country abbreviation is not available for this locale.
|
||||
*/
|
||||
public String getISO3Country() throws MissingResourceException {
|
||||
String country3 = getISO3Code(baseLocale.getRegion(), LocaleISOData.isoCountryTable);
|
||||
String country3 = LocaleISOData.getISO3CtryCode(baseLocale.getRegion());
|
||||
if (country3 == null) {
|
||||
throw new MissingResourceException("Couldn't find 3-letter country code for "
|
||||
+ baseLocale.getRegion(), "FormatData_" + toString(), "ShortCountry");
|
||||
@ -1991,27 +1934,6 @@ public final class Locale implements Cloneable, Serializable {
|
||||
return country3;
|
||||
}
|
||||
|
||||
private static String getISO3Code(String iso2Code, String table) {
|
||||
int codeLength = iso2Code.length();
|
||||
if (codeLength == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int tableLength = table.length();
|
||||
int index = tableLength;
|
||||
if (codeLength == 2) {
|
||||
char c1 = iso2Code.charAt(0);
|
||||
char c2 = iso2Code.charAt(1);
|
||||
for (index = 0; index < tableLength; index += 5) {
|
||||
if (table.charAt(index) == c1
|
||||
&& table.charAt(index + 1) == c2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index < tableLength ? table.substring(index + 2, index + 5) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a name for the locale's language that is appropriate for display to the
|
||||
* user.
|
||||
@ -2393,7 +2315,13 @@ public final class Locale implements Cloneable, Serializable {
|
||||
private static volatile Locale defaultDisplayLocale;
|
||||
private static volatile Locale defaultFormatLocale;
|
||||
|
||||
private transient volatile String languageTag;
|
||||
private final transient Supplier<String> languageTag =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
@Override
|
||||
public String get() {
|
||||
return computeLanguageTag();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Return an array of the display names of the variant.
|
||||
@ -2587,10 +2515,6 @@ public final class Locale implements Cloneable, Serializable {
|
||||
baseLocale.getRegion(), baseLocale.getVariant(), localeExtensions);
|
||||
}
|
||||
|
||||
private static volatile String[] isoLanguages;
|
||||
|
||||
private static volatile String[] isoCountries;
|
||||
|
||||
private static String convertOldISOCodes(String language) {
|
||||
// we accept both the old and the new ISO codes for the languages whose ISO
|
||||
// codes have changed, but we always store the NEW code, unless the property
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 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
|
||||
@ -25,11 +25,47 @@
|
||||
|
||||
package java.util;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
// Methods and suppliers for producing ISO 639/3166 resources used by Locale.
|
||||
class LocaleISOData {
|
||||
|
||||
static final Supplier<String[]> ISO_639 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
@Override
|
||||
public String[] get() {
|
||||
return getISO2Table(isoLanguageTable);
|
||||
}
|
||||
});
|
||||
|
||||
static final Supplier<String[]> ISO_3166_1_ALPHA2 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
@Override
|
||||
public String[] get() {
|
||||
return getISO2Table(isoCountryTable);
|
||||
}
|
||||
});
|
||||
|
||||
static final Supplier<Set<String>> ISO_3166_1_ALPHA3 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
@Override
|
||||
public Set<String> get() {
|
||||
return computeISO3166_1Alpha3Countries();
|
||||
}
|
||||
});
|
||||
|
||||
static final Supplier<Set<String>> ISO_3166_3 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
@Override
|
||||
public Set<String> get() {
|
||||
return Set.of(ISO3166_3);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The 2- and 3-letter ISO 639 language codes.
|
||||
*/
|
||||
static final String isoLanguageTable =
|
||||
private static final String isoLanguageTable =
|
||||
"aa" + "aar" // Afar
|
||||
+ "ab" + "abk" // Abkhazian
|
||||
+ "ae" + "ave" // Avestan
|
||||
@ -223,7 +259,7 @@ class LocaleISOData {
|
||||
/**
|
||||
* The 2- and 3-letter ISO 3166 country codes.
|
||||
*/
|
||||
static final String isoCountryTable =
|
||||
private static final String isoCountryTable =
|
||||
"AD" + "AND" // Andorra, Principality of
|
||||
+ "AE" + "ARE" // United Arab Emirates
|
||||
+ "AF" + "AFG" // Afghanistan
|
||||
@ -480,18 +516,60 @@ class LocaleISOData {
|
||||
/**
|
||||
* Array to hold country codes for ISO3166-3.
|
||||
*/
|
||||
static final String[] ISO3166_3 = {
|
||||
private static final String[] ISO3166_3 = {
|
||||
"AIDJ", "ANHH", "BQAQ", "BUMM", "BYAA", "CSHH", "CSXX", "CTKI", "DDDE",
|
||||
"DYBJ", "FQHH", "FXFR", "GEHH", "HVBF", "JTUM", "MIUM", "NHVU", "NQAQ",
|
||||
"NTHH", "PCHH", "PUUM", "PZPA", "RHZW", "SKIN", "SUHH", "TPTL", "VDVN",
|
||||
"WKUM", "YDYE", "YUCS", "ZRCD"
|
||||
};
|
||||
|
||||
static String getISO3LangCode(String language) {
|
||||
return getISO3Code(language, isoLanguageTable);
|
||||
}
|
||||
|
||||
static String getISO3CtryCode(String country) {
|
||||
return getISO3Code(country, isoCountryTable);
|
||||
}
|
||||
|
||||
private static String getISO3Code(String iso2Code, String table) {
|
||||
int codeLength = iso2Code.length();
|
||||
if (codeLength == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int tableLength = table.length();
|
||||
int index = tableLength;
|
||||
if (codeLength == 2) {
|
||||
char c1 = iso2Code.charAt(0);
|
||||
char c2 = iso2Code.charAt(1);
|
||||
for (index = 0; index < tableLength; index += 5) {
|
||||
if (table.charAt(index) == c1
|
||||
&& table.charAt(index + 1) == c2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index < tableLength ? table.substring(index + 2, index + 5) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method computes an array of alpha-2 codes from either ISO639 or
|
||||
* ISO3166.
|
||||
*/
|
||||
private static String[] getISO2Table(String table) {
|
||||
int len = table.length() / 5;
|
||||
String[] isoTable = new String[len];
|
||||
for (int i = 0, j = 0; i < len; i++, j += 5) {
|
||||
isoTable[i] = table.substring(j, j + 2);
|
||||
}
|
||||
return isoTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method computes a set of ISO3166-1 alpha-3 country codes from
|
||||
* existing isoCountryTable.
|
||||
*/
|
||||
static Set<String> computeISO3166_1Alpha3Countries() {
|
||||
private static Set<String> computeISO3166_1Alpha3Countries() {
|
||||
int tableLength = isoCountryTable.length();
|
||||
String[] isoTable = new String[tableLength / 5];
|
||||
for (int i = 0, index = 0; index < tableLength; i++, index += 5) {
|
||||
@ -500,6 +578,5 @@ class LocaleISOData {
|
||||
return Set.of(isoTable);
|
||||
}
|
||||
|
||||
private LocaleISOData() {
|
||||
}
|
||||
private LocaleISOData() {}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -38,6 +38,7 @@ import jdk.internal.util.StaticProperty;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class BaseLocale {
|
||||
|
||||
@ -90,6 +91,15 @@ public final class BaseLocale {
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@ -164,11 +174,7 @@ public final class BaseLocale {
|
||||
// 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.
|
||||
class InterningCache { // TODO: StableValue
|
||||
private static final ReferencedKeySet<BaseLocale> CACHE =
|
||||
ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier());
|
||||
}
|
||||
return InterningCache.CACHE.intern(new BaseLocale(
|
||||
return CACHE.get().intern(new BaseLocale(
|
||||
language.intern(), // guaranteed to be lower-case
|
||||
LocaleUtils.toTitleString(script).intern(),
|
||||
region.intern(), // guaranteed to be upper-case
|
||||
|
Loading…
x
Reference in New Issue
Block a user