diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 88a49a94f9b..0dde196a784 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -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 { - - 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 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 { diff --git a/src/java.base/share/classes/java/util/ResourceBundle.java b/src/java.base/share/classes/java/util/ResourceBundle.java index 81ff9e7b534..d2407c403ae 100644 --- a/src/java.base/share/classes/java/util/ResourceBundle.java +++ b/src/java.base/share/classes/java/util/ResourceBundle.java @@ -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> CANDIDATES_CACHE = ReferencedKeyMap.create(true, ConcurrentHashMap::new); - private static class CandidateListCache extends LocaleObjectCache> { - protected List createObject(BaseLocale base) { - String language = base.getLanguage(); - String script = base.getScript(); - String region = base.getRegion(); - String variant = base.getVariant(); + private static List 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 tmpList = getDefaultList("nb", script, region, variant); + // Insert a locale replacing "nb" with "no" for every list entry with precedence + List 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 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 tmpList = getDefaultList("nb", script, region, variant); - // Insert a locale replacing "nb" with "no" for every list entry with precedence - List 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 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 getDefaultList(String language, String script, String region, String variant) { - List 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 getDefaultList(String language, String script, String region, String variant) { + List 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 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 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; } /** diff --git a/src/java.base/share/classes/sun/util/locale/BaseLocale.java b/src/java.base/share/classes/sun/util/locale/BaseLocale.java index 89cda00a208..7e0fc9a2d34 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -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 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 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 { - - 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(); - } - } } diff --git a/src/java.base/share/classes/sun/util/locale/LocaleObjectCache.java b/src/java.base/share/classes/sun/util/locale/LocaleObjectCache.java deleted file mode 100644 index 8712f8b8ad8..00000000000 --- a/src/java.base/share/classes/sun/util/locale/LocaleObjectCache.java +++ /dev/null @@ -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 { - private final ConcurrentMap> map; - private final ReferenceQueue 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 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 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 entry = new CacheEntry<>(key, value, queue); - CacheEntry oldEntry = map.put(key, entry); - return (oldEntry == null) ? null : oldEntry.get(); - } - - @SuppressWarnings("unchecked") - private void cleanStaleEntries() { - CacheEntry entry; - while ((entry = (CacheEntry)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 extends SoftReference { - private final K key; - - CacheEntry(K key, V value, ReferenceQueue queue) { - super(value, queue); - this.key = key; - } - - K getKey() { - return key; - } - } -}