8309622: Re-examine the cache mechanism in BaseLocale
Reviewed-by: dfuchs, rriggs
This commit is contained in:
parent
6f8d351e86
commit
f615ac4bdf
src/java.base/share/classes
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2024, 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
|
||||
@ -51,6 +51,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.spi.LocaleNameProvider;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.util.ReferencedKeyMap;
|
||||
import jdk.internal.util.StaticProperty;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
@ -60,7 +61,6 @@ import sun.util.locale.InternalLocaleBuilder;
|
||||
import sun.util.locale.LanguageTag;
|
||||
import sun.util.locale.LocaleExtensions;
|
||||
import sun.util.locale.LocaleMatcher;
|
||||
import sun.util.locale.LocaleObjectCache;
|
||||
import sun.util.locale.LocaleSyntaxException;
|
||||
import sun.util.locale.LocaleUtils;
|
||||
import sun.util.locale.ParseStatus;
|
||||
@ -987,29 +987,20 @@ public final class Locale implements Cloneable, Serializable {
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
}
|
||||
return Cache.LOCALECACHE.get(baseloc);
|
||||
return LOCALE_CACHE.computeIfAbsent(baseloc, Locale::createLocale);
|
||||
} else {
|
||||
LocaleKey key = new LocaleKey(baseloc, extensions);
|
||||
return Cache.LOCALECACHE.get(key);
|
||||
return LOCALE_CACHE.computeIfAbsent(key, Locale::createLocale);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cache extends LocaleObjectCache<Object, Locale> {
|
||||
|
||||
private static final Cache LOCALECACHE = new Cache();
|
||||
|
||||
private Cache() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Locale createObject(Object key) {
|
||||
if (key instanceof BaseLocale) {
|
||||
return new Locale((BaseLocale)key, null);
|
||||
} else {
|
||||
LocaleKey lk = (LocaleKey)key;
|
||||
return new Locale(lk.base, lk.exts);
|
||||
}
|
||||
}
|
||||
private static final ReferencedKeyMap<Object, Locale> LOCALE_CACHE = ReferencedKeyMap.create(true, ConcurrentHashMap::new);
|
||||
private static Locale createLocale(Object key) {
|
||||
return switch (key) {
|
||||
case BaseLocale base -> new Locale(base, null);
|
||||
case LocaleKey lk -> new Locale(lk.base, lk.exts);
|
||||
default -> throw new InternalError("should not happen");
|
||||
};
|
||||
}
|
||||
|
||||
private static final class LocaleKey {
|
||||
|
@ -69,9 +69,9 @@ import jdk.internal.access.JavaUtilResourceBundleAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.reflect.CallerSensitive;
|
||||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.util.ReferencedKeyMap;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
import sun.util.locale.BaseLocale;
|
||||
import sun.util.locale.LocaleObjectCache;
|
||||
import sun.util.resources.Bundles;
|
||||
|
||||
import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
|
||||
@ -2867,123 +2867,122 @@ public abstract class ResourceBundle {
|
||||
if (baseName == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
return new ArrayList<>(CANDIDATES_CACHE.get(locale.getBaseLocale()));
|
||||
return new ArrayList<>(CANDIDATES_CACHE.computeIfAbsent(locale.getBaseLocale(),
|
||||
Control::createCandidateList));
|
||||
}
|
||||
|
||||
private static final CandidateListCache CANDIDATES_CACHE = new CandidateListCache();
|
||||
private static final ReferencedKeyMap<BaseLocale, List<Locale>> CANDIDATES_CACHE = ReferencedKeyMap.create(true, ConcurrentHashMap::new);
|
||||
|
||||
private static class CandidateListCache extends LocaleObjectCache<BaseLocale, List<Locale>> {
|
||||
protected List<Locale> createObject(BaseLocale base) {
|
||||
String language = base.getLanguage();
|
||||
String script = base.getScript();
|
||||
String region = base.getRegion();
|
||||
String variant = base.getVariant();
|
||||
private static List<Locale> createCandidateList(BaseLocale base) {
|
||||
String language = base.getLanguage();
|
||||
String script = base.getScript();
|
||||
String region = base.getRegion();
|
||||
String variant = base.getVariant();
|
||||
|
||||
// Special handling for Norwegian
|
||||
boolean isNorwegianBokmal = false;
|
||||
boolean isNorwegianNynorsk = false;
|
||||
if (language.equals("no")) {
|
||||
if (region.equals("NO") && variant.equals("NY")) {
|
||||
variant = "";
|
||||
isNorwegianNynorsk = true;
|
||||
} else {
|
||||
isNorwegianBokmal = true;
|
||||
// Special handling for Norwegian
|
||||
boolean isNorwegianBokmal = false;
|
||||
boolean isNorwegianNynorsk = false;
|
||||
if (language.equals("no")) {
|
||||
if (region.equals("NO") && variant.equals("NY")) {
|
||||
variant = "";
|
||||
isNorwegianNynorsk = true;
|
||||
} else {
|
||||
isNorwegianBokmal = true;
|
||||
}
|
||||
}
|
||||
if (language.equals("nb") || isNorwegianBokmal) {
|
||||
List<Locale> tmpList = getDefaultList("nb", script, region, variant);
|
||||
// Insert a locale replacing "nb" with "no" for every list entry with precedence
|
||||
List<Locale> bokmalList = new ArrayList<>();
|
||||
for (Locale l_nb : tmpList) {
|
||||
var isRoot = l_nb.getLanguage().isEmpty();
|
||||
var l_no = Locale.getInstance(isRoot ? "" : "no",
|
||||
l_nb.getScript(), l_nb.getCountry(), l_nb.getVariant(), null);
|
||||
bokmalList.add(isNorwegianBokmal ? l_no : l_nb);
|
||||
if (isRoot) {
|
||||
break;
|
||||
}
|
||||
bokmalList.add(isNorwegianBokmal ? l_nb : l_no);
|
||||
}
|
||||
return bokmalList;
|
||||
} else if (language.equals("nn") || isNorwegianNynorsk) {
|
||||
// Insert no_NO_NY, no_NO, no after nn
|
||||
List<Locale> nynorskList = getDefaultList("nn", script, region, variant);
|
||||
int idx = nynorskList.size() - 1;
|
||||
nynorskList.add(idx++, Locale.getInstance("no", "NO", "NY"));
|
||||
nynorskList.add(idx++, Locale.getInstance("no", "NO", ""));
|
||||
nynorskList.add(idx++, Locale.getInstance("no", "", ""));
|
||||
return nynorskList;
|
||||
}
|
||||
// Special handling for Chinese
|
||||
else if (language.equals("zh")) {
|
||||
if (script.isEmpty() && !region.isEmpty()) {
|
||||
// Supply script for users who want to use zh_Hans/zh_Hant
|
||||
// as bundle names (recommended for Java7+)
|
||||
switch (region) {
|
||||
case "TW", "HK", "MO" -> script = "Hant";
|
||||
case "CN", "SG" -> script = "Hans";
|
||||
}
|
||||
}
|
||||
if (language.equals("nb") || isNorwegianBokmal) {
|
||||
List<Locale> tmpList = getDefaultList("nb", script, region, variant);
|
||||
// Insert a locale replacing "nb" with "no" for every list entry with precedence
|
||||
List<Locale> bokmalList = new ArrayList<>();
|
||||
for (Locale l_nb : tmpList) {
|
||||
var isRoot = l_nb.getLanguage().isEmpty();
|
||||
var l_no = Locale.getInstance(isRoot ? "" : "no",
|
||||
l_nb.getScript(), l_nb.getCountry(), l_nb.getVariant(), null);
|
||||
bokmalList.add(isNorwegianBokmal ? l_no : l_nb);
|
||||
if (isRoot) {
|
||||
break;
|
||||
}
|
||||
bokmalList.add(isNorwegianBokmal ? l_nb : l_no);
|
||||
}
|
||||
return bokmalList;
|
||||
} else if (language.equals("nn") || isNorwegianNynorsk) {
|
||||
// Insert no_NO_NY, no_NO, no after nn
|
||||
List<Locale> nynorskList = getDefaultList("nn", script, region, variant);
|
||||
int idx = nynorskList.size() - 1;
|
||||
nynorskList.add(idx++, Locale.getInstance("no", "NO", "NY"));
|
||||
nynorskList.add(idx++, Locale.getInstance("no", "NO", ""));
|
||||
nynorskList.add(idx++, Locale.getInstance("no", "", ""));
|
||||
return nynorskList;
|
||||
}
|
||||
// Special handling for Chinese
|
||||
else if (language.equals("zh")) {
|
||||
if (script.isEmpty() && !region.isEmpty()) {
|
||||
// Supply script for users who want to use zh_Hans/zh_Hant
|
||||
// as bundle names (recommended for Java7+)
|
||||
switch (region) {
|
||||
case "TW", "HK", "MO" -> script = "Hant";
|
||||
case "CN", "SG" -> script = "Hans";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getDefaultList(language, script, region, variant);
|
||||
}
|
||||
|
||||
private static List<Locale> getDefaultList(String language, String script, String region, String variant) {
|
||||
List<String> variants = null;
|
||||
return getDefaultList(language, script, region, variant);
|
||||
}
|
||||
|
||||
if (!variant.isEmpty()) {
|
||||
variants = new ArrayList<>();
|
||||
int idx = variant.length();
|
||||
while (idx != -1) {
|
||||
variants.add(variant.substring(0, idx));
|
||||
idx = variant.lastIndexOf('_', --idx);
|
||||
private static List<Locale> getDefaultList(String language, String script, String region, String variant) {
|
||||
List<String> variants = null;
|
||||
|
||||
if (!variant.isEmpty()) {
|
||||
variants = new ArrayList<>();
|
||||
int idx = variant.length();
|
||||
while (idx != -1) {
|
||||
variants.add(variant.substring(0, idx));
|
||||
idx = variant.lastIndexOf('_', --idx);
|
||||
}
|
||||
}
|
||||
|
||||
List<Locale> list = new ArrayList<>();
|
||||
|
||||
if (variants != null) {
|
||||
for (String v : variants) {
|
||||
list.add(Locale.getInstance(language, script, region, v, null));
|
||||
}
|
||||
}
|
||||
if (!region.isEmpty()) {
|
||||
list.add(Locale.getInstance(language, script, region, "", null));
|
||||
}
|
||||
if (!script.isEmpty()) {
|
||||
list.add(Locale.getInstance(language, script, "", "", null));
|
||||
// Special handling for Chinese
|
||||
if (language.equals("zh")) {
|
||||
if (region.isEmpty()) {
|
||||
// Supply region(country) for users who still package Chinese
|
||||
// bundles using old convention.
|
||||
switch (script) {
|
||||
case "Hans" -> region = "CN";
|
||||
case "Hant" -> region = "TW";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Locale> list = new ArrayList<>();
|
||||
|
||||
// With script, after truncating variant, region and script,
|
||||
// start over without script.
|
||||
if (variants != null) {
|
||||
for (String v : variants) {
|
||||
list.add(Locale.getInstance(language, script, region, v, null));
|
||||
list.add(Locale.getInstance(language, "", region, v, null));
|
||||
}
|
||||
}
|
||||
if (!region.isEmpty()) {
|
||||
list.add(Locale.getInstance(language, script, region, "", null));
|
||||
list.add(Locale.getInstance(language, "", region, "", null));
|
||||
}
|
||||
if (!script.isEmpty()) {
|
||||
list.add(Locale.getInstance(language, script, "", "", null));
|
||||
// Special handling for Chinese
|
||||
if (language.equals("zh")) {
|
||||
if (region.isEmpty()) {
|
||||
// Supply region(country) for users who still package Chinese
|
||||
// bundles using old convention.
|
||||
switch (script) {
|
||||
case "Hans" -> region = "CN";
|
||||
case "Hant" -> region = "TW";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With script, after truncating variant, region and script,
|
||||
// start over without script.
|
||||
if (variants != null) {
|
||||
for (String v : variants) {
|
||||
list.add(Locale.getInstance(language, "", region, v, null));
|
||||
}
|
||||
}
|
||||
if (!region.isEmpty()) {
|
||||
list.add(Locale.getInstance(language, "", region, "", null));
|
||||
}
|
||||
}
|
||||
if (!language.isEmpty()) {
|
||||
list.add(Locale.getInstance(language, "", "", "", null));
|
||||
}
|
||||
// Add root locale at the end
|
||||
list.add(Locale.ROOT);
|
||||
|
||||
return list;
|
||||
}
|
||||
if (!language.isEmpty()) {
|
||||
list.add(Locale.getInstance(language, "", "", "", null));
|
||||
}
|
||||
// Add root locale at the end
|
||||
list.add(Locale.ROOT);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2010, 2024, 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
|
||||
@ -33,34 +33,35 @@
|
||||
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.lang.ref.SoftReference;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class BaseLocale {
|
||||
|
||||
public static @Stable BaseLocale[] constantBaseLocales;
|
||||
public static final byte ENGLISH = 0,
|
||||
FRENCH = 1,
|
||||
GERMAN = 2,
|
||||
ITALIAN = 3,
|
||||
JAPANESE = 4,
|
||||
KOREAN = 5,
|
||||
CHINESE = 6,
|
||||
SIMPLIFIED_CHINESE = 7,
|
||||
TRADITIONAL_CHINESE = 8,
|
||||
FRANCE = 9,
|
||||
GERMANY = 10,
|
||||
ITALY = 11,
|
||||
JAPAN = 12,
|
||||
KOREA = 13,
|
||||
UK = 14,
|
||||
US = 15,
|
||||
CANADA = 16,
|
||||
CANADA_FRENCH = 17,
|
||||
ROOT = 18,
|
||||
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);
|
||||
@ -90,6 +91,10 @@ public final class BaseLocale {
|
||||
}
|
||||
}
|
||||
|
||||
// Interned BaseLocale cache
|
||||
private static final ReferencedKeySet<BaseLocale> CACHE =
|
||||
ReferencedKeySet.create(true, ConcurrentHashMap::new);
|
||||
|
||||
public static final String SEP = "_";
|
||||
|
||||
private final String language;
|
||||
@ -107,27 +112,17 @@ public final class BaseLocale {
|
||||
private static final boolean OLD_ISO_CODES = StaticProperty.javaLocaleUseOldISOCodes()
|
||||
.equalsIgnoreCase("true");
|
||||
|
||||
// This method must be called with normalize = false only when creating the
|
||||
// Locale.* constants and non-normalized BaseLocale$Keys used for lookup.
|
||||
private BaseLocale(String language, String script, String region, String variant,
|
||||
boolean normalize) {
|
||||
if (normalize) {
|
||||
this.language = LocaleUtils.toLowerString(language).intern();
|
||||
this.script = LocaleUtils.toTitleString(script).intern();
|
||||
this.region = LocaleUtils.toUpperString(region).intern();
|
||||
this.variant = variant.intern();
|
||||
} else {
|
||||
this.language = language;
|
||||
this.script = script;
|
||||
this.region = region;
|
||||
this.variant = variant;
|
||||
}
|
||||
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, "", false);
|
||||
return new BaseLocale(language, "", region, "");
|
||||
}
|
||||
|
||||
public static BaseLocale getInstance(String language, String script,
|
||||
@ -153,8 +148,8 @@ public final class BaseLocale {
|
||||
// Check for constant base locales first
|
||||
if (script.isEmpty() && variant.isEmpty()) {
|
||||
for (BaseLocale baseLocale : constantBaseLocales) {
|
||||
if (baseLocale.getLanguage().equals(language)
|
||||
&& baseLocale.getRegion().equals(region)) {
|
||||
if (baseLocale.language.equals(language)
|
||||
&& baseLocale.region.equals(region)) {
|
||||
return baseLocale;
|
||||
}
|
||||
}
|
||||
@ -165,8 +160,15 @@ public final class BaseLocale {
|
||||
language = convertOldISOCodes(language);
|
||||
}
|
||||
|
||||
Key key = new Key(language, script, region, variant, false);
|
||||
return Cache.CACHE.get(key);
|
||||
// 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.intern(new BaseLocale(language, script, region, variant),
|
||||
(b) -> new BaseLocale(
|
||||
LocaleUtils.toLowerString(b.language).intern(),
|
||||
LocaleUtils.toTitleString(b.script).intern(),
|
||||
LocaleUtils.toUpperString(b.region).intern(),
|
||||
b.variant.intern()));
|
||||
}
|
||||
|
||||
public static String convertOldISOCodes(String language) {
|
||||
@ -199,14 +201,14 @@ public final class BaseLocale {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof BaseLocale)) {
|
||||
return false;
|
||||
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);
|
||||
}
|
||||
BaseLocale other = (BaseLocale)obj;
|
||||
return language == other.language
|
||||
&& script == other.script
|
||||
&& region == other.region
|
||||
&& variant == other.variant;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -231,128 +233,26 @@ public final class BaseLocale {
|
||||
public int hashCode() {
|
||||
int h = hash;
|
||||
if (h == 0) {
|
||||
// Generating a hash value from language, script, region and variant
|
||||
h = language.hashCode();
|
||||
h = 31 * h + script.hashCode();
|
||||
h = 31 * h + region.hashCode();
|
||||
h = 31 * h + variant.hashCode();
|
||||
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;
|
||||
}
|
||||
|
||||
private static final class Key {
|
||||
/**
|
||||
* Keep a SoftReference to the Key data if normalized (actually used
|
||||
* as a cache key) and not initialized via the constant creation path.
|
||||
*
|
||||
* This allows us to avoid creating SoftReferences on lookup Keys
|
||||
* (which are short-lived) and for Locales created via
|
||||
* Locale#createConstant.
|
||||
*/
|
||||
private final SoftReference<BaseLocale> holderRef;
|
||||
private final BaseLocale holder;
|
||||
|
||||
private final boolean normalized;
|
||||
private final int hash;
|
||||
|
||||
private Key(String language, String script, String region,
|
||||
String variant, boolean normalize) {
|
||||
BaseLocale locale = new BaseLocale(language, script, region, variant, normalize);
|
||||
this.normalized = normalize;
|
||||
if (normalized) {
|
||||
this.holderRef = new SoftReference<>(locale);
|
||||
this.holder = null;
|
||||
} else {
|
||||
this.holderRef = null;
|
||||
this.holder = locale;
|
||||
}
|
||||
this.hash = hashCode(locale);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
private int hashCode(BaseLocale locale) {
|
||||
int h = 0;
|
||||
String lang = locale.getLanguage();
|
||||
int len = lang.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
h = 31*h + LocaleUtils.toLower(lang.charAt(i));
|
||||
}
|
||||
String scrt = locale.getScript();
|
||||
len = scrt.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
h = 31*h + LocaleUtils.toLower(scrt.charAt(i));
|
||||
}
|
||||
String regn = locale.getRegion();
|
||||
len = regn.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
h = 31*h + LocaleUtils.toLower(regn.charAt(i));
|
||||
}
|
||||
String vart = locale.getVariant();
|
||||
len = vart.length();
|
||||
for (int i = 0; i < len; i++) {
|
||||
h = 31*h + vart.charAt(i);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
private BaseLocale getBaseLocale() {
|
||||
return (holder == null) ? holderRef.get() : holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof Key && this.hash == ((Key)obj).hash) {
|
||||
BaseLocale other = ((Key) obj).getBaseLocale();
|
||||
BaseLocale locale = this.getBaseLocale();
|
||||
if (other != null && locale != null
|
||||
&& LocaleUtils.caseIgnoreMatch(other.getLanguage(), locale.getLanguage())
|
||||
&& LocaleUtils.caseIgnoreMatch(other.getScript(), locale.getScript())
|
||||
&& LocaleUtils.caseIgnoreMatch(other.getRegion(), locale.getRegion())
|
||||
// variant is case sensitive in JDK!
|
||||
&& other.getVariant().equals(locale.getVariant())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Key normalize(Key key) {
|
||||
if (key.normalized) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// Only normalized keys may be softly referencing the data holder
|
||||
assert (key.holder != null && key.holderRef == null);
|
||||
BaseLocale locale = key.holder;
|
||||
return new Key(locale.getLanguage(), locale.getScript(),
|
||||
locale.getRegion(), locale.getVariant(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cache extends LocaleObjectCache<Key, BaseLocale> {
|
||||
|
||||
private static final Cache CACHE = new Cache();
|
||||
|
||||
public Cache() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Key normalizeKey(Key key) {
|
||||
return Key.normalize(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseLocale createObject(Key key) {
|
||||
return Key.normalize(key).getBaseLocale();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2023, 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 java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public abstract class LocaleObjectCache<K, V> {
|
||||
private final ConcurrentMap<K, CacheEntry<K, V>> map;
|
||||
private final ReferenceQueue<V> queue = new ReferenceQueue<>();
|
||||
|
||||
public LocaleObjectCache() {
|
||||
this(16, 0.75f, 16);
|
||||
}
|
||||
|
||||
public LocaleObjectCache(int initialCapacity, float loadFactor, int concurrencyLevel) {
|
||||
map = new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel);
|
||||
}
|
||||
|
||||
public V get(K key) {
|
||||
V value = null;
|
||||
|
||||
cleanStaleEntries();
|
||||
CacheEntry<K, V> entry = map.get(key);
|
||||
if (entry != null) {
|
||||
value = entry.get();
|
||||
}
|
||||
if (value == null) {
|
||||
key = normalizeKey(key);
|
||||
V newVal = createObject(key);
|
||||
if (key == null || newVal == null) {
|
||||
// subclass must return non-null key/value object
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheEntry<K, V> newEntry = new CacheEntry<>(key, newVal, queue);
|
||||
entry = map.putIfAbsent(key, newEntry);
|
||||
if (entry == null) {
|
||||
value = newVal;
|
||||
} else {
|
||||
value = entry.get();
|
||||
if (value == null) {
|
||||
map.put(key, newEntry);
|
||||
value = newVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected V put(K key, V value) {
|
||||
CacheEntry<K, V> entry = new CacheEntry<>(key, value, queue);
|
||||
CacheEntry<K, V> oldEntry = map.put(key, entry);
|
||||
return (oldEntry == null) ? null : oldEntry.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void cleanStaleEntries() {
|
||||
CacheEntry<K, V> entry;
|
||||
while ((entry = (CacheEntry<K, V>)queue.poll()) != null) {
|
||||
map.remove(entry.getKey(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract V createObject(K key);
|
||||
|
||||
protected K normalizeKey(K key) {
|
||||
return key;
|
||||
}
|
||||
|
||||
private static class CacheEntry<K, V> extends SoftReference<V> {
|
||||
private final K key;
|
||||
|
||||
CacheEntry(K key, V value, ReferenceQueue<V> queue) {
|
||||
super(value, queue);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
K getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user