com.ibm.icu.impl.ICUResourceBundle Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
/*
* *****************************************************************************
* Copyright (C) 2005-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
* *****************************************************************************
*/
package com.ibm.icu.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import com.ibm.icu.impl.URLHandler.URLVisitor;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
import com.ibm.icu.util.UResourceBundleIterator;
import com.ibm.icu.util.VersionInfo;
public class ICUResourceBundle extends UResourceBundle {
/**
* The data path to be used with getBundleInstance API
*/
protected static final String ICU_DATA_PATH = "com/ibm/icu/impl/";
/**
* The data path to be used with getBundleInstance API
*/
public static final String ICU_BUNDLE = "data/icudt" + VersionInfo.ICU_DATA_VERSION_PATH;
/**
* The base name of ICU data to be used with getBundleInstance API
*/
public static final String ICU_BASE_NAME = ICU_DATA_PATH + ICU_BUNDLE;
/**
* The base name of collation data to be used with getBundleInstance API
*/
public static final String ICU_COLLATION_BASE_NAME = ICU_BASE_NAME + "/coll";
/**
* The base name of rbbi data to be used with getData API
*/
public static final String ICU_BRKITR_NAME = "/brkitr";
/**
* The base name of rbbi data to be used with getBundleInstance API
*/
public static final String ICU_BRKITR_BASE_NAME = ICU_BASE_NAME + ICU_BRKITR_NAME;
/**
* The base name of rbnf data to be used with getBundleInstance API
*/
public static final String ICU_RBNF_BASE_NAME = ICU_BASE_NAME + "/rbnf";
/**
* The base name of transliterator data to be used with getBundleInstance API
*/
public static final String ICU_TRANSLIT_BASE_NAME = ICU_BASE_NAME + "/translit";
public static final String ICU_LANG_BASE_NAME = ICU_BASE_NAME + "/lang";
public static final String ICU_CURR_BASE_NAME = ICU_BASE_NAME + "/curr";
public static final String ICU_REGION_BASE_NAME = ICU_BASE_NAME + "/region";
public static final String ICU_ZONE_BASE_NAME = ICU_BASE_NAME + "/zone";
private static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205";
/**
* The actual path of the resource
*/
protected String resPath;
/**
* The class loader constant to be used with getBundleInstance API
*/
public static final ClassLoader ICU_DATA_CLASS_LOADER;
static {
ClassLoader loader = ICUData.class.getClassLoader();
if (loader == null) {
loader = Utility.getFallbackClassLoader();
}
ICU_DATA_CLASS_LOADER = loader;
}
/**
* The name of the resource containing the installed locales
*/
protected static final String INSTALLED_LOCALES = "InstalledLocales";
public static final int FROM_FALLBACK = 1, FROM_ROOT = 2, FROM_DEFAULT = 3, FROM_LOCALE = 4;
private int loadingStatus = -1;
public void setLoadingStatus(int newStatus) {
loadingStatus = newStatus;
}
/**
* Returns the loading status of a particular resource.
*
* @return FROM_FALLBACK if the resource is fetched from fallback bundle
* FROM_ROOT if the resource is fetched from root bundle.
* FROM_DEFAULT if the resource is fetched from the default locale.
*/
public int getLoadingStatus() {
return loadingStatus;
}
public void setLoadingStatus(String requestedLocale){
String locale = getLocaleID();
if(locale.equals("root")) {
setLoadingStatus(FROM_ROOT);
} else if(locale.equals(requestedLocale)) {
setLoadingStatus(FROM_LOCALE);
} else {
setLoadingStatus(FROM_FALLBACK);
}
}
/**
* Returns the respath of this bundle
* @return the respath of the bundle
*/
public String getResPath(){
return resPath;
}
/**
* Returns a functionally equivalent locale, considering keywords as well, for the specified keyword.
* @param baseName resource specifier
* @param resName top level resource to consider (such as "collations")
* @param keyword a particular keyword to consider (such as "collation" )
* @param locID The requested locale
* @param isAvailable If non-null, 1-element array of fillin parameter that indicates whether the
* requested locale was available. The locale is defined as 'available' if it physically
* exists within the specified tree and included in 'InstalledLocales'.
* @param omitDefault if true, omit keyword and value if default.
* 'de_DE\@collation=standard' -> 'de_DE'
* @return the locale
* @internal ICU 3.0
*/
public static final ULocale getFunctionalEquivalent(String baseName, ClassLoader loader,
String resName, String keyword, ULocale locID,
boolean isAvailable[], boolean omitDefault) {
String kwVal = locID.getKeywordValue(keyword);
String baseLoc = locID.getBaseName();
String defStr = null;
ULocale parent = new ULocale(baseLoc);
ULocale defLoc = null; // locale where default (found) resource is
boolean lookForDefault = false; // true if kwVal needs to be set
ULocale fullBase = null; // base locale of found (target) resource
int defDepth = 0; // depth of 'default' marker
int resDepth = 0; // depth of found resource;
if ((kwVal == null) || (kwVal.length() == 0)
|| kwVal.equals(DEFAULT_TAG)) {
kwVal = ""; // default tag is treated as no keyword
lookForDefault = true;
}
// Check top level locale first
ICUResourceBundle r = null;
r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
if (isAvailable != null) {
isAvailable[0] = false;
ULocale[] availableULocales = getAvailEntry(baseName, loader).getULocaleList();
for (int i = 0; i < availableULocales.length; i++) {
if (parent.equals(availableULocales[i])) {
isAvailable[0] = true;
break;
}
}
}
// determine in which locale (if any) the currently relevant 'default' is
do {
try {
ICUResourceBundle irb = (ICUResourceBundle) r.get(resName);
defStr = irb.getString(DEFAULT_TAG);
if (lookForDefault == true) {
kwVal = defStr;
lookForDefault = false;
}
defLoc = r.getULocale();
} catch (MissingResourceException t) {
// Ignore error and continue search.
}
if (defLoc == null) {
r = (ICUResourceBundle) r.getParent();
defDepth++;
}
} while ((r != null) && (defLoc == null));
// Now, search for the named resource
parent = new ULocale(baseLoc);
r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
// determine in which locale (if any) the named resource is located
do {
try {
ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
/* UResourceBundle urb = */irb.get(kwVal);
fullBase = irb.getULocale();
// If the get() completed, we have the full base locale
// If we fell back to an ancestor of the old 'default',
// we need to re calculate the "default" keyword.
if ((fullBase != null) && ((resDepth) > defDepth)) {
defStr = irb.getString(DEFAULT_TAG);
defLoc = r.getULocale();
defDepth = resDepth;
}
} catch (MissingResourceException t) {
// Ignore error,
}
if (fullBase == null) {
r = (ICUResourceBundle) r.getParent();
resDepth++;
}
} while ((r != null) && (fullBase == null));
if (fullBase == null && // Could not find resource 'kwVal'
(defStr != null) && // default was defined
!defStr.equals(kwVal)) { // kwVal is not default
// couldn't find requested resource. Fall back to default.
kwVal = defStr; // Fall back to default.
parent = new ULocale(baseLoc);
r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
resDepth = 0;
// determine in which locale (if any) the named resource is located
do {
try {
ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
UResourceBundle urb = irb.get(kwVal);
// if we didn't fail before this..
fullBase = r.getULocale();
// If the fetched item (urb) is in a different locale than our outer locale (r/fullBase)
// then we are in a 'fallback' situation. treat as a missing resource situation.
if(!fullBase.toString().equals(urb.getLocale().toString())) {
fullBase = null; // fallback condition. Loop and try again.
}
// If we fell back to an ancestor of the old 'default',
// we need to re calculate the "default" keyword.
if ((fullBase != null) && ((resDepth) > defDepth)) {
defStr = irb.getString(DEFAULT_TAG);
defLoc = r.getULocale();
defDepth = resDepth;
}
} catch (MissingResourceException t) {
// Ignore error, continue search.
}
if (fullBase == null) {
r = (ICUResourceBundle) r.getParent();
resDepth++;
}
} while ((r != null) && (fullBase == null));
}
if (fullBase == null) {
throw new MissingResourceException(
"Could not find locale containing requested or default keyword.",
baseName, keyword + "=" + kwVal);
}
if (omitDefault
&& defStr.equals(kwVal) // if default was requested and
&& resDepth <= defDepth) { // default was set in same locale or child
return fullBase; // Keyword value is default - no keyword needed in locale
} else {
return new ULocale(fullBase.toString() + "@" + keyword + "=" + kwVal);
}
}
/**
* Given a tree path and keyword, return a string enumeration of all possible values for that keyword.
* @param baseName resource specifier
* @param keyword a particular keyword to consider, must match a top level resource name
* within the tree. (i.e. "collations")
* @internal ICU 3.0
*/
public static final String[] getKeywordValues(String baseName, String keyword) {
Set keywords = new HashSet();
ULocale locales[] = createULocaleList(baseName, ICU_DATA_CLASS_LOADER);
int i;
for (i = 0; i < locales.length; i++) {
try {
UResourceBundle b = UResourceBundle.getBundleInstance(baseName, locales[i]);
// downcast to ICUResourceBundle?
ICUResourceBundle irb = (ICUResourceBundle) (b.getObject(keyword));
Enumeration e = irb.getKeys();
while (e.hasMoreElements()) {
String s = e.nextElement();
if (!DEFAULT_TAG.equals(s)) {
// don't add 'default' items
keywords.add(s);
}
}
} catch (Throwable t) {
//System.err.println("Error in - " + new Integer(i).toString()
// + " - " + t.toString());
// ignore the err - just skip that resource
}
}
return keywords.toArray(new String[0]);
}
/**
* This method performs multilevel fallback for fetching items from the
* bundle e.g: If resource is in the form de__PHONEBOOK{ collations{
* default{ "phonebook"} } } If the value of "default" key needs to be
* accessed, then do:
* UResourceBundle bundle = UResourceBundle.getBundleInstance("de__PHONEBOOK");
* ICUResourceBundle result = null;
* if(bundle instanceof ICUResourceBundle){
* result = ((ICUResourceBundle) bundle).getWithFallback("collations/default");
* }
*
*
* @param path The path to the required resource key
* @return resource represented by the key
* @exception MissingResourceException If a resource was not found.
*/
public ICUResourceBundle getWithFallback(String path) throws MissingResourceException {
ICUResourceBundle result = null;
ICUResourceBundle actualBundle = this;
// now recurse to pick up sub levels of the items
result = findResourceWithFallback(path, actualBundle, null);
if (result == null) {
throw new MissingResourceException(
"Can't find resource for bundle "
+ this.getClass().getName() + ", key " + getType(),
path, getKey());
}
if ( result.getType() == ICUResourceBundle.STRING && result.getString().equals(NO_INHERITANCE_MARKER)) {
throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER",path,getKey());
}
return result;
}
public ICUResourceBundle at(int index) {
return (ICUResourceBundle) handleGet(index, null, this);
}
public ICUResourceBundle at(String key) {
// don't ever presume the key is an int in disguise, like ResourceArray does.
if (this instanceof ICUResourceBundleImpl.ResourceTable) {
return (ICUResourceBundle) handleGet(key, null, this);
}
return null;
}
@Override
public ICUResourceBundle findTopLevel(int index) {
return (ICUResourceBundle) super.findTopLevel(index);
}
@Override
public ICUResourceBundle findTopLevel(String aKey) {
return (ICUResourceBundle) super.findTopLevel(aKey);
}
/**
* Like getWithFallback, but returns null if the resource is not found instead of
* throwing an exception.
* @param path the path to the resource
* @return the resource, or null
*/
public ICUResourceBundle findWithFallback(String path) {
return findResourceWithFallback(path, this, null);
}
// will throw type mismatch exception if the resource is not a string
public String getStringWithFallback(String path) throws MissingResourceException {
return getWithFallback(path).getString();
}
/**
* Return a set of the locale names supported by a collection of resource
* bundles.
*
* @param bundlePrefix the prefix of the resource bundles to use.
*/
public static Set getAvailableLocaleNameSet(String bundlePrefix, ClassLoader loader) {
return getAvailEntry(bundlePrefix, loader).getLocaleNameSet();
}
/**
* Return a set of all the locale names supported by a collection of
* resource bundles.
*/
public static Set getFullLocaleNameSet() {
return getFullLocaleNameSet(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
}
/**
* Return a set of all the locale names supported by a collection of
* resource bundles.
*
* @param bundlePrefix the prefix of the resource bundles to use.
*/
public static Set getFullLocaleNameSet(String bundlePrefix, ClassLoader loader) {
return getAvailEntry(bundlePrefix, loader).getFullLocaleNameSet();
}
/**
* Return a set of the locale names supported by a collection of resource
* bundles.
*/
public static Set getAvailableLocaleNameSet() {
return getAvailableLocaleNameSet(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
}
/**
* Get the set of Locales installed in the specified bundles.
* @return the list of available locales
*/
public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader) {
return getAvailEntry(baseName, loader).getULocaleList();
}
/**
* Get the set of ULocales installed the base bundle.
* @return the list of available locales
*/
public static final ULocale[] getAvailableULocales() {
return getAvailableULocales(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
}
/**
* Get the set of Locales installed in the specified bundles.
* @return the list of available locales
*/
public static final Locale[] getAvailableLocales(String baseName, ClassLoader loader) {
return getAvailEntry(baseName, loader).getLocaleList();
}
/**
* Get the set of Locales installed the base bundle.
* @return the list of available locales
*/
public static final Locale[] getAvailableLocales() {
return getAvailEntry(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER).getLocaleList();
}
/**
* Convert a list of ULocales to a list of Locales. ULocales with a script code will not be converted
* since they cannot be represented as a Locale. This means that the two lists will not match
* one-to-one, and that the returned list might be shorter than the input list.
* @param ulocales a list of ULocales to convert to a list of Locales.
* @return the list of converted ULocales
*/
public static final Locale[] getLocaleList(ULocale[] ulocales) {
ArrayList list = new ArrayList(ulocales.length);
HashSet uniqueSet = new HashSet();
for (int i = 0; i < ulocales.length; i++) {
Locale loc = ulocales[i].toLocale();
if (!uniqueSet.contains(loc)) {
list.add(loc);
uniqueSet.add(loc);
}
}
return list.toArray(new Locale[list.size()]);
}
/**
* Returns the locale of this resource bundle. This method can be used after
* a call to getBundle() to determine whether the resource bundle returned
* really corresponds to the requested locale or is a fallback.
*
* @return the locale of this resource bundle
*/
public Locale getLocale() {
return getULocale().toLocale();
}
// ========== privates ==========
private static final String ICU_RESOURCE_INDEX = "res_index";
private static final String DEFAULT_TAG = "default";
// The name of text file generated by ICU4J build script including all locale names
// (canonical, alias and root)
private static final String FULL_LOCALE_NAMES_LIST = "fullLocaleNames.lst";
// Flag for enabling/disabling debugging code
private static final boolean DEBUG = ICUDebug.enabled("localedata");
private static final ULocale[] createULocaleList(String baseName,
ClassLoader root) {
// the canned list is a subset of all the available .res files, the idea
// is we don't export them
// all. gotta be a better way to do this, since to add a locale you have
// to update this list,
// and it's embedded in our binary resources.
ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
int length = bundle.getSize();
int i = 0;
ULocale[] locales = new ULocale[length];
UResourceBundleIterator iter = bundle.getIterator();
iter.reset();
while (iter.hasNext()) {
String locstr = iter.next().getKey();
if (locstr.equals("root")) {
locales[i++] = ULocale.ROOT;
} else {
locales[i++] = new ULocale(locstr);
}
}
bundle = null;
return locales;
}
private static final Locale[] createLocaleList(String baseName, ClassLoader loader) {
ULocale[] ulocales = getAvailEntry(baseName, loader).getULocaleList();
return getLocaleList(ulocales);
}
private static final String[] createLocaleNameArray(String baseName,
ClassLoader root) {
ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle( baseName, ICU_RESOURCE_INDEX, root, true);
bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
int length = bundle.getSize();
int i = 0;
String[] locales = new String[length];
UResourceBundleIterator iter = bundle.getIterator();
iter.reset();
while (iter.hasNext()) {
String locstr = iter.next(). getKey();
if (locstr.equals("root")) {
locales[i++] = ULocale.ROOT.toString();
} else {
locales[i++] = locstr;
}
}
bundle = null;
return locales;
}
private static final List createFullLocaleNameArray(
final String baseName, final ClassLoader root) {
List list = java.security.AccessController
.doPrivileged(new java.security.PrivilegedAction>() {
public List run() {
// WebSphere class loader will return null for a raw
// directory name without trailing slash
String bn = baseName.endsWith("/")
? baseName
: baseName + "/";
List resList = null;
String skipScan = ICUConfig.get("com.ibm.icu.impl.ICUResourceBundle.skipRuntimeLocaleResourceScan", "false");
if (!skipScan.equalsIgnoreCase("true")) {
// scan available locale resources under the base url first
try {
Enumeration urls = root.getResources(bn);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
URLHandler handler = URLHandler.get(url);
if (handler != null) {
final List lst = new ArrayList();
URLVisitor v = new URLVisitor() {
public void visit(String s) {
//TODO: This is ugly hack. We have to figure out how
// we can distinguish locale data from others
if (s.endsWith(".res")) {
String locstr = s.substring(0, s.length() - 4);
if (locstr.contains("_") && !locstr.equals("res_index")) {
// locale data with country/script contain "_",
// except for res_index.res
lst.add(locstr);
} else if (locstr.length() == 2 || locstr.length() == 3) {
// all 2-letter or 3-letter entries are all locale
// data at least for now
lst.add(locstr);
} else if (locstr.equalsIgnoreCase("root")) {
// root locale is a special case
lst.add(ULocale.ROOT.toString());
}
}
}
};
handler.guide(v, false);
if (resList == null) {
resList = new ArrayList(lst);
} else {
resList.addAll(lst);
}
} else {
if (DEBUG) System.out.println("handler for " + url + " is null");
}
}
} catch (IOException e) {
if (DEBUG) System.out.println("ouch: " + e.getMessage());
resList = null;
}
}
if (resList == null) {
// look for prebuilt full locale names list next
try {
InputStream s = root.getResourceAsStream(bn + FULL_LOCALE_NAMES_LIST);
if (s != null) {
resList = new ArrayList();
BufferedReader br = new BufferedReader(new InputStreamReader(s, "ASCII"));
String line;
while ((line = br.readLine()) != null) {
if (line.length() != 0 && !line.startsWith("#")) {
if (line.equalsIgnoreCase("root")) {
resList.add(ULocale.ROOT.toString());
} else {
resList.add(line);
}
}
}
br.close();
}
} catch (IOException e) {
// swallow it
}
}
return resList;
}
});
return list;
}
private static Set createFullLocaleNameSet(String baseName, ClassLoader loader) {
List list = createFullLocaleNameArray(baseName, loader);
if(list == null){
if (DEBUG) System.out.println("createFullLocaleNameArray returned null");
// Use locale name set as the last resort fallback
Set locNameSet = createLocaleNameSet(baseName, loader);
String rootLocaleID = ULocale.ROOT.toString();
if (!locNameSet.contains(rootLocaleID)) {
// We need to add the root locale in the set
Set tmp = new HashSet(locNameSet);
tmp.add(rootLocaleID);
locNameSet = Collections.unmodifiableSet(tmp);
}
return locNameSet;
}
Set fullLocNameSet = new HashSet();
fullLocNameSet.addAll(list);
return Collections.unmodifiableSet(fullLocNameSet);
}
private static Set createLocaleNameSet(String baseName, ClassLoader loader) {
try {
String[] locales = createLocaleNameArray(baseName, loader);
HashSet set = new HashSet();
set.addAll(Arrays.asList(locales));
return Collections.unmodifiableSet(set);
} catch (MissingResourceException e) {
if (DEBUG) {
System.out.println("couldn't find index for bundleName: " + baseName);
Thread.dumpStack();
}
}
return Collections.emptySet();
}
/**
* Holds the prefix, and lazily creates the Locale[] list or the locale name
* Set as needed.
*/
private static final class AvailEntry {
private String prefix;
private ClassLoader loader;
private volatile ULocale[] ulocales;
private volatile Locale[] locales;
private volatile Set nameSet;
private volatile Set fullNameSet;
AvailEntry(String prefix, ClassLoader loader) {
this.prefix = prefix;
this.loader = loader;
}
ULocale[] getULocaleList() {
if (ulocales == null) {
synchronized(this) {
if (ulocales == null) {
ulocales = createULocaleList(prefix, loader);
}
}
}
return ulocales;
}
Locale[] getLocaleList() {
if (locales == null) {
synchronized(this) {
if (locales == null) {
locales = createLocaleList(prefix, loader);
}
}
}
return locales;
}
Set getLocaleNameSet() {
if (nameSet == null) {
synchronized(this) {
if (nameSet == null) {
nameSet = createLocaleNameSet(prefix, loader);
}
}
}
return nameSet;
}
Set getFullLocaleNameSet() {
// When there's no prebuilt index, we iterate through the jar files
// and read the contents to build it. If many threads try to read the
// same jar at the same time, java thrashes. Synchronize here
// so that we can avoid this problem. We don't synchronize on the
// other methods since they don't do this.
//
// This is the common entry point for access into the code that walks
// through the resources, and is cached. So it's a good place to lock
// access. Locking in the URLHandler doesn't give us a common object
// to lock.
if (fullNameSet == null) {
synchronized(this) {
if (fullNameSet == null) {
fullNameSet = createFullLocaleNameSet(prefix, loader);
}
}
}
return fullNameSet;
}
}
/*
* Cache used for AvailableEntry
*/
private static CacheBase GET_AVAILABLE_CACHE =
new SoftCache() {
protected AvailEntry createInstance(String key, ClassLoader loader) {
return new AvailEntry(key, loader);
}
};
/**
* Stores the locale information in a cache accessed by key (bundle prefix).
* The cached objects are AvailEntries. The cache is implemented by SoftCache
* so it can be GC'd.
*/
private static AvailEntry getAvailEntry(String key, ClassLoader loader) {
return GET_AVAILABLE_CACHE.getInstance(key, loader);
}
protected static final ICUResourceBundle findResourceWithFallback(String path,
UResourceBundle actualBundle, UResourceBundle requested) {
ICUResourceBundle sub = null;
if (requested == null) {
requested = actualBundle;
}
ICUResourceBundle base = (ICUResourceBundle) actualBundle;
String basePath = ((ICUResourceBundle)actualBundle).resPath.length() > 0 ?
((ICUResourceBundle)actualBundle).resPath : "";
while (base != null) {
if (path.indexOf('/') == -1) { // skip the tokenizer
sub = (ICUResourceBundle) base.handleGet(path, null, requested);
if (sub != null) {
break;
}
} else {
ICUResourceBundle currentBase = base;
StringTokenizer st = new StringTokenizer(path, "/");
while (st.hasMoreTokens()) {
String subKey = st.nextToken();
sub = ICUResourceBundle.findResourceWithFallback(subKey, currentBase, requested);
if (sub == null) {
break;
}
currentBase = sub;
}
if (sub != null) {
//we found it
break;
}
}
// if not try the parent bundle - note, getParent() returns the bundle root
base = (ICUResourceBundle)base.getParent();
path = basePath.length() > 0 ? basePath + "/" + path : path;
basePath = "";
}
if(sub != null){
sub.setLoadingStatus(((ICUResourceBundle)requested).getLocaleID());
}
return sub;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other instanceof ICUResourceBundle) {
ICUResourceBundle o = (ICUResourceBundle) other;
if (getBaseName().equals(o.getBaseName())
&& getLocaleID().equals(o.getLocaleID())) {
return true;
}
}
return false;
}
public int hashCode() {
assert false : "hashCode not designed";
return 42;
}
// This method is for super class's instantiateBundle method
public static UResourceBundle getBundleInstance(String baseName, String localeID,
ClassLoader root, boolean disableFallback){
UResourceBundle b = instantiateBundle(baseName, localeID, root, disableFallback);
if(b==null){
throw new MissingResourceException("Could not find the bundle "+ baseName+"/"+ localeID+".res","","");
}
return b;
}
// recursively build bundle .. over-ride super class method.
protected synchronized static UResourceBundle instantiateBundle(String baseName, String localeID,
ClassLoader root, boolean disableFallback){
ULocale defaultLocale = ULocale.getDefault();
String localeName = localeID;
if(localeName.indexOf('@')>=0){
localeName = ULocale.getBaseName(localeID);
}
String fullName = ICUResourceBundleReader.getFullName(baseName, localeName);
ICUResourceBundle b = (ICUResourceBundle)loadFromCache(root, fullName, defaultLocale);
// here we assume that java type resource bundle organization
// is required then the base name contains '.' else
// the resource organization is of ICU type
// so clients can instantiate resources of the type
// com.mycompany.data.MyLocaleElements_en.res and
// com.mycompany.data.MyLocaleElements.res
//
final String rootLocale = (baseName.indexOf('.')==-1) ? "root" : "";
final String defaultID = defaultLocale.getBaseName();
if(localeName.equals("")){
localeName = rootLocale;
}
if(DEBUG) System.out.println("Creating "+fullName+ " currently b is "+b);
if (b == null) {
b = ICUResourceBundle.createBundle(baseName, localeName, root);
if(DEBUG)System.out.println("The bundle created is: "+b+" and disableFallback="+disableFallback+" and bundle.getNoFallback="+(b!=null && b.getNoFallback()));
if(disableFallback || (b!=null && b.getNoFallback())){
// no fallback because the caller said so or because the bundle says so
return addToCache(root, fullName, defaultLocale, b);
}
// fallback to locale ID parent
if(b == null){
int i = localeName.lastIndexOf('_');
if (i != -1) {
String temp = localeName.substring(0, i);
b = (ICUResourceBundle)instantiateBundle(baseName, temp, root, disableFallback);
if(b!=null && b.getULocale().getName().equals(temp)){
b.setLoadingStatus(ICUResourceBundle.FROM_FALLBACK);
}
}else{
if(defaultID.indexOf(localeName)==-1){
b = (ICUResourceBundle)instantiateBundle(baseName, defaultID, root, disableFallback);
if(b!=null){
b.setLoadingStatus(ICUResourceBundle.FROM_DEFAULT);
}
}else if(rootLocale.length()!=0){
b = ICUResourceBundle.createBundle(baseName, rootLocale, root);
if(b!=null){
b.setLoadingStatus(ICUResourceBundle.FROM_ROOT);
}
}
}
}else{
UResourceBundle parent = null;
localeName = b.getLocaleID();
int i = localeName.lastIndexOf('_');
b = (ICUResourceBundle)addToCache(root, fullName, defaultLocale, b);
if (b.getTableResource("%%Parent") != RES_BOGUS) {
String parentLocaleName = b.getString("%%Parent");
parent = instantiateBundle(baseName, parentLocaleName, root, disableFallback);
} else if (i != -1) {
parent = instantiateBundle(baseName, localeName.substring(0, i), root, disableFallback);
} else if (!localeName.equals(rootLocale)){
parent = instantiateBundle(baseName, rootLocale, root, true);
}
if (!b.equals(parent)){
b.setParent(parent);
}
}
}
return b;
}
UResourceBundle get(String aKey, HashMap table, UResourceBundle requested) {
ICUResourceBundle obj = (ICUResourceBundle)handleGet(aKey, table, requested);
if (obj == null) {
obj = (ICUResourceBundle)getParent();
if (obj != null) {
//call the get method to recursively fetch the resource
obj = (ICUResourceBundle)obj.get(aKey, table, requested);
}
if (obj == null) {
String fullName = ICUResourceBundleReader.getFullName(getBaseName(), getLocaleID());
throw new MissingResourceException(
"Can't find resource for bundle " + fullName + ", key "
+ aKey, this.getClass().getName(), aKey);
}
}
obj.setLoadingStatus(((ICUResourceBundle)requested).getLocaleID());
return obj;
}
protected String localeID;
protected String baseName;
protected ULocale ulocale;
protected ClassLoader loader;
/**
* Access to the bits and bytes of the resource bundle.
* Hides low-level details.
*/
protected ICUResourceBundleReader reader;
/** Data member where the subclasses store the key. */
protected String key;
/** Data member where the subclasses store the offset within resource data. */
protected int resource;
/**
* A resource word value that means "no resource".
* Note: 0xffffffff == -1
* This has the same value as UResourceBundle.NONE, but they are semantically
* different and should be used appropriately according to context:
* NONE means "no type".
* (The type of RES_BOGUS is RES_RESERVED=15 which was defined in ICU4C ures.h.)
*/
public static final int RES_BOGUS = 0xffffffff;
/**
* Resource type constant for aliases;
* internally stores a string which identifies the actual resource
* storing the data (can be in a different resource bundle).
* Resolved internally before delivering the actual resource through the API.
*/
public static final int ALIAS = 3;
/** Resource type constant for tables with 32-bit count, key offsets and values. */
public static final int TABLE32 = 4;
/**
* Resource type constant for tables with 16-bit count, key offsets and values.
* All values are STRING_V2 strings.
*/
public static final int TABLE16 = 5;
/** Resource type constant for 16-bit Unicode strings in formatVersion 2. */
public static final int STRING_V2 = 6;
/**
* Resource type constant for arrays with 16-bit count and values.
* All values are STRING_V2 strings.
*/
public static final int ARRAY16 = 9;
/**
* Create a bundle using a reader.
* @param baseName The name for the bundle.
* @param localeID The locale identification.
* @param root The ClassLoader object root.
* @return the new bundle
*/
public static ICUResourceBundle createBundle(String baseName, String localeID, ClassLoader root) {
ICUResourceBundleReader reader = ICUResourceBundleReader.getReader(baseName, localeID, root);
if (reader == null) {
// could not open the .res file
return null;
}
return getBundle(reader, baseName, localeID, root);
}
protected String getLocaleID() {
return localeID;
}
protected String getBaseName() {
return baseName;
}
public ULocale getULocale() {
return ulocale;
}
public UResourceBundle getParent() {
return (UResourceBundle) parent;
}
protected void setParent(ResourceBundle parent) {
this.parent = parent;
}
public String getKey() {
return key;
}
private static final int[] gPublicTypes = new int[] {
STRING,
BINARY,
TABLE,
ALIAS,
TABLE, /* TABLE32 */
TABLE, /* TABLE16 */
STRING, /* STRING_V2 */
INT,
ARRAY,
ARRAY, /* ARRAY16 */
NONE,
NONE,
NONE,
NONE,
INT_VECTOR,
NONE
};
public int getType() {
return gPublicTypes[ICUResourceBundleReader.RES_GET_TYPE(resource)];
}
/**
* Get the noFallback flag specified in the loaded bundle.
* @return The noFallback flag.
*/
private boolean getNoFallback() {
return reader.getNoFallback();
}
private static ICUResourceBundle getBundle(ICUResourceBundleReader reader,
String baseName, String localeID,
ClassLoader loader) {
ICUResourceBundleImpl bundle;
int rootRes = reader.getRootResource();
if(gPublicTypes[ICUResourceBundleReader.RES_GET_TYPE(rootRes)] == TABLE) {
bundle = new ICUResourceBundleImpl.ResourceTable(reader, null, "", rootRes, null);
} else {
throw new IllegalStateException("Invalid format error");
}
bundle.baseName = baseName;
bundle.localeID = localeID;
bundle.ulocale = new ULocale(localeID);
bundle.loader = loader;
UResourceBundle alias = bundle.handleGetImpl("%%ALIAS", null, bundle, null, null); // handleGet will cache the bundle with no parent set
if(alias != null) {
return (ICUResourceBundle)UResourceBundle.getBundleInstance(baseName, alias.getString());
} else {
return bundle;
}
}
// constructor for inner classes
protected ICUResourceBundle(ICUResourceBundleReader reader, String key, String resPath, int resource,
ICUResourceBundle container) {
this.reader = reader;
this.key = key;
this.resPath = resPath;
this.resource = resource;
if(container != null) {
baseName = container.baseName;
localeID = container.localeID;
ulocale = container.ulocale;
loader = container.loader;
this.parent = container.parent;
}
}
private String getAliasValue(int res) {
String result = reader.getAlias(res);
return result != null ? result : "";
}
private static final char RES_PATH_SEP_CHAR = '/';
private static final String RES_PATH_SEP_STR = "/";
private static final String ICUDATA = "ICUDATA";
private static final char HYPHEN = '-';
private static final String LOCALE = "LOCALE";
protected ICUResourceBundle findResource(String key,
String resPath,
int _resource,
HashMap table,
UResourceBundle requested) {
ClassLoader loaderToUse = loader;
String locale = null, keyPath = null;
String bundleName;
String rpath = getAliasValue(_resource);
if (table == null) {
table = new HashMap();
}
if (table.get(rpath) != null) {
throw new IllegalArgumentException(
"Circular references in the resource bundles");
}
table.put(rpath, "");
if (rpath.indexOf(RES_PATH_SEP_CHAR) == 0) {
int i = rpath.indexOf(RES_PATH_SEP_CHAR, 1);
int j = rpath.indexOf(RES_PATH_SEP_CHAR, i + 1);
bundleName = rpath.substring(1, i);
if (j < 0) {
locale = rpath.substring(i + 1);
// if key path is not available,
// use the given key path
keyPath = resPath;
} else {
locale = rpath.substring(i + 1, j);
keyPath = rpath.substring(j + 1, rpath.length());
}
//there is a path included
if (bundleName.equals(ICUDATA)) {
bundleName = ICU_BASE_NAME;
loaderToUse = ICU_DATA_CLASS_LOADER;
}else if(bundleName.indexOf(ICUDATA)>-1){
int idx = bundleName.indexOf(HYPHEN);
if(idx>-1){
bundleName = ICU_BASE_NAME+RES_PATH_SEP_STR+bundleName.substring(idx+1,bundleName.length());
loaderToUse = ICU_DATA_CLASS_LOADER;
}
}
} else {
//no path start with locale
int i = rpath.indexOf(RES_PATH_SEP_CHAR);
if (i != -1) {
locale = rpath.substring(0, i);
keyPath = rpath.substring(i + 1);
} else {
locale = rpath;
// if key path is not available,
// use the given key path
keyPath = resPath;
}
bundleName = baseName;
}
ICUResourceBundle bundle = null;
ICUResourceBundle sub = null;
if(bundleName.equals(LOCALE)){
bundleName = baseName;
keyPath = rpath.substring(LOCALE.length() + 2/* prepending and appending / */, rpath.length());
locale = ((ICUResourceBundle)requested).getLocaleID();
// Get the top bundle of the requested bundle
bundle = (ICUResourceBundle)getBundleInstance(bundleName, locale, loaderToUse, false);
if (bundle != null) {
sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null);
// TODO
// The resPath of the resolved bundle should reflect the resource path
// requested by caller. However, overwriting resPath here will affect cached
// resource instance. The resPath is exposed by ICUResourceBundle#getResPath,
// but there are no call sites in ICU (and ICUResourceBundle is an implementation
// class). We may create a safe clone to overwrite the resPath field, but
// it has no benefit at least for now. -Yoshito
//if (sub != null) {
// sub.resPath = resPath;
//}
}
}else{
if (locale == null) {
// {dlf} must use requestor's class loader to get resources from same jar
bundle = (ICUResourceBundle) getBundleInstance(bundleName, "",
loaderToUse, false);
} else {
bundle = (ICUResourceBundle) getBundleInstance(bundleName, locale,
loaderToUse, false);
}
StringTokenizer st = new StringTokenizer(keyPath, "/");
ICUResourceBundle current = bundle;
while (st.hasMoreTokens()) {
String subKey = st.nextToken();
sub = (ICUResourceBundle)current.get(subKey, table, requested);
if (sub == null) {
break;
}
current = sub;
}
// TODO
// See the comments above.
//if (sub != null) {
// sub.resPath = resPath;
//}
}
if (sub == null) {
throw new MissingResourceException(localeID, baseName, key);
}
return sub;
}
// Resource bundle lookup cache, which may be used by subclasses
// which have nested resources
protected ICUCache