|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
/* |
|
* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved |
|
* (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved |
|
* |
|
* The original version of this source code and documentation |
|
* is copyrighted and owned by Taligent, Inc., a wholly-owned |
|
* subsidiary of IBM. These materials are provided under terms |
|
* of a License Agreement between Taligent and Sun. This technology |
|
* is protected by multiple US and International patents. |
|
* |
|
* This notice and attribution to Taligent may not be removed. |
|
* Taligent is a registered trademark of Taligent, Inc. |
|
* |
|
*/ |
|
|
|
package sun.util.resources; |
|
|
|
import java.lang.ref.ReferenceQueue; |
|
import java.lang.ref.SoftReference; |
|
import java.security.AccessController; |
|
import java.security.PrivilegedAction; |
|
import java.util.Enumeration; |
|
import java.util.Iterator; |
|
import java.util.List; |
|
import java.util.Locale; |
|
import java.util.MissingResourceException; |
|
import java.util.Objects; |
|
import java.util.ResourceBundle; |
|
import java.util.ServiceConfigurationError; |
|
import java.util.ServiceLoader; |
|
import java.util.concurrent.ConcurrentHashMap; |
|
import java.util.concurrent.ConcurrentMap; |
|
import java.util.spi.ResourceBundleProvider; |
|
import jdk.internal.access.JavaUtilResourceBundleAccess; |
|
import jdk.internal.access.SharedSecrets; |
|
|
|
|
|
*/ |
|
public abstract class Bundles { |
|
|
|
|
|
private static final int INITIAL_CACHE_SIZE = 32; |
|
|
|
|
|
private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() { |
|
@Override |
|
public Enumeration<String> getKeys() { return null; } |
|
@Override |
|
protected Object handleGetObject(String key) { return null; } |
|
@Override |
|
public String toString() { return "NONEXISTENT_BUNDLE"; } |
|
}; |
|
|
|
private static final JavaUtilResourceBundleAccess bundleAccess |
|
= SharedSecrets.getJavaUtilResourceBundleAccess(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final ConcurrentMap<CacheKey, BundleReference> cacheList |
|
= new ConcurrentHashMap<>(INITIAL_CACHE_SIZE); |
|
|
|
|
|
|
|
*/ |
|
private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); |
|
|
|
private Bundles() { |
|
} |
|
|
|
public static ResourceBundle of(String baseName, Locale locale, Strategy strategy) { |
|
return loadBundleOf(baseName, locale, strategy); |
|
} |
|
|
|
private static ResourceBundle loadBundleOf(String baseName, |
|
Locale targetLocale, |
|
Strategy strategy) { |
|
Objects.requireNonNull(baseName); |
|
Objects.requireNonNull(targetLocale); |
|
Objects.requireNonNull(strategy); |
|
|
|
CacheKey cacheKey = new CacheKey(baseName, targetLocale); |
|
|
|
ResourceBundle bundle = null; |
|
|
|
|
|
BundleReference bundleRef = cacheList.get(cacheKey); |
|
if (bundleRef != null) { |
|
bundle = bundleRef.get(); |
|
} |
|
|
|
// If this bundle and all of its parents are valid, |
|
|
|
if (isValidBundle(bundle)) { |
|
return bundle; |
|
} |
|
|
|
// Get the providers for loading the "leaf" bundle (i.e., bundle for |
|
// targetLocale). If no providers are required for the bundle, |
|
|
|
Class<? extends ResourceBundleProvider> type |
|
= strategy.getResourceBundleProviderType(baseName, targetLocale); |
|
if (type != null) { |
|
@SuppressWarnings("unchecked") |
|
ServiceLoader<ResourceBundleProvider> providers |
|
= (ServiceLoader<ResourceBundleProvider>) ServiceLoader.loadInstalled(type); |
|
cacheKey.setProviders(providers); |
|
} |
|
|
|
List<Locale> candidateLocales = strategy.getCandidateLocales(baseName, targetLocale); |
|
bundle = findBundleOf(cacheKey, strategy, baseName, candidateLocales, 0); |
|
if (bundle == null) { |
|
throwMissingResourceException(baseName, targetLocale, cacheKey.getCause()); |
|
} |
|
return bundle; |
|
} |
|
|
|
private static ResourceBundle findBundleOf(CacheKey cacheKey, |
|
Strategy strategy, |
|
String baseName, |
|
List<Locale> candidateLocales, |
|
int index) { |
|
ResourceBundle parent = null; |
|
Locale targetLocale = candidateLocales.get(index); |
|
if (index != candidateLocales.size() - 1) { |
|
parent = findBundleOf(cacheKey, strategy, baseName, candidateLocales, index + 1); |
|
} |
|
|
|
// Before we do the real loading work, see whether we need to |
|
// do some housekeeping: If resource bundles have been nulled out, |
|
|
|
cleanupCache(); |
|
|
|
|
|
cacheKey.setLocale(targetLocale); |
|
ResourceBundle bundle = findBundleInCache(cacheKey); |
|
if (bundle != null) { |
|
if (bundle == NONEXISTENT_BUNDLE) { |
|
return parent; |
|
} |
|
if (bundleAccess.getParent(bundle) == parent) { |
|
return bundle; |
|
} |
|
|
|
BundleReference bundleRef = cacheList.get(cacheKey); |
|
if (bundleRef != null && bundleRef.get() == bundle) { |
|
cacheList.remove(cacheKey, bundleRef); |
|
} |
|
} |
|
|
|
// Determine if providers should be used for loading the bundle. |
|
// An assumption here is that if the leaf bundle of a look-up path is |
|
// in java.base, all bundles of the path are in java.base. |
|
// (e.g., en_US of path en_US -> en -> root is in java.base and the rest |
|
// are in java.base as well) |
|
|
|
ServiceLoader<ResourceBundleProvider> providers = cacheKey.getProviders(); |
|
if (providers != null) { |
|
if (strategy.getResourceBundleProviderType(baseName, targetLocale) == null) { |
|
providers = null; |
|
} |
|
} |
|
|
|
CacheKey constKey = (CacheKey) cacheKey.clone(); |
|
try { |
|
if (providers != null) { |
|
bundle = loadBundleFromProviders(baseName, targetLocale, providers, cacheKey); |
|
} else { |
|
try { |
|
String bundleName = strategy.toBundleName(baseName, targetLocale); |
|
Class<?> c = Class.forName(Bundles.class.getModule(), bundleName); |
|
if (c != null && ResourceBundle.class.isAssignableFrom(c)) { |
|
@SuppressWarnings("unchecked") |
|
Class<ResourceBundle> bundleClass = (Class<ResourceBundle>) c; |
|
bundle = bundleAccess.newResourceBundle(bundleClass); |
|
} |
|
if (bundle == null) { |
|
var otherBundleName = toOtherBundleName(baseName, bundleName, targetLocale); |
|
if (!bundleName.equals(otherBundleName)) { |
|
c = Class.forName(Bundles.class.getModule(), otherBundleName); |
|
if (c != null && ResourceBundle.class.isAssignableFrom(c)) { |
|
@SuppressWarnings("unchecked") |
|
Class<ResourceBundle> bundleClass = (Class<ResourceBundle>) c; |
|
bundle = bundleAccess.newResourceBundle(bundleClass); |
|
} |
|
} |
|
} |
|
} catch (Exception e) { |
|
cacheKey.setCause(e); |
|
} |
|
} |
|
} finally { |
|
if (constKey.getCause() instanceof InterruptedException) { |
|
Thread.currentThread().interrupt(); |
|
} |
|
} |
|
|
|
if (bundle == null) { |
|
// Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle |
|
|
|
putBundleInCache(cacheKey, NONEXISTENT_BUNDLE); |
|
return parent; |
|
} |
|
|
|
if (parent != null && bundleAccess.getParent(bundle) == null) { |
|
bundleAccess.setParent(bundle, parent); |
|
} |
|
bundleAccess.setLocale(bundle, targetLocale); |
|
bundleAccess.setName(bundle, baseName); |
|
bundle = putBundleInCache(cacheKey, bundle); |
|
return bundle; |
|
} |
|
|
|
private static void cleanupCache() { |
|
Object ref; |
|
while ((ref = referenceQueue.poll()) != null) { |
|
cacheList.remove(((CacheKeyReference)ref).getCacheKey()); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
@SuppressWarnings("removal") |
|
private static ResourceBundle loadBundleFromProviders(String baseName, |
|
Locale locale, |
|
ServiceLoader<ResourceBundleProvider> providers, |
|
CacheKey cacheKey) |
|
{ |
|
return AccessController.doPrivileged( |
|
new PrivilegedAction<>() { |
|
public ResourceBundle run() { |
|
for (Iterator<ResourceBundleProvider> itr = providers.iterator(); itr.hasNext(); ) { |
|
try { |
|
ResourceBundleProvider provider = itr.next(); |
|
ResourceBundle bundle = provider.getBundle(baseName, locale); |
|
if (bundle != null) { |
|
return bundle; |
|
} |
|
} catch (ServiceConfigurationError | SecurityException e) { |
|
if (cacheKey != null) { |
|
cacheKey.setCause(e); |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
}); |
|
|
|
} |
|
|
|
private static boolean isValidBundle(ResourceBundle bundle) { |
|
return bundle != null && bundle != NONEXISTENT_BUNDLE; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static void throwMissingResourceException(String baseName, |
|
Locale locale, |
|
Throwable cause) { |
|
// If the cause is a MissingResourceException, avoid creating |
|
|
|
if (cause instanceof MissingResourceException) { |
|
cause = null; |
|
} |
|
MissingResourceException e; |
|
e = new MissingResourceException("Can't find bundle for base name " |
|
+ baseName + ", locale " + locale, |
|
baseName + "_" + locale, |
|
""); |
|
e.initCause(cause); |
|
throw e; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static ResourceBundle findBundleInCache(CacheKey cacheKey) { |
|
BundleReference bundleRef = cacheList.get(cacheKey); |
|
if (bundleRef == null) { |
|
return null; |
|
} |
|
return bundleRef.get(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static ResourceBundle putBundleInCache(CacheKey cacheKey, |
|
ResourceBundle bundle) { |
|
CacheKey key = (CacheKey) cacheKey.clone(); |
|
BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key); |
|
|
|
|
|
BundleReference result = cacheList.putIfAbsent(key, bundleRef); |
|
|
|
// If someone else has put the same bundle in the cache before |
|
|
|
if (result != null) { |
|
ResourceBundle rb = result.get(); |
|
if (rb != null) { |
|
|
|
bundle = rb; |
|
// Clear the reference in the BundleReference so that |
|
|
|
bundleRef.clear(); |
|
} else { |
|
// Replace the invalid (garbage collected) |
|
|
|
cacheList.put(key, bundleRef); |
|
} |
|
} |
|
return bundle; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static String toOtherBundleName(String baseName, String bundleName, Locale locale) { |
|
var simpleName= baseName.substring(baseName.lastIndexOf('.') + 1); |
|
var suffix = bundleName.substring(bundleName.lastIndexOf(simpleName) + simpleName.length()); |
|
var otherSuffix = switch(locale.getLanguage()) { |
|
case "he" -> suffix.replaceFirst("^_he(_.*)?$", "_iw$1"); |
|
case "id" -> suffix.replaceFirst("^_id(_.*)?$", "_in$1"); |
|
case "yi" -> suffix.replaceFirst("^_yi(_.*)?$", "_ji$1"); |
|
case "iw" -> suffix.replaceFirst("^_iw(_.*)?$", "_he$1"); |
|
case "in" -> suffix.replaceFirst("^_in(_.*)?$", "_id$1"); |
|
case "ji" -> suffix.replaceFirst("^_ji(_.*)?$", "_yi$1"); |
|
default -> suffix; |
|
}; |
|
|
|
if (suffix.equals(otherSuffix)) { |
|
return bundleName; |
|
} else { |
|
return bundleName.substring(0, bundleName.lastIndexOf(suffix)) + otherSuffix; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public interface Strategy { |
|
|
|
|
|
*/ |
|
List<Locale> getCandidateLocales(String baseName, Locale locale); |
|
|
|
|
|
|
|
*/ |
|
String toBundleName(String baseName, Locale locale); |
|
|
|
|
|
|
|
|
|
*/ |
|
Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName, |
|
Locale locale); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static interface CacheKeyReference { |
|
CacheKey getCacheKey(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static class BundleReference extends SoftReference<ResourceBundle> |
|
implements CacheKeyReference { |
|
private final CacheKey cacheKey; |
|
|
|
BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key) { |
|
super(referent, q); |
|
cacheKey = key; |
|
} |
|
|
|
@Override |
|
public CacheKey getCacheKey() { |
|
return cacheKey; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static class CacheKey implements Cloneable { |
|
|
|
private String name; |
|
private Locale locale; |
|
|
|
|
|
private Throwable cause; |
|
|
|
// Hash code value cache to avoid recalculating the hash code |
|
|
|
private int hashCodeCache; |
|
|
|
// The service loader to load bundles or null if no service loader |
|
|
|
private ServiceLoader<ResourceBundleProvider> providers; |
|
|
|
CacheKey(String baseName, Locale locale) { |
|
this.name = baseName; |
|
this.locale = locale; |
|
calculateHashCode(); |
|
} |
|
|
|
String getName() { |
|
return name; |
|
} |
|
|
|
CacheKey setName(String baseName) { |
|
if (!this.name.equals(baseName)) { |
|
this.name = baseName; |
|
calculateHashCode(); |
|
} |
|
return this; |
|
} |
|
|
|
Locale getLocale() { |
|
return locale; |
|
} |
|
|
|
CacheKey setLocale(Locale locale) { |
|
if (!this.locale.equals(locale)) { |
|
this.locale = locale; |
|
calculateHashCode(); |
|
} |
|
return this; |
|
} |
|
|
|
ServiceLoader<ResourceBundleProvider> getProviders() { |
|
return providers; |
|
} |
|
|
|
void setProviders(ServiceLoader<ResourceBundleProvider> providers) { |
|
this.providers = providers; |
|
} |
|
|
|
@Override |
|
public boolean equals(Object other) { |
|
if (this == other) { |
|
return true; |
|
} |
|
try { |
|
final CacheKey otherEntry = (CacheKey)other; |
|
|
|
if (hashCodeCache != otherEntry.hashCodeCache) { |
|
return false; |
|
} |
|
return locale.equals(otherEntry.locale) |
|
&& name.equals(otherEntry.name); |
|
} catch (NullPointerException | ClassCastException e) { |
|
} |
|
return false; |
|
} |
|
|
|
@Override |
|
public int hashCode() { |
|
return hashCodeCache; |
|
} |
|
|
|
private void calculateHashCode() { |
|
hashCodeCache = name.hashCode() << 3; |
|
hashCodeCache ^= locale.hashCode(); |
|
} |
|
|
|
@Override |
|
public Object clone() { |
|
try { |
|
CacheKey clone = (CacheKey) super.clone(); |
|
|
|
clone.cause = null; |
|
|
|
clone.providers = null; |
|
return clone; |
|
} catch (CloneNotSupportedException e) { |
|
|
|
throw new InternalError(e); |
|
} |
|
} |
|
|
|
private void setCause(Throwable cause) { |
|
if (this.cause == null) { |
|
this.cause = cause; |
|
} else { |
|
// Override the cause if the previous one is |
|
|
|
if (this.cause instanceof ClassNotFoundException) { |
|
this.cause = cause; |
|
} |
|
} |
|
} |
|
|
|
private Throwable getCause() { |
|
return cause; |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
String l = locale.toString(); |
|
if (l.isEmpty()) { |
|
if (!locale.getVariant().isEmpty()) { |
|
l = "__" + locale.getVariant(); |
|
} else { |
|
l = "\"\""; |
|
} |
|
} |
|
return "CacheKey[" + name + ", lc=" + l + ")]"; |
|
} |
|
} |
|
} |