Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8309622: Re-examine the cache mechanism in BaseLocale #1349

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions src/java.base/share/classes/java/util/Locale.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -47,9 +47,11 @@
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
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;

Expand All @@ -59,7 +61,6 @@
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;
Expand Down Expand Up @@ -912,38 +913,42 @@ static Locale getInstance(String language, String script, String country,
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 Cache.LOCALECACHE.get(baseloc);
return LocaleCache.cache(baseloc);
} else {
LocaleKey key = new LocaleKey(baseloc, extensions);
return Cache.LOCALECACHE.get(key);
return LocaleCache.cache(key);
}
}

private static class Cache extends LocaleObjectCache<Object, Locale> {
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 Cache LOCALECACHE = new Cache();
private static final Function<Object, Locale> LOCALE_CREATOR = new LocaleCache();

private Cache() {
public static Locale cache(Object key) {
return LOCALE_CACHE.computeIfAbsent(key, LOCALE_CREATOR);
}

@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);
public Locale apply(Object key) {
if (key instanceof BaseLocale base) {
return new Locale(base, null);
}
LocaleKey lk = (LocaleKey)key;
return new Locale(lk.base, lk.exts);
}
}

private static final class LocaleKey {

private final BaseLocale base;
private final LocaleExtensions exts;
private final int hash;
Expand Down
189 changes: 94 additions & 95 deletions src/java.base/share/classes/java/util/ResourceBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@
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;
Expand Down Expand Up @@ -2876,123 +2876,122 @@ public List<Locale> getCandidateLocales(String baseName, Locale locale) {
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);
}
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;
}
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;
bokmalList.add(isNorwegianBokmal ? l_nb : l_no);
}
// 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 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<>();
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 (variants != null) {
for (String v : variants) {
list.add(Locale.getInstance(language, script, region, v, 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";
}
}
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";
}
}
}

// 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));
// 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 (!language.isEmpty()) {
list.add(Locale.getInstance(language, "", "", "", null));
if (!region.isEmpty()) {
list.add(Locale.getInstance(language, "", region, "", 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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,21 @@ public final class ReferencedKeyMap<K, V> implements Map<K, V> {
*/
private final ReferenceQueue<K> stale;

/**
* @return a supplier to create a {@code ConcurrentHashMap} appropriate for use in the
* create methods.
* @param <K> the type of keys maintained by the new map
* @param <V> the type of mapped values
*/
public static <K, V> Supplier<Map<ReferenceKey<K>, V>> concurrentHashMapSupplier() {
return new Supplier<>() {
@Override
public Map<ReferenceKey<K>, V> get() {
return new ConcurrentHashMap<>();
}
};
}

/**
* Private constructor.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ public final class ReferencedKeySet<T> extends AbstractSet<T> {
*/
final ReferencedKeyMap<T, ReferenceKey<T>> map;

/**
* @return a supplier to create a {@code ConcurrentHashMap} appropriate for use in the
* create methods.
* @param <E> the type of elements maintained by this set
*/
public static <E> Supplier<Map<ReferenceKey<E>, ReferenceKey<E>>> concurrentHashMapSupplier() {
return ReferencedKeyMap.concurrentHashMapSupplier();
}

/**
* Private constructor.
*
Expand Down
12 changes: 4 additions & 8 deletions src/java.base/share/classes/sun/invoke/util/VerifyAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,10 @@ public static boolean isClassAccessible(Class<?> refc,
Module lookupModule = lookupClass.getModule();
Module refModule = refc.getModule();

// early VM startup case, java.base not defined
if (lookupModule == null) {
assert refModule == null;
// early VM startup case, java.base not defined or
// module system is not fully initialized and exports are not set up
if (lookupModule == null || !jdk.internal.misc.VM.isModuleSystemInited()) {
assert lookupModule == refModule;
return true;
}

Expand All @@ -230,11 +231,6 @@ public static boolean isClassAccessible(Class<?> refc,
if (isModuleAccessible(refc, lookupModule, prevLookupModule))
return true;

// not exported but allow access during VM initialization
// because java.base does not have its exports setup
if (!jdk.internal.misc.VM.isModuleSystemInited())
return true;

// public class not accessible to lookupClass
return false;
}
Expand Down
Loading