blob: 2aaaa7c30245e60ada91602b64e5235297c2e994 [file] [log] [blame]
/*
******************************************************************************
* Copyright (C) 2004, International Business Machines Corporation and *
* others. All Rights Reserved. *
******************************************************************************
*/
package com.ibm.icu.impl;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
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.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.Vector;
import com.ibm.icu.util.StringTokenizer;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
import com.ibm.icu.util.UResourceTypeMismatchException;
import com.ibm.icu.util.VersionInfo;
public abstract class ICUResourceBundle extends UResourceBundle{
/**
* The data path to be used with getBundleInstance API
* @draft ICU 3.0
*/
protected static final String ICU_DATA_PATH = "com/ibm/icu/impl/";
/**
* The data path to be used with getBundleInstance API
* @draft ICU 3.0
*/
public static final String ICU_BUNDLE = "data/icudt"+VersionInfo.ICU_DATA_VERSION;
/**
* The base name of ICU data to be used with getBundleInstance API
* @draft ICU 3.0
*/
public static final String ICU_BASE_NAME= ICU_DATA_PATH+ICU_BUNDLE;
/**
* The base name of collation data to be used with getBundleInstance API
* @draft ICU 3.0
*/
public static final String ICU_COLLATION_BASE_NAME = ICU_BASE_NAME + "/coll";
/**
* The class loader constant to be used with getBundleInstance API
* @draft ICU 3.0
*/
public static final ClassLoader ICU_DATA_CLASS_LOADER = ICUData.class.getClassLoader();
/**
* The name of the resource containing the installed locales
* @draft ICU 3.0
*/
protected static final String INSTALLED_LOCALES = "InstalledLocales";
/**
* Resource type constant for "no resource".
* @draft ICU 3.0
*/
public static final int NONE=-1;
/**
* Resource type constant for strings.
* @draft ICU 3.0
*/
public static final int STRING=0;
/**
* Resource type constant for binary data.
* @draft ICU 3.0
*/
public static final int BINARY=1;
/**
* Resource type constant for tables of key-value pairs.
* @draft ICU 3.0
*/
public static final int TABLE=2;
/**
* 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.
* @draft ICU 3.0
* @internal
*/
protected static final int ALIAS=3;
/**
* Internal use only.
* Alternative resource type constant for tables of key-value pairs.
* Never returned by getType().
* @internal
* @draft ICU 3.0
*/
protected static final int TABLE32=4;
/**
* Resource type constant for a single 28-bit integer, interpreted as
* signed or unsigned by the getInt() function.
* @see #getInt
* @draft ICU 3.0
*/
public static final int INT=7;
/**
* Resource type constant for arrays of resources.
* @draft ICU 3.0
*/
public static final int ARRAY=8;
/**
* Resource type constant for vectors of 32-bit integers.
* @see #getIntVector
* @draft ICU 3.0
*/
public static final int INT_VECTOR=14;
/**
* Return the version number associated with this UResourceBundle as an
* VersionInfo object.
* @return VersionInfo object containing the version of the bundle
* @draft ICU 3.0
*/
public VersionInfo getVersion(){
return null;
}
/**
* Returns a string from a string resource type
*
* @return a string
* @see #getBinary
* @see #getIntVector
* @see #getInt
* @throws MissingResourceException
* @throws UResourceTypeMismatchException
* @draft ICU 3.0
*/
public String getString(){
throw new UResourceTypeMismatchException("");
}
/**
* @internal ICU 3.0
*/
public String[] getStringArray(){
throw new UResourceTypeMismatchException("");
}
/**
* Returns a string from a string resource type
* @param key The key whose values needs to be fetched
* @return a string
* @see #getBinary
* @see #getIntVector
* @see #getInt
* @throws MissingResourceException
* @throws UResourceTypeMismatchException
* @draft ICU 3.0
*/
// public String getString(String key) {
// throw new UResourceTypeMismatchException("");
// }
/**
* Returns a binary data from a binary resource.
*
* @return a pointer to a chuck of unsigned bytes which live in a memory mapped/DLL file.
* @see #getIntVector
* @see #getInt
* @throws MissingResourceException
* @throws UResourceTypeMismatchException
* @draft ICU 3.0
*/
public ByteBuffer getBinary(){
throw new UResourceTypeMismatchException("");
}
/**
* Returns a 32 bit integer array from a resource.
*
* @return a pointer to a chunk of unsigned bytes which live in a memory mapped/DLL file.
* @see #getBinary
* @see #getInt
* @throws MissingResourceException
* @throws UResourceTypeMismatchException
* @draft ICU 3.0
*/
public int[] getIntVector(){
throw new UResourceTypeMismatchException("");
}
/**
* Returns a signed integer from a resource.
*
* @return an integer value
* @see #getIntVector
* @see #getBinary
* @throws MissingResourceException
* @throws UResourceTypeMismatchException
* @stable ICU 2.0
*/
public int getInt() {
throw new UResourceTypeMismatchException("");
}
/**
* Returns a unsigned integer from a resource.
* This integer is originally 28 bit and the sign gets propagated.
*
* @return an integer value
* @see #getIntVector
* @see #getBinary
* @throws MissingResourceException
* @throws UResourceTypeMismatchException
* @stable ICU 2.0
*/
public int getUInt() {
throw new UResourceTypeMismatchException("");
}
/**
* Returns the size of a resource. Size for scalar types is always 1,
* and for vector/table types is the number of child resources.
* <br><b><font color='red'>Warning: </font></b> Integer array is treated as a scalar type. There are no
* APIs to access individual members of an integer array. It
* is always returned as a whole.
* @return number of resources in a given resource.
* @draft ICU 3.0
*/
public int getSize(){
return size;
}
/**
* Returns the type of a resource.
* Available types are {@link #INT INT}, {@link #ARRAY ARRAY},
* {@link #BINARY BINARY}, {@link #INT_VECTOR INT_VECTOR},
* {@link #STRING STRING}, {@link #TABLE TABLE}.
*
* @return type of the given resource.
* @draft ICU 3.0
*/
public int getType(){
return type;
}
/**
* Returns the key associated with a given resource. Not all the resources have a key - only
* those that are members of a table.
* @return a key associated to this resource, or NULL if it doesn't have a key
* @draft ICU 3.0
*/
public String getKey(){
return key;
}
/**
* Returns the iterator which iterates over this
* resource bundle
* @draft ICU 3.0
*/
public ICUResourceBundleIterator getIterator(){
return new ICUResourceBundleIterator(this);
}
/**
* Returns the resource in a given resource at the specified index.
*
* @param index an index to the wanted resource.
* @return the sub resource UResourceBundle object
* @throws IndexOutOfBoundsException
* @draft ICU 3.0
*/
public ICUResourceBundle get(int index){
ICUResourceBundle obj = handleGet(index);
if (obj == null) {
obj = (ICUResourceBundle)getParent();
if ( obj!= null) {
obj = obj.get(index);
}
if (obj == null)
throw new MissingResourceException("Can't find resource for bundle "
+this.getClass().getName()
+", key "+getKey(),
this.getClass().getName(),
getKey());
}
return obj;
}
// abstract UResourceBundle handleGetInt(int index);
/**
* Returns a resource in a given resource that has a given key.
*
* @param key a key associated with the wanted resource
* @return a resource bundle object representing rhe resource
* @throws MissingResourceException
* @draft ICU 3.0
*/
public ICUResourceBundle get(String key){
ICUResourceBundle obj = handleGet(key);
if (obj == null) {
obj = (ICUResourceBundle)getParent();
if ( obj!= null) {
obj = obj.get(key);
}
if (obj == null){
String fullName = ICUResourceBundleReader.getFullName(getBaseName(), getLocaleID());
throw new MissingResourceException("Can't find resource for bundle "
+fullName
+", key "+key,
this.getClass().getName(),
key);
}
}
return obj;
}
/**
* Returns the string in a given resource at the specified index.
*
* @param index an index to the wanted string.
* @return a string which lives in the resource.
* @throws IndexOutOfBoundsException
* @throws UResourceTypeMismatchException
* @draft ICU 3.0
*/
public String getString(int index){
ICUResourceBundle temp = get(index);
if(temp.getType()==STRING){
return temp.getString();
}
throw new UResourceTypeMismatchException("");
}
/**
* Returns the parent bundle of this bundle
* @return UResourceBundle the parent of this bundle. Returns null if none
* @draft ICU 3.0
*/
public abstract UResourceBundle getParent();
/**
* 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 fillinIsAvailable 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.
* @return the locale
* @internal ICU 3.0
*/
public static final ULocale getFunctionalEquivalent(String baseName, String resName, String keyword,
ULocale locID, boolean fillinIsAvailable[]){
String kwVal = locID.getKeywordValue(keyword);
String baseLoc = locID.getBaseName();
String defStr = null;
ULocale parent = new ULocale(baseLoc);
ULocale found = locID;
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(fillinIsAvailable != null) {
fillinIsAvailable[0] = true;
}
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);
found = r.getULocale();
if(fillinIsAvailable != null) {
if (!found.equals(parent)) {
fillinIsAvailable[0] = false;
}
}
// determine in which locale (if any) the currently relevant 'default' is
do {
try {
ICUResourceBundle irb = 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.
}
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 = r.get(resName);
/* UResourceBundle urb = */irb.get(kwVal);
fullBase = r.getULocale(); // If the get() completed, we have the full base locale
} catch (MissingResourceException t) {
// Ignore error,
}
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 = r.get(resName);
/*UResourceBundle urb =*/ irb.get(kwVal);
// if we didn't fail before this..
fullBase = r.getULocale();
} catch (MissingResourceException t) {
// Ignore error, continue search.
}
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(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();
Object s;
while(e.hasMoreElements()) {
s= e.nextElement();
if ((s instanceof String) && !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 (String[])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:
* <code>
* UResourceBundle bundle = UResourceBundle.getBundleInstance("de__PHONEBOOK");
* ICUResourceBundle result = null;
* if(bundle instanceof ICUListResourceBundle){
* result = ((ICUListResourceBundle) bundle).getWithFallback("collations/default");
* }
* </code>
* @param path The path to the required resource key
* @return resource represented by the key
* @exception MissingResourceException
*/
public ICUResourceBundle getWithFallback(String path) throws MissingResourceException {
ICUResourceBundle result = null;
ICUResourceBundle actualBundle = this;
// now recuse to pick up sub levels of the items
result = findResourceWithFallback(path, actualBundle);
if(result == null){
throw new MissingResourceException("Can't find resource for bundle "
+this.getClass().getName()
+", key "+getType(),
path,
getKey());
}
return result;
}
// will throw type mismatch exception if the resource is not a string
public String getStringWithFallback(String path) throws MissingResourceException {
return getWithFallback(path).getString();
}
/**
* Gets 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 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
*
*/
public static UResourceBundle getBundleInstance(String baseName, String localeName, boolean disableFallback){
return instantiateBundle(baseName, localeName, ICU_DATA_CLASS_LOADER, disableFallback);
}
/**
* 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) {
return getAvailEntry(bundlePrefix).getLocaleNameSet();
}
/**
* Return a set of the locale names supported by a collection of resource bundles.
*/
public static Set getAvailableLocaleNameSet() {
return getAvailableLocaleNameSet(ICU_BASE_NAME);
}
/**
* Get the set of Locales installed in the specified bundles.
* @return the list of available locales
* @draft ICU 3.0
*/
public static final ULocale[] getAvailableULocales(String baseName){
return getAvailEntry(baseName).getULocaleList();
}
/**
* Get the set of ULocales installed the base bundle.
* @return the list of available locales
* @draft ICU 3.0
*/
public static final ULocale[] getAvailableULocales(){
return getAvailableULocales(ICU_BASE_NAME);
}
/**
* Get the set of Locales installed in the specified bundles.
* @return the list of available locales
* @draft ICU 3.0
*/
public static final Locale[] getAvailableLocales(String baseName){
return getAvailEntry(baseName).getLocaleList();
}
/**
* Get the set of Locales installed the base bundle.
* @return the list of available locales
* @draft ICU 3.0
*/
public static final Locale[] getAvailableLocales(){
return getAvailEntry(ICU_BASE_NAME).getLocaleList();
}
/**
*
* @param ulocales
* @return
* @draft ICU 3.0
*/
public static final Locale[] getLocaleList(ULocale[] ulocales){
ArrayList list = new ArrayList();
for(int i=0; i< ulocales.length; i++){
// if the ULocale does not contain a script code
// only then convert it to a Locale object
if(ulocales[i].getScript().length()==0){
list.add(new Locale(ulocales[i].getLanguage(),
ulocales[i].getCountry(),
ulocales[i].getVariant()));
}
}
Locale[] locales = new Locale[list.size()];
for(int i=0; i<locales.length; i++){
locales[i] = (Locale)list.get(i);
}
return locales;
}
public Enumeration getKeys(){
Vector keys = new Vector();
ICUResourceBundle item =null;
for(int i=0; i<size; i++){
item = get(i);
keys.add(item.getKey());
}
return keys.elements();
}
public static ICUResourceBundle createBundle(String baseName, String localeID, ClassLoader root) {
return ICUResourceBundleImpl.createBundle(baseName, localeID, root);
}
//====== protected members ==============
protected int type = NONE;
protected String key;
protected int size = 1;
protected String resPath;
protected long resource = RES_BOGUS;
protected boolean isTopLevel = false;
protected static final long UNSIGNED_INT_MASK = 0xffffffffL;
protected static final long RES_BOGUS = 0xffffffff;
protected ICUResourceBundle handleGet(String key, HashMap table){
throw new UResourceTypeMismatchException("");
}
protected ICUResourceBundle handleGet(int index, HashMap table){
throw new UResourceTypeMismatchException("");
}
/**
* 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();
}
protected Object handleGetObject(String key){
Object obj = handleGetObjectImpl(key);
if (obj == null) {
UResourceBundle parent = getParent();
if ( parent!= null) {
obj = parent.getObject(key);
}
if (obj == null)
throw new MissingResourceException("Can't find resource for bundle "
+this.getClass().getName()
+", key "+key,
this.getClass().getName(),
key);
}
return obj;
}
private Object handleGetObjectImpl(String key){
if(getType()==STRING){
return getString();
}
ICUResourceBundle obj = handleGet(key);
if(obj!=null){
if(obj.getType()==STRING){
return obj.getString();
}
try{
if(obj.getType()==ARRAY){
return obj.handleGetStringArray();
}
}catch(UResourceTypeMismatchException ex){
return obj;
}
}
return obj;
}
protected ICUResourceBundle handleGet(int index){
return null;
}
protected ICUResourceBundle handleGet(String key){
return null;
}
protected String[] handleGetStringArray(){
return null;
}
// ========== privates ==========
private static final String ICU_RESOURCE_INDEX = "res_index";
private static final String DEFAULT_TAG = "default";
// Flag for enabling/disabling debugging code
private static final boolean DEBUG = ICUDebug.enabled("localedata");
// Cache for getAvailableLocales
private static SoftReference GET_AVAILABLE_CACHE;
private static final ULocale[] createULocaleList(String baseName, ClassLoader root){
ICUResourceBundle bundle = (ICUResourceBundle) instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
bundle = bundle.get(INSTALLED_LOCALES);
int length = bundle.getSize();
int i = 0;
ULocale[] locales = new ULocale[length];
ICUResourceBundleIterator iter = bundle.getIterator();
iter.reset();
while(iter.hasNext()){
locales[i++] = new ULocale(iter.next().getKey());
}
bundle = null;
return locales;
}
private static final Locale[] createLocaleList(String baseName){
ULocale[] ulocales = getAvailEntry(baseName).getULocaleList();
return getLocaleList(ulocales);
}
private static final String[] createLocaleNameArray(String baseName, ClassLoader root){
ICUResourceBundle bundle = (ICUResourceBundle) instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
bundle = bundle.get(INSTALLED_LOCALES);
int length = bundle.getSize();
int i = 0;
String[] locales = new String[length];
ICUResourceBundleIterator iter = bundle.getIterator();
iter.reset();
while(iter.hasNext()){
locales[i++] = iter.next().getKey();
}
bundle = null;
return locales;
}
private static Set createLocaleNameSet(String baseName) {
try {
String[] locales = createLocaleNameArray(baseName,ICU_DATA_CLASS_LOADER);
HashSet set = new HashSet();
set.addAll(Arrays.asList(locales));
// add the obsolete ids, we really do support them
set.add("iw");
set.add("iw_IL");
set.add("in");
set.add("in_ID");
return Collections.unmodifiableSet(set);
}
catch (MissingResourceException e) {
if (DEBUG){
System.out.println("couldn't find index for bundleName: " + baseName);
Thread.dumpStack();
}
//System.out.println("couldn't find index for bundleName: " + baseName);
//e.printStackTrace();
}
return Collections.EMPTY_SET;
}
/**
* 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 ULocale[] ulocales;
private Locale[] locales;
private Set nameSet;
AvailEntry(String prefix) {
this.prefix = prefix;
}
ULocale[] getULocaleList() {
if (ulocales == null) {
ulocales = createULocaleList(prefix, ICU_DATA_CLASS_LOADER);
}
return ulocales;
}
Locale[] getLocaleList(){
if(locales == null){
locales = createLocaleList(prefix);
}
return locales;
}
Set getLocaleNameSet() {
if (nameSet == null) {
nameSet = createLocaleNameSet(prefix);
}
return nameSet;
}
}
/**
* Stores the locale information in a cache accessed by key (bundle prefix). The
* cached objects are AvailEntries. The cache is held by a SoftReference
* so it can be GC'd.
*/
private static AvailEntry getAvailEntry(String key) {
AvailEntry ae = null;
Map lcache = null;
if (GET_AVAILABLE_CACHE != null) {
lcache = (Map)GET_AVAILABLE_CACHE.get();
if (lcache != null) {
ae = (AvailEntry)lcache.get(key);
}
}
if (ae == null) {
ae = new AvailEntry(key);
if (lcache == null) {
lcache = new HashMap();
lcache.put(key, ae);
GET_AVAILABLE_CACHE = new SoftReference(lcache);
} else {
lcache.put(key, ae);
}
}
return ae;
}
private ICUResourceBundle findResourceWithFallback(String path, ICUResourceBundle actualBundle) {
ICUResourceBundle sub = null;
while (actualBundle != null) {
StringTokenizer st = new StringTokenizer(path, "/");
ICUResourceBundle current = actualBundle;
while (st.hasMoreTokens()) {
String subKey = st.nextToken();
sub = current.handleGet(subKey, null);
if(sub==null){
break;
}
current = sub;
}
if(sub!=null){
//we found it
break;
}
if(actualBundle.resPath.length()!=0){
path = resPath+"/"+path;
}
// if not try the parent bundle
actualBundle = (ICUResourceBundle) actualBundle.getParent();
}
return sub;
}
}