blob: 29f1b9d02e6aec0c84cbf03ce85abca2b93eb079 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2004-2006, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.util;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.ICUResourceBundleReader;
import com.ibm.icu.impl.ResourceBundleWrapper;
import com.ibm.icu.util.ULocale;
/**
* A class representing a collection of resource information pertaining to a given
* locale. A resource bundle provides a way of accessing locale- specfic information in
* a data file. You create a resource bundle that manages the resources for a given
* locale and then ask it for individual resources.
* <P>
* In ResourceBundle class, an object is created
* and the sub items are fetched using getString, getObject methods.
* In UResourceBundle,each individual element of a resource is a resource by itself.
*
* <P>
* Resource bundles in ICU are currently defined using text files which conform to the following
* <a href="http://dev.icu-project.org/cgi-bin/viewcvs.cgi/~checkout~/icuhtml/design/bnf_rb.txt">BNF definition</a>.
* More on resource bundle concepts and syntax can be found in the
* <a href="http://icu.sourceforge.net/userguide/ResourceManagement.html">Users Guide</a>.
* <P>
*
* The packaging of ICU *.res files can be of two types
* ICU4C:
* <code>
* root.res
* |
* --------
* | |
* fr.res en.res
* |
* --------
* | |
* fr_CA.res fr_FR.res
* </code>
* JAVA/JDK:
* <code>
* LocaleElements.res
* |
* -------------------
* | |
* LocaleElements_fr.res LocaleElements_en.res
* |
* ---------------------------
* | |
* LocaleElements_fr_CA.res LocaleElements_fr_FR.res
* </code>
* Depending on the organization of your resources, the syntax to getBundleInstance will change.
* To open ICU style organization use:
* <code>
* UResourceBundle bundle = UResourceBundle.getBundleInstance("com/ibm/icu/impl/data/icudt30b", "en_US");
* </code>
* To open Java/JDK style organization use:
* <code>
* UResourceBundle bundle = UResourceBundle.getBundleInstance("com.ibm.icu.impl.data.LocaleElements", "en_US");
* </code>
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
* @author ram
*/
public abstract class UResourceBundle extends ResourceBundle{
/**
* Creates a resource bundle using the specified base name and locale.
* ICU_DATA_CLASS is used as the default root.
* @param baseName the base name of the resource bundle, a fully qualified class name
* @param localeName the locale for which a resource bundle is desired
* @exception MissingResourceException
* if no resource bundle for the specified base name can be found
* @return a resource bundle for the given base name and locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public static UResourceBundle getBundleInstance(String baseName, String localeName){
return getBundleInstance(baseName, localeName, ICUResourceBundle.ICU_DATA_CLASS_LOADER, false);
}
/**
* Creates a resource bundle using the specified base name, locale, and class root.
*
* @param baseName the base name of the resource bundle, a fully qualified class name
* @param localeName the locale for which a resource bundle is desired
* @param root the class object from which to load the resource bundle
* @exception MissingResourceException
* if no resource bundle for the specified base name can be found
* @return a resource bundle for the given base name and locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public static UResourceBundle getBundleInstance(String baseName, String localeName, ClassLoader root){
return getBundleInstance(baseName, localeName, root, false);
}
/**
* Creates a resource bundle using the specified base name, locale, and class root.
*
* @param baseName the base name of the resource bundle, a fully qualified class name
* @param localeName the locale for which a resource bundle is desired
* @param root the class object from which to load the resource bundle
* @param disableFallback Option to disable locale inheritence.
* If true the fallback chain will not be built.
* @exception MissingResourceException
* if no resource bundle for the specified base name can be found
* @return a resource bundle for the given base name and locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*
*/
protected static UResourceBundle getBundleInstance(String baseName, String localeName, ClassLoader root, boolean disableFallback){
return instantiateBundle(baseName, localeName, root, disableFallback);
}
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.) This is public for compatibility with Java, whose compiler
* will generate public default constructors for an abstract class.
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public UResourceBundle() {
}
/**
* Creates a UResourceBundle for the locale specified, from which users can extract resources by using
* their corresponding keys.
* @param locale specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @return a resource bundle for the given locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public static UResourceBundle getBundleInstance(ULocale locale){
if(locale==null){
locale = ULocale.getDefault();
}
return getBundleInstance( ICUResourceBundle.ICU_BASE_NAME, locale.toString(), ICUResourceBundle.ICU_DATA_CLASS_LOADER );
}
/**
* Creates a UResourceBundle for the default locale and specified base name,
* from which users can extract resources by using their corresponding keys.
* @param baseName specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @return a resource bundle for the given base name and default locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public static UResourceBundle getBundleInstance(String baseName){
return getBundleInstance( baseName, ULocale.getDefault().toString(), ICUResourceBundle.ICU_DATA_CLASS_LOADER );
}
/**
* Creates a UResourceBundle for the specified locale and specified base name,
* from which users can extract resources by using their corresponding keys.
* @param baseName specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @param locale specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @return a resource bundle for the given base name and locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public static UResourceBundle getBundleInstance(String baseName, Locale locale){
return getBundleInstance(baseName, ULocale.forLocale(locale));
}
/**
* Creates a UResourceBundle, from which users can extract resources by using
* their corresponding keys.
* @param baseName string containing the name of the data package.
* If null the default ICU package name is used.
* @param locale specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @return a resource bundle for the given base name and locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public static UResourceBundle getBundleInstance(String baseName, ULocale locale){
return getBundleInstance(baseName, locale.toString(),ICUResourceBundle.ICU_DATA_CLASS_LOADER);
}
/**
* Creates a UResourceBundle for the specified locale and specified base name,
* from which users can extract resources by using their corresponding keys.
* @param baseName specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @param locale specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @param loader the loader to use
* @return a resource bundle for the given base name and locale
* @internal revisit for ICU 3.6
* @provisional This API might change or be removed in a future release.
*/
public static UResourceBundle getBundleInstance(String baseName, Locale locale, ClassLoader loader){
return getBundleInstance(baseName, ULocale.forLocale(locale), loader);
}
/**
* Creates a UResourceBundle, from which users can extract resources by using
* their corresponding keys.
* @param baseName string containing the name of the data package.
* If null the default ICU package name is used.
* @param locale specifies the locale for which we want to open the resource.
* If null the bundle for default locale is opened.
* @param loader the loader to use
* @return a resource bundle for the given base name and locale
* @internal revisit for ICU 3.6
* @deprecated This API is ICU internal only.
*/
public static UResourceBundle getBundleInstance(String baseName, ULocale locale, ClassLoader loader){
return getBundleInstance(baseName, locale.toString(),loader);
}
/**
* Returns the RFC 3066 conformant locale id of this resource bundle.
* This method can be used after a call to getBundleInstance() to
* determine whether the resource bundle returned really
* corresponds to the requested locale or is a fallback.
*
* @return the locale of this resource bundle
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public abstract ULocale getULocale();
/**
* Gets the localeID
* @return The string representation of the localeID
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
protected abstract String getLocaleID();
/**
* Gets the base name of the resource bundle
* @return The string representation of the base name
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
protected abstract String getBaseName();
/**
* Gets the parent bundle
* @return The parent bundle
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
protected abstract UResourceBundle getParent();
/**
* Get the locale of this bundle
* @return the locale of this resource bundle
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
public Locale getLocale(){
return getULocale().toLocale();
}
// Cache for ResourceBundle instantiation
private static SoftReference BUNDLE_CACHE;
private static void addToCache(ResourceCacheKey key, UResourceBundle b) {
Map m = null;
if (BUNDLE_CACHE != null) {
m = (Map)BUNDLE_CACHE.get();
}
if (m == null) {
m = new HashMap();
BUNDLE_CACHE = new SoftReference(m);
}
m.put(key, b);
}
/**
* @internal revisit for ICU 3.6
*/
protected static synchronized void addToCache(ClassLoader cl, String fullName, ULocale defaultLocale, UResourceBundle b){
cacheKey.setKeyValues(cl, fullName, defaultLocale);
addToCache(cacheKey, b);
}
/**
* @internal revisit for ICU 3.6
*/
protected static synchronized UResourceBundle loadFromCache(ClassLoader cl, String fullName, ULocale defaultLocale){
cacheKey.setKeyValues(cl, fullName, defaultLocale);
return loadFromCache(cacheKey);
}
private static UResourceBundle loadFromCache(ResourceCacheKey key) {
if (BUNDLE_CACHE != null) {
Map m = (Map)BUNDLE_CACHE.get();
if (m != null) {
return (UResourceBundle)m.get(key);
}
}
return null;
}
/**
* Key used for cached resource bundles. The key checks
* the resource name, the class root, and the default
* locale to determine if the resource is a match to the
* requested one. The root may be null, but the
* searchName and the default locale must have a non-null value.
* Note that the default locale may change over time, and
* lookup should always be based on the current default
* locale (if at all).
*/
private static final class ResourceCacheKey implements Cloneable {
private SoftReference loaderRef;
private String searchName;
private ULocale defaultLocale;
private int hashCodeCache;
///CLOVER:OFF
public boolean equals(Object other) {
if (this == other) {
return true;
}
try {
final ResourceCacheKey otherEntry = (ResourceCacheKey) other;
//quick check to see if they are not equal
if (hashCodeCache != otherEntry.hashCodeCache) {
return false;
}
//are the names the same?
if (!searchName.equals(otherEntry.searchName)) {
return false;
}
// are the default locales the same?
if (defaultLocale == null) {
if (otherEntry.defaultLocale != null) {
return false;
}
} else {
if (!defaultLocale.equals(otherEntry.defaultLocale)) {
return false;
}
}
//are refs (both non-null) or (both null)?
if (loaderRef == null) {
return otherEntry.loaderRef == null;
} else {
return (otherEntry.loaderRef != null)
&& (loaderRef.get() == otherEntry.loaderRef.get());
}
} catch (NullPointerException e) {
return false;
} catch (ClassCastException e) {
return false;
}
}
public int hashCode() {
return hashCodeCache;
}
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
//this should never happen
throw new InternalError();
}
}
///CLOVER:ON
private void setKeyValues(ClassLoader root, String searchName, ULocale defaultLocale) {
this.searchName = searchName;
hashCodeCache = searchName.hashCode();
this.defaultLocale = defaultLocale;
if (defaultLocale != null) {
hashCodeCache ^= defaultLocale.hashCode();
}
if (root == null) {
this.loaderRef = null;
} else {
loaderRef = new SoftReference(root);
hashCodeCache ^= root.hashCode();
}
}
///CLOVER:OFF
private void clear() {
setKeyValues(null, "", null);
}
///CLOVER:ON
}
private static final ResourceCacheKey cacheKey = new ResourceCacheKey();
private static final int ROOT_MISSING = 0;
private static final int ROOT_ICU = 1;
private static final int ROOT_JAVA = 2;
private static SoftReference ROOT_CACHE;
private static int getRootType(String baseName, ClassLoader root)
{
Map m = null;
Integer rootType;
if (ROOT_CACHE != null) {
m = (Map) ROOT_CACHE.get();
}
if (m == null) {
m = new HashMap();
ROOT_CACHE = new SoftReference(m);
}
rootType = (Integer) m.get(baseName);
if (rootType == null) {
String rootLocale = (baseName.indexOf('.')==-1) ? "root" : "";
int rt = ROOT_MISSING;
UResourceBundle b = null;
try{
b = ICUResourceBundle.getBundleInstance(baseName, rootLocale, root, true);
rt = ROOT_ICU;
}catch(MissingResourceException ex){
try{
b = ResourceBundleWrapper.getBundleInstance(baseName, rootLocale, root, true);
rt = ROOT_JAVA;
}catch(MissingResourceException e){
//throw away the exception
}
}
rootType = new Integer(rt);
m.put(baseName, rootType);
}
return rootType.intValue();
}
private static void setRootType(String baseName, int rootType)
{
Integer rt = new Integer(rootType);
Map m = null;
if (ROOT_CACHE != null) {
m = (Map) ROOT_CACHE.get();
} else {
m = new HashMap();
ROOT_CACHE = new SoftReference(m);
}
m.put(baseName, rt);
}
/**
* Loads a new resource bundle for the give base name, locale and class loader.
* Optionally will disable loading of fallback bundles.
* @param baseName the base name of the resource bundle, a fully qualified class name
* @param localeName the locale for which a resource bundle is desired
* @param root the class object from which to load the resource bundle
* @param disableFallback disables loading of fallback lookup chain
* @exception MissingResourceException
* if no resource bundle for the specified base name can be found
* @return a resource bundle for the given base name and locale
* @draft ICU 3.0
* @provisional This API might change or be removed in a future release.
*/
protected static synchronized UResourceBundle instantiateBundle(
String baseName, String localeName, ClassLoader root, boolean disableFallback)
{
UResourceBundle b = null;
int rootType = getRootType(baseName, root);
ULocale defaultLocale = ULocale.getDefault();
switch (rootType)
{
case ROOT_ICU:
if(disableFallback) {
String fullName = ICUResourceBundleReader.getFullName(baseName, localeName);
cacheKey.setKeyValues(root, fullName, defaultLocale);
b = loadFromCache(cacheKey);
if (b == null) {
b = ICUResourceBundle.getBundleInstance(baseName, localeName, root, disableFallback);
cacheKey.setKeyValues(root, fullName, defaultLocale);
addToCache(cacheKey, b);
}
} else {
b = ICUResourceBundle.getBundleInstance(baseName, localeName, root, disableFallback);
}
return b;
case ROOT_JAVA:
return ResourceBundleWrapper.getBundleInstance(baseName, localeName, root, disableFallback);
default:
try{
b = ICUResourceBundle.getBundleInstance(baseName, localeName, root, disableFallback);
setRootType(baseName, ROOT_ICU);
}catch(MissingResourceException ex){
b = ResourceBundleWrapper.getBundleInstance(baseName, localeName, root, disableFallback);
setRootType(baseName, ROOT_JAVA);
}
return b;
}
}
/**
* @internal
*/
protected abstract void setLoadingStatus(int newStatus);
}