2 * *****************************************************************************
3 * Copyright (C) 2005-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 * *****************************************************************************
8 package com.ibm.icu.impl;
10 import java.io.BufferedReader;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.InputStreamReader;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Collections;
18 import java.util.Enumeration;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.MissingResourceException;
24 import java.util.ResourceBundle;
26 import java.util.StringTokenizer;
28 import com.ibm.icu.impl.URLHandler.URLVisitor;
29 import com.ibm.icu.util.ULocale;
30 import com.ibm.icu.util.UResourceBundle;
31 import com.ibm.icu.util.UResourceBundleIterator;
32 import com.ibm.icu.util.VersionInfo;
34 public class ICUResourceBundle extends UResourceBundle {
36 * The data path to be used with getBundleInstance API
38 protected static final String ICU_DATA_PATH = "com/ibm/icu/impl/";
40 * The data path to be used with getBundleInstance API
42 public static final String ICU_BUNDLE = "data/icudt" + VersionInfo.ICU_DATA_VERSION_PATH;
45 * The base name of ICU data to be used with getBundleInstance API
47 public static final String ICU_BASE_NAME = ICU_DATA_PATH + ICU_BUNDLE;
50 * The base name of collation data to be used with getBundleInstance API
52 public static final String ICU_COLLATION_BASE_NAME = ICU_BASE_NAME + "/coll";
55 * The base name of rbbi data to be used with getData API
57 public static final String ICU_BRKITR_NAME = "/brkitr";
60 * The base name of rbbi data to be used with getBundleInstance API
62 public static final String ICU_BRKITR_BASE_NAME = ICU_BASE_NAME + ICU_BRKITR_NAME;
65 * The base name of rbnf data to be used with getBundleInstance API
67 public static final String ICU_RBNF_BASE_NAME = ICU_BASE_NAME + "/rbnf";
70 * The base name of transliterator data to be used with getBundleInstance API
72 public static final String ICU_TRANSLIT_BASE_NAME = ICU_BASE_NAME + "/translit";
74 public static final String ICU_LANG_BASE_NAME = ICU_BASE_NAME + "/lang";
75 public static final String ICU_CURR_BASE_NAME = ICU_BASE_NAME + "/curr";
76 public static final String ICU_REGION_BASE_NAME = ICU_BASE_NAME + "/region";
77 public static final String ICU_ZONE_BASE_NAME = ICU_BASE_NAME + "/zone";
79 private static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205";
82 * The actual path of the resource
84 protected String resPath;
87 * The class loader constant to be used with getBundleInstance API
89 public static final ClassLoader ICU_DATA_CLASS_LOADER;
91 ClassLoader loader = ICUData.class.getClassLoader();
93 loader = Utility.getFallbackClassLoader();
95 ICU_DATA_CLASS_LOADER = loader;
99 * The name of the resource containing the installed locales
101 protected static final String INSTALLED_LOCALES = "InstalledLocales";
103 public static final int FROM_FALLBACK = 1, FROM_ROOT = 2, FROM_DEFAULT = 3, FROM_LOCALE = 4;
105 private int loadingStatus = -1;
107 public void setLoadingStatus(int newStatus) {
108 loadingStatus = newStatus;
111 * Returns the loading status of a particular resource.
113 * @return FROM_FALLBACK if the resource is fetched from fallback bundle
114 * FROM_ROOT if the resource is fetched from root bundle.
115 * FROM_DEFAULT if the resource is fetched from the default locale.
117 public int getLoadingStatus() {
118 return loadingStatus;
121 public void setLoadingStatus(String requestedLocale){
122 String locale = getLocaleID();
123 if(locale.equals("root")) {
124 setLoadingStatus(FROM_ROOT);
125 } else if(locale.equals(requestedLocale)) {
126 setLoadingStatus(FROM_LOCALE);
128 setLoadingStatus(FROM_FALLBACK);
133 * Returns the respath of this bundle
134 * @return the respath of the bundle
136 public String getResPath(){
141 * Returns a functionally equivalent locale, considering keywords as well, for the specified keyword.
142 * @param baseName resource specifier
143 * @param resName top level resource to consider (such as "collations")
144 * @param keyword a particular keyword to consider (such as "collation" )
145 * @param locID The requested locale
146 * @param isAvailable If non-null, 1-element array of fillin parameter that indicates whether the
147 * requested locale was available. The locale is defined as 'available' if it physically
148 * exists within the specified tree and included in 'InstalledLocales'.
149 * @param omitDefault if true, omit keyword and value if default.
150 * 'de_DE\@collation=standard' -> 'de_DE'
154 public static final ULocale getFunctionalEquivalent(String baseName, ClassLoader loader,
155 String resName, String keyword, ULocale locID,
156 boolean isAvailable[], boolean omitDefault) {
157 String kwVal = locID.getKeywordValue(keyword);
158 String baseLoc = locID.getBaseName();
159 String defStr = null;
160 ULocale parent = new ULocale(baseLoc);
161 ULocale defLoc = null; // locale where default (found) resource is
162 boolean lookForDefault = false; // true if kwVal needs to be set
163 ULocale fullBase = null; // base locale of found (target) resource
164 int defDepth = 0; // depth of 'default' marker
165 int resDepth = 0; // depth of found resource;
167 if ((kwVal == null) || (kwVal.length() == 0)
168 || kwVal.equals(DEFAULT_TAG)) {
169 kwVal = ""; // default tag is treated as no keyword
170 lookForDefault = true;
173 // Check top level locale first
174 ICUResourceBundle r = null;
176 r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
177 if (isAvailable != null) {
178 isAvailable[0] = false;
179 ULocale[] availableULocales = getAvailEntry(baseName, loader).getULocaleList();
180 for (int i = 0; i < availableULocales.length; i++) {
181 if (parent.equals(availableULocales[i])) {
182 isAvailable[0] = true;
187 // determine in which locale (if any) the currently relevant 'default' is
190 ICUResourceBundle irb = (ICUResourceBundle) r.get(resName);
191 defStr = irb.getString(DEFAULT_TAG);
192 if (lookForDefault == true) {
194 lookForDefault = false;
196 defLoc = r.getULocale();
197 } catch (MissingResourceException t) {
198 // Ignore error and continue search.
200 if (defLoc == null) {
201 r = (ICUResourceBundle) r.getParent();
204 } while ((r != null) && (defLoc == null));
206 // Now, search for the named resource
207 parent = new ULocale(baseLoc);
208 r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
209 // determine in which locale (if any) the named resource is located
212 ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
213 /* UResourceBundle urb = */irb.get(kwVal);
214 fullBase = irb.getULocale();
215 // If the get() completed, we have the full base locale
216 // If we fell back to an ancestor of the old 'default',
217 // we need to re calculate the "default" keyword.
218 if ((fullBase != null) && ((resDepth) > defDepth)) {
219 defStr = irb.getString(DEFAULT_TAG);
220 defLoc = r.getULocale();
223 } catch (MissingResourceException t) {
226 if (fullBase == null) {
227 r = (ICUResourceBundle) r.getParent();
230 } while ((r != null) && (fullBase == null));
232 if (fullBase == null && // Could not find resource 'kwVal'
233 (defStr != null) && // default was defined
234 !defStr.equals(kwVal)) { // kwVal is not default
235 // couldn't find requested resource. Fall back to default.
236 kwVal = defStr; // Fall back to default.
237 parent = new ULocale(baseLoc);
238 r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
240 // determine in which locale (if any) the named resource is located
243 ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
244 UResourceBundle urb = irb.get(kwVal);
246 // if we didn't fail before this..
247 fullBase = r.getULocale();
249 // If the fetched item (urb) is in a different locale than our outer locale (r/fullBase)
250 // then we are in a 'fallback' situation. treat as a missing resource situation.
251 if(!fullBase.toString().equals(urb.getLocale().toString())) {
252 fullBase = null; // fallback condition. Loop and try again.
255 // If we fell back to an ancestor of the old 'default',
256 // we need to re calculate the "default" keyword.
257 if ((fullBase != null) && ((resDepth) > defDepth)) {
258 defStr = irb.getString(DEFAULT_TAG);
259 defLoc = r.getULocale();
262 } catch (MissingResourceException t) {
263 // Ignore error, continue search.
265 if (fullBase == null) {
266 r = (ICUResourceBundle) r.getParent();
269 } while ((r != null) && (fullBase == null));
272 if (fullBase == null) {
273 throw new MissingResourceException(
274 "Could not find locale containing requested or default keyword.",
275 baseName, keyword + "=" + kwVal);
279 && defStr.equals(kwVal) // if default was requested and
280 && resDepth <= defDepth) { // default was set in same locale or child
281 return fullBase; // Keyword value is default - no keyword needed in locale
283 return new ULocale(fullBase.toString() + "@" + keyword + "=" + kwVal);
288 * Given a tree path and keyword, return a string enumeration of all possible values for that keyword.
289 * @param baseName resource specifier
290 * @param keyword a particular keyword to consider, must match a top level resource name
291 * within the tree. (i.e. "collations")
294 public static final String[] getKeywordValues(String baseName, String keyword) {
295 Set<String> keywords = new HashSet<String>();
296 ULocale locales[] = createULocaleList(baseName, ICU_DATA_CLASS_LOADER);
299 for (i = 0; i < locales.length; i++) {
301 UResourceBundle b = UResourceBundle.getBundleInstance(baseName, locales[i]);
302 // downcast to ICUResourceBundle?
303 ICUResourceBundle irb = (ICUResourceBundle) (b.getObject(keyword));
304 Enumeration<String> e = irb.getKeys();
305 while (e.hasMoreElements()) {
306 String s = e.nextElement();
307 if (!DEFAULT_TAG.equals(s)) {
308 // don't add 'default' items
312 } catch (Throwable t) {
313 //System.err.println("Error in - " + new Integer(i).toString()
314 // + " - " + t.toString());
315 // ignore the err - just skip that resource
318 return keywords.toArray(new String[0]);
322 * This method performs multilevel fallback for fetching items from the
323 * bundle e.g: If resource is in the form de__PHONEBOOK{ collations{
324 * default{ "phonebook"} } } If the value of "default" key needs to be
325 * accessed, then do: <code>
326 * UResourceBundle bundle = UResourceBundle.getBundleInstance("de__PHONEBOOK");
327 * ICUResourceBundle result = null;
328 * if(bundle instanceof ICUResourceBundle){
329 * result = ((ICUResourceBundle) bundle).getWithFallback("collations/default");
333 * @param path The path to the required resource key
334 * @return resource represented by the key
335 * @exception MissingResourceException If a resource was not found.
337 public ICUResourceBundle getWithFallback(String path) throws MissingResourceException {
338 ICUResourceBundle result = null;
339 ICUResourceBundle actualBundle = this;
341 // now recurse to pick up sub levels of the items
342 result = findResourceWithFallback(path, actualBundle, null);
344 if (result == null) {
345 throw new MissingResourceException(
346 "Can't find resource for bundle "
347 + this.getClass().getName() + ", key " + getType(),
351 if ( result.getType() == ICUResourceBundle.STRING && result.getString().equals(NO_INHERITANCE_MARKER)) {
352 throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER",path,getKey());
358 public ICUResourceBundle at(int index) {
359 return (ICUResourceBundle) handleGet(index, null, this);
362 public ICUResourceBundle at(String key) {
363 // don't ever presume the key is an int in disguise, like ResourceArray does.
364 if (this instanceof ICUResourceBundleImpl.ResourceTable) {
365 return (ICUResourceBundle) handleGet(key, null, this);
371 public ICUResourceBundle findTopLevel(int index) {
372 return (ICUResourceBundle) super.findTopLevel(index);
376 public ICUResourceBundle findTopLevel(String aKey) {
377 return (ICUResourceBundle) super.findTopLevel(aKey);
381 * Like getWithFallback, but returns null if the resource is not found instead of
382 * throwing an exception.
383 * @param path the path to the resource
384 * @return the resource, or null
386 public ICUResourceBundle findWithFallback(String path) {
387 return findResourceWithFallback(path, this, null);
390 // will throw type mismatch exception if the resource is not a string
391 public String getStringWithFallback(String path) throws MissingResourceException {
392 return getWithFallback(path).getString();
396 * Return a set of the locale names supported by a collection of resource
399 * @param bundlePrefix the prefix of the resource bundles to use.
401 public static Set<String> getAvailableLocaleNameSet(String bundlePrefix, ClassLoader loader) {
402 return getAvailEntry(bundlePrefix, loader).getLocaleNameSet();
406 * Return a set of all the locale names supported by a collection of
409 public static Set<String> getFullLocaleNameSet() {
410 return getFullLocaleNameSet(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
414 * Return a set of all the locale names supported by a collection of
417 * @param bundlePrefix the prefix of the resource bundles to use.
419 public static Set<String> getFullLocaleNameSet(String bundlePrefix, ClassLoader loader) {
420 return getAvailEntry(bundlePrefix, loader).getFullLocaleNameSet();
424 * Return a set of the locale names supported by a collection of resource
427 public static Set<String> getAvailableLocaleNameSet() {
428 return getAvailableLocaleNameSet(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
432 * Get the set of Locales installed in the specified bundles.
433 * @return the list of available locales
435 public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader) {
436 return getAvailEntry(baseName, loader).getULocaleList();
440 * Get the set of ULocales installed the base bundle.
441 * @return the list of available locales
443 public static final ULocale[] getAvailableULocales() {
444 return getAvailableULocales(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
448 * Get the set of Locales installed in the specified bundles.
449 * @return the list of available locales
451 public static final Locale[] getAvailableLocales(String baseName, ClassLoader loader) {
452 return getAvailEntry(baseName, loader).getLocaleList();
456 * Get the set of Locales installed the base bundle.
457 * @return the list of available locales
459 public static final Locale[] getAvailableLocales() {
460 return getAvailEntry(ICU_BASE_NAME, ICU_DATA_CLASS_LOADER).getLocaleList();
464 * Convert a list of ULocales to a list of Locales. ULocales with a script code will not be converted
465 * since they cannot be represented as a Locale. This means that the two lists will <b>not</b> match
466 * one-to-one, and that the returned list might be shorter than the input list.
467 * @param ulocales a list of ULocales to convert to a list of Locales.
468 * @return the list of converted ULocales
470 public static final Locale[] getLocaleList(ULocale[] ulocales) {
471 ArrayList<Locale> list = new ArrayList<Locale>(ulocales.length);
472 HashSet<Locale> uniqueSet = new HashSet<Locale>();
473 for (int i = 0; i < ulocales.length; i++) {
474 Locale loc = ulocales[i].toLocale();
475 if (!uniqueSet.contains(loc)) {
480 return list.toArray(new Locale[list.size()]);
484 * Returns the locale of this resource bundle. This method can be used after
485 * a call to getBundle() to determine whether the resource bundle returned
486 * really corresponds to the requested locale or is a fallback.
488 * @return the locale of this resource bundle
490 public Locale getLocale() {
491 return getULocale().toLocale();
495 // ========== privates ==========
496 private static final String ICU_RESOURCE_INDEX = "res_index";
498 private static final String DEFAULT_TAG = "default";
500 // The name of text file generated by ICU4J build script including all locale names
501 // (canonical, alias and root)
502 private static final String FULL_LOCALE_NAMES_LIST = "fullLocaleNames.lst";
504 // Flag for enabling/disabling debugging code
505 private static final boolean DEBUG = ICUDebug.enabled("localedata");
507 private static final ULocale[] createULocaleList(String baseName,
509 // the canned list is a subset of all the available .res files, the idea
510 // is we don't export them
511 // all. gotta be a better way to do this, since to add a locale you have
512 // to update this list,
513 // and it's embedded in our binary resources.
514 ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
516 bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
517 int length = bundle.getSize();
519 ULocale[] locales = new ULocale[length];
520 UResourceBundleIterator iter = bundle.getIterator();
522 while (iter.hasNext()) {
523 String locstr = iter.next().getKey();
524 if (locstr.equals("root")) {
525 locales[i++] = ULocale.ROOT;
527 locales[i++] = new ULocale(locstr);
534 private static final Locale[] createLocaleList(String baseName, ClassLoader loader) {
535 ULocale[] ulocales = getAvailEntry(baseName, loader).getULocaleList();
536 return getLocaleList(ulocales);
539 private static final String[] createLocaleNameArray(String baseName,
541 ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle( baseName, ICU_RESOURCE_INDEX, root, true);
542 bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
543 int length = bundle.getSize();
545 String[] locales = new String[length];
546 UResourceBundleIterator iter = bundle.getIterator();
548 while (iter.hasNext()) {
549 String locstr = iter.next(). getKey();
550 if (locstr.equals("root")) {
551 locales[i++] = ULocale.ROOT.toString();
553 locales[i++] = locstr;
560 private static final List<String> createFullLocaleNameArray(
561 final String baseName, final ClassLoader root) {
563 List<String> list = java.security.AccessController
564 .doPrivileged(new java.security.PrivilegedAction<List<String>>() {
565 public List<String> run() {
566 // WebSphere class loader will return null for a raw
567 // directory name without trailing slash
568 String bn = baseName.endsWith("/")
572 List<String> resList = null;
574 String skipScan = ICUConfig.get("com.ibm.icu.impl.ICUResourceBundle.skipRuntimeLocaleResourceScan", "false");
575 if (!skipScan.equalsIgnoreCase("true")) {
576 // scan available locale resources under the base url first
578 Enumeration<URL> urls = root.getResources(bn);
579 while (urls.hasMoreElements()) {
580 URL url = urls.nextElement();
581 URLHandler handler = URLHandler.get(url);
582 if (handler != null) {
583 final List<String> lst = new ArrayList<String>();
584 URLVisitor v = new URLVisitor() {
585 public void visit(String s) {
586 //TODO: This is ugly hack. We have to figure out how
587 // we can distinguish locale data from others
588 if (s.endsWith(".res")) {
589 String locstr = s.substring(0, s.length() - 4);
590 if (locstr.contains("_") && !locstr.equals("res_index")) {
591 // locale data with country/script contain "_",
592 // except for res_index.res
594 } else if (locstr.length() == 2 || locstr.length() == 3) {
595 // all 2-letter or 3-letter entries are all locale
596 // data at least for now
598 } else if (locstr.equalsIgnoreCase("root")) {
599 // root locale is a special case
600 lst.add(ULocale.ROOT.toString());
605 handler.guide(v, false);
607 if (resList == null) {
608 resList = new ArrayList<String>(lst);
613 if (DEBUG) System.out.println("handler for " + url + " is null");
616 } catch (IOException e) {
617 if (DEBUG) System.out.println("ouch: " + e.getMessage());
622 if (resList == null) {
623 // look for prebuilt full locale names list next
625 InputStream s = root.getResourceAsStream(bn + FULL_LOCALE_NAMES_LIST);
627 resList = new ArrayList<String>();
628 BufferedReader br = new BufferedReader(new InputStreamReader(s, "ASCII"));
630 while ((line = br.readLine()) != null) {
631 if (line.length() != 0 && !line.startsWith("#")) {
632 if (line.equalsIgnoreCase("root")) {
633 resList.add(ULocale.ROOT.toString());
641 } catch (IOException e) {
653 private static Set<String> createFullLocaleNameSet(String baseName, ClassLoader loader) {
654 List<String> list = createFullLocaleNameArray(baseName, loader);
656 if (DEBUG) System.out.println("createFullLocaleNameArray returned null");
657 // Use locale name set as the last resort fallback
658 Set<String> locNameSet = createLocaleNameSet(baseName, loader);
659 String rootLocaleID = ULocale.ROOT.toString();
660 if (!locNameSet.contains(rootLocaleID)) {
661 // We need to add the root locale in the set
662 Set<String> tmp = new HashSet<String>(locNameSet);
663 tmp.add(rootLocaleID);
664 locNameSet = Collections.unmodifiableSet(tmp);
668 Set<String> fullLocNameSet = new HashSet<String>();
669 fullLocNameSet.addAll(list);
670 return Collections.unmodifiableSet(fullLocNameSet);
673 private static Set<String> createLocaleNameSet(String baseName, ClassLoader loader) {
675 String[] locales = createLocaleNameArray(baseName, loader);
677 HashSet<String> set = new HashSet<String>();
678 set.addAll(Arrays.asList(locales));
679 return Collections.unmodifiableSet(set);
680 } catch (MissingResourceException e) {
682 System.out.println("couldn't find index for bundleName: " + baseName);
686 return Collections.emptySet();
690 * Holds the prefix, and lazily creates the Locale[] list or the locale name
693 private static final class AvailEntry {
694 private String prefix;
695 private ClassLoader loader;
696 private volatile ULocale[] ulocales;
697 private volatile Locale[] locales;
698 private volatile Set<String> nameSet;
699 private volatile Set<String> fullNameSet;
701 AvailEntry(String prefix, ClassLoader loader) {
702 this.prefix = prefix;
703 this.loader = loader;
706 ULocale[] getULocaleList() {
707 if (ulocales == null) {
709 if (ulocales == null) {
710 ulocales = createULocaleList(prefix, loader);
716 Locale[] getLocaleList() {
717 if (locales == null) {
719 if (locales == null) {
720 locales = createLocaleList(prefix, loader);
726 Set<String> getLocaleNameSet() {
727 if (nameSet == null) {
729 if (nameSet == null) {
730 nameSet = createLocaleNameSet(prefix, loader);
736 Set<String> getFullLocaleNameSet() {
737 // When there's no prebuilt index, we iterate through the jar files
738 // and read the contents to build it. If many threads try to read the
739 // same jar at the same time, java thrashes. Synchronize here
740 // so that we can avoid this problem. We don't synchronize on the
741 // other methods since they don't do this.
743 // This is the common entry point for access into the code that walks
744 // through the resources, and is cached. So it's a good place to lock
745 // access. Locking in the URLHandler doesn't give us a common object
747 if (fullNameSet == null) {
749 if (fullNameSet == null) {
750 fullNameSet = createFullLocaleNameSet(prefix, loader);
760 * Cache used for AvailableEntry
762 private static CacheBase<String, AvailEntry, ClassLoader> GET_AVAILABLE_CACHE =
763 new SoftCache<String, AvailEntry, ClassLoader>() {
764 protected AvailEntry createInstance(String key, ClassLoader loader) {
765 return new AvailEntry(key, loader);
770 * Stores the locale information in a cache accessed by key (bundle prefix).
771 * The cached objects are AvailEntries. The cache is implemented by SoftCache
774 private static AvailEntry getAvailEntry(String key, ClassLoader loader) {
775 return GET_AVAILABLE_CACHE.getInstance(key, loader);
778 protected static final ICUResourceBundle findResourceWithFallback(String path,
779 UResourceBundle actualBundle, UResourceBundle requested) {
780 ICUResourceBundle sub = null;
781 if (requested == null) {
782 requested = actualBundle;
785 ICUResourceBundle base = (ICUResourceBundle) actualBundle;
786 String basePath = ((ICUResourceBundle)actualBundle).resPath.length() > 0 ?
787 ((ICUResourceBundle)actualBundle).resPath : "";
789 while (base != null) {
790 if (path.indexOf('/') == -1) { // skip the tokenizer
791 sub = (ICUResourceBundle) base.handleGet(path, null, requested);
796 ICUResourceBundle currentBase = base;
797 StringTokenizer st = new StringTokenizer(path, "/");
798 while (st.hasMoreTokens()) {
799 String subKey = st.nextToken();
800 sub = ICUResourceBundle.findResourceWithFallback(subKey, currentBase, requested);
811 // if not try the parent bundle - note, getParent() returns the bundle root
812 base = (ICUResourceBundle)base.getParent();
813 path = basePath.length() > 0 ? basePath + "/" + path : path;
817 sub.setLoadingStatus(((ICUResourceBundle)requested).getLocaleID());
822 public boolean equals(Object other) {
826 if (other instanceof ICUResourceBundle) {
827 ICUResourceBundle o = (ICUResourceBundle) other;
828 if (getBaseName().equals(o.getBaseName())
829 && getLocaleID().equals(o.getLocaleID())) {
836 public int hashCode() {
837 assert false : "hashCode not designed";
841 // This method is for super class's instantiateBundle method
842 public static UResourceBundle getBundleInstance(String baseName, String localeID,
843 ClassLoader root, boolean disableFallback){
844 UResourceBundle b = instantiateBundle(baseName, localeID, root, disableFallback);
846 throw new MissingResourceException("Could not find the bundle "+ baseName+"/"+ localeID+".res","","");
850 // recursively build bundle .. over-ride super class method.
851 protected synchronized static UResourceBundle instantiateBundle(String baseName, String localeID,
852 ClassLoader root, boolean disableFallback){
853 ULocale defaultLocale = ULocale.getDefault();
854 String localeName = localeID;
855 if(localeName.indexOf('@')>=0){
856 localeName = ULocale.getBaseName(localeID);
858 String fullName = ICUResourceBundleReader.getFullName(baseName, localeName);
859 ICUResourceBundle b = (ICUResourceBundle)loadFromCache(root, fullName, defaultLocale);
861 // here we assume that java type resource bundle organization
862 // is required then the base name contains '.' else
863 // the resource organization is of ICU type
864 // so clients can instantiate resources of the type
865 // com.mycompany.data.MyLocaleElements_en.res and
866 // com.mycompany.data.MyLocaleElements.res
868 final String rootLocale = (baseName.indexOf('.')==-1) ? "root" : "";
869 final String defaultID = defaultLocale.getBaseName();
871 if(localeName.equals("")){
872 localeName = rootLocale;
874 if(DEBUG) System.out.println("Creating "+fullName+ " currently b is "+b);
876 b = ICUResourceBundle.createBundle(baseName, localeName, root);
878 if(DEBUG)System.out.println("The bundle created is: "+b+" and disableFallback="+disableFallback+" and bundle.getNoFallback="+(b!=null && b.getNoFallback()));
879 if(disableFallback || (b!=null && b.getNoFallback())){
880 // no fallback because the caller said so or because the bundle says so
881 return addToCache(root, fullName, defaultLocale, b);
884 // fallback to locale ID parent
886 int i = localeName.lastIndexOf('_');
888 String temp = localeName.substring(0, i);
889 b = (ICUResourceBundle)instantiateBundle(baseName, temp, root, disableFallback);
890 if(b!=null && b.getULocale().getName().equals(temp)){
891 b.setLoadingStatus(ICUResourceBundle.FROM_FALLBACK);
894 if(defaultID.indexOf(localeName)==-1){
895 b = (ICUResourceBundle)instantiateBundle(baseName, defaultID, root, disableFallback);
897 b.setLoadingStatus(ICUResourceBundle.FROM_DEFAULT);
899 }else if(rootLocale.length()!=0){
900 b = ICUResourceBundle.createBundle(baseName, rootLocale, root);
902 b.setLoadingStatus(ICUResourceBundle.FROM_ROOT);
907 UResourceBundle parent = null;
908 localeName = b.getLocaleID();
909 int i = localeName.lastIndexOf('_');
911 b = (ICUResourceBundle)addToCache(root, fullName, defaultLocale, b);
913 if (b.getTableResource("%%Parent") != RES_BOGUS) {
914 String parentLocaleName = b.getString("%%Parent");
915 parent = instantiateBundle(baseName, parentLocaleName, root, disableFallback);
916 } else if (i != -1) {
917 parent = instantiateBundle(baseName, localeName.substring(0, i), root, disableFallback);
918 } else if (!localeName.equals(rootLocale)){
919 parent = instantiateBundle(baseName, rootLocale, root, true);
922 if (!b.equals(parent)){
929 UResourceBundle get(String aKey, HashMap<String, String> table, UResourceBundle requested) {
930 ICUResourceBundle obj = (ICUResourceBundle)handleGet(aKey, table, requested);
932 obj = (ICUResourceBundle)getParent();
934 //call the get method to recursively fetch the resource
935 obj = (ICUResourceBundle)obj.get(aKey, table, requested);
938 String fullName = ICUResourceBundleReader.getFullName(getBaseName(), getLocaleID());
939 throw new MissingResourceException(
940 "Can't find resource for bundle " + fullName + ", key "
941 + aKey, this.getClass().getName(), aKey);
944 obj.setLoadingStatus(((ICUResourceBundle)requested).getLocaleID());
948 protected String localeID;
949 protected String baseName;
950 protected ULocale ulocale;
951 protected ClassLoader loader;
954 * Access to the bits and bytes of the resource bundle.
955 * Hides low-level details.
957 protected ICUResourceBundleReader reader;
958 /** Data member where the subclasses store the key. */
959 protected String key;
960 /** Data member where the subclasses store the offset within resource data. */
961 protected int resource;
964 * A resource word value that means "no resource".
965 * Note: 0xffffffff == -1
966 * This has the same value as UResourceBundle.NONE, but they are semantically
967 * different and should be used appropriately according to context:
968 * NONE means "no type".
969 * (The type of RES_BOGUS is RES_RESERVED=15 which was defined in ICU4C ures.h.)
971 public static final int RES_BOGUS = 0xffffffff;
974 * Resource type constant for aliases;
975 * internally stores a string which identifies the actual resource
976 * storing the data (can be in a different resource bundle).
977 * Resolved internally before delivering the actual resource through the API.
979 public static final int ALIAS = 3;
981 /** Resource type constant for tables with 32-bit count, key offsets and values. */
982 public static final int TABLE32 = 4;
985 * Resource type constant for tables with 16-bit count, key offsets and values.
986 * All values are STRING_V2 strings.
988 public static final int TABLE16 = 5;
990 /** Resource type constant for 16-bit Unicode strings in formatVersion 2. */
991 public static final int STRING_V2 = 6;
994 * Resource type constant for arrays with 16-bit count and values.
995 * All values are STRING_V2 strings.
997 public static final int ARRAY16 = 9;
1000 * Create a bundle using a reader.
1001 * @param baseName The name for the bundle.
1002 * @param localeID The locale identification.
1003 * @param root The ClassLoader object root.
1004 * @return the new bundle
1006 public static ICUResourceBundle createBundle(String baseName, String localeID, ClassLoader root) {
1007 ICUResourceBundleReader reader = ICUResourceBundleReader.getReader(baseName, localeID, root);
1008 if (reader == null) {
1009 // could not open the .res file
1012 return getBundle(reader, baseName, localeID, root);
1015 protected String getLocaleID() {
1019 protected String getBaseName() {
1023 public ULocale getULocale() {
1027 public UResourceBundle getParent() {
1028 return (UResourceBundle) parent;
1031 protected void setParent(ResourceBundle parent) {
1032 this.parent = parent;
1035 public String getKey() {
1039 private static final int[] gPublicTypes = new int[] {
1045 TABLE, /* TABLE32 */
1046 TABLE, /* TABLE16 */
1047 STRING, /* STRING_V2 */
1051 ARRAY, /* ARRAY16 */
1061 public int getType() {
1062 return gPublicTypes[ICUResourceBundleReader.RES_GET_TYPE(resource)];
1066 * Get the noFallback flag specified in the loaded bundle.
1067 * @return The noFallback flag.
1069 private boolean getNoFallback() {
1070 return reader.getNoFallback();
1073 private static ICUResourceBundle getBundle(ICUResourceBundleReader reader,
1074 String baseName, String localeID,
1075 ClassLoader loader) {
1076 ICUResourceBundleImpl bundle;
1077 int rootRes = reader.getRootResource();
1078 if(gPublicTypes[ICUResourceBundleReader.RES_GET_TYPE(rootRes)] == TABLE) {
1079 bundle = new ICUResourceBundleImpl.ResourceTable(reader, null, "", rootRes, null);
1081 throw new IllegalStateException("Invalid format error");
1083 bundle.baseName = baseName;
1084 bundle.localeID = localeID;
1085 bundle.ulocale = new ULocale(localeID);
1086 bundle.loader = loader;
1087 UResourceBundle alias = bundle.handleGetImpl("%%ALIAS", null, bundle, null, null); // handleGet will cache the bundle with no parent set
1089 return (ICUResourceBundle)UResourceBundle.getBundleInstance(baseName, alias.getString());
1094 // constructor for inner classes
1095 protected ICUResourceBundle(ICUResourceBundleReader reader, String key, String resPath, int resource,
1096 ICUResourceBundle container) {
1097 this.reader = reader;
1099 this.resPath = resPath;
1100 this.resource = resource;
1101 if(container != null) {
1102 baseName = container.baseName;
1103 localeID = container.localeID;
1104 ulocale = container.ulocale;
1105 loader = container.loader;
1106 this.parent = container.parent;
1110 private String getAliasValue(int res) {
1111 String result = reader.getAlias(res);
1112 return result != null ? result : "";
1114 private static final char RES_PATH_SEP_CHAR = '/';
1115 private static final String RES_PATH_SEP_STR = "/";
1116 private static final String ICUDATA = "ICUDATA";
1117 private static final char HYPHEN = '-';
1118 private static final String LOCALE = "LOCALE";
1120 protected ICUResourceBundle findResource(String key,
1123 HashMap<String, String> table,
1124 UResourceBundle requested) {
1125 ClassLoader loaderToUse = loader;
1126 String locale = null, keyPath = null;
1128 String rpath = getAliasValue(_resource);
1129 if (table == null) {
1130 table = new HashMap<String, String>();
1132 if (table.get(rpath) != null) {
1133 throw new IllegalArgumentException(
1134 "Circular references in the resource bundles");
1136 table.put(rpath, "");
1137 if (rpath.indexOf(RES_PATH_SEP_CHAR) == 0) {
1138 int i = rpath.indexOf(RES_PATH_SEP_CHAR, 1);
1139 int j = rpath.indexOf(RES_PATH_SEP_CHAR, i + 1);
1140 bundleName = rpath.substring(1, i);
1142 locale = rpath.substring(i + 1);
1143 // if key path is not available,
1144 // use the given key path
1147 locale = rpath.substring(i + 1, j);
1148 keyPath = rpath.substring(j + 1, rpath.length());
1150 //there is a path included
1151 if (bundleName.equals(ICUDATA)) {
1152 bundleName = ICU_BASE_NAME;
1153 loaderToUse = ICU_DATA_CLASS_LOADER;
1154 }else if(bundleName.indexOf(ICUDATA)>-1){
1155 int idx = bundleName.indexOf(HYPHEN);
1157 bundleName = ICU_BASE_NAME+RES_PATH_SEP_STR+bundleName.substring(idx+1,bundleName.length());
1158 loaderToUse = ICU_DATA_CLASS_LOADER;
1162 //no path start with locale
1163 int i = rpath.indexOf(RES_PATH_SEP_CHAR);
1165 locale = rpath.substring(0, i);
1166 keyPath = rpath.substring(i + 1);
1169 // if key path is not available,
1170 // use the given key path
1173 bundleName = baseName;
1175 ICUResourceBundle bundle = null;
1176 ICUResourceBundle sub = null;
1177 if(bundleName.equals(LOCALE)){
1178 bundleName = baseName;
1179 keyPath = rpath.substring(LOCALE.length() + 2/* prepending and appending / */, rpath.length());
1180 locale = ((ICUResourceBundle)requested).getLocaleID();
1182 // Get the top bundle of the requested bundle
1183 bundle = (ICUResourceBundle)getBundleInstance(bundleName, locale, loaderToUse, false);
1184 if (bundle != null) {
1185 sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null);
1187 // The resPath of the resolved bundle should reflect the resource path
1188 // requested by caller. However, overwriting resPath here will affect cached
1189 // resource instance. The resPath is exposed by ICUResourceBundle#getResPath,
1190 // but there are no call sites in ICU (and ICUResourceBundle is an implementation
1191 // class). We may create a safe clone to overwrite the resPath field, but
1192 // it has no benefit at least for now. -Yoshito
1193 //if (sub != null) {
1194 // sub.resPath = resPath;
1198 if (locale == null) {
1199 // {dlf} must use requestor's class loader to get resources from same jar
1200 bundle = (ICUResourceBundle) getBundleInstance(bundleName, "",
1201 loaderToUse, false);
1203 bundle = (ICUResourceBundle) getBundleInstance(bundleName, locale,
1204 loaderToUse, false);
1207 StringTokenizer st = new StringTokenizer(keyPath, "/");
1208 ICUResourceBundle current = bundle;
1209 while (st.hasMoreTokens()) {
1210 String subKey = st.nextToken();
1211 sub = (ICUResourceBundle)current.get(subKey, table, requested);
1218 // See the comments above.
1219 //if (sub != null) {
1220 // sub.resPath = resPath;
1224 throw new MissingResourceException(localeID, baseName, key);
1229 // Resource bundle lookup cache, which may be used by subclasses
1230 // which have nested resources
1231 protected ICUCache<Object, UResourceBundle> lookup;
1232 private static final int MAX_INITIAL_LOOKUP_SIZE = 64;
1234 protected void createLookupCache() {
1235 lookup = new SimpleCache<Object, UResourceBundle>(ICUCache.WEAK, Math.max(getSize()*2, MAX_INITIAL_LOOKUP_SIZE));
1238 protected UResourceBundle handleGet(String resKey, HashMap<String, String> table, UResourceBundle requested) {
1239 UResourceBundle res = null;
1240 if (lookup != null) {
1241 res = lookup.get(resKey);
1244 int[] index = new int[1];
1245 boolean[] alias = new boolean[1];
1246 res = handleGetImpl(resKey, table, requested, index, alias);
1247 if (res != null && lookup != null && !alias[0]) {
1248 // We do not want to cache a result from alias entry
1249 lookup.put(resKey, res);
1250 lookup.put(Integer.valueOf(index[0]), res);
1256 protected UResourceBundle handleGet(int index, HashMap<String, String> table, UResourceBundle requested) {
1257 UResourceBundle res = null;
1258 Integer indexKey = null;
1259 if (lookup != null) {
1260 indexKey = Integer.valueOf(index);
1261 res = lookup.get(indexKey);
1264 boolean[] alias = new boolean[1];
1265 res = handleGetImpl(index, table, requested, alias);
1266 if (res != null && lookup != null && !alias[0]) {
1267 // We do not want to cache a result from alias entry
1268 lookup.put(res.getKey(), res);
1269 lookup.put(indexKey, res);
1275 // Subclass which supports key based resource access to implement this method
1276 protected UResourceBundle handleGetImpl(String resKey, HashMap<String, String> table, UResourceBundle requested,
1277 int[] index, boolean[] isAlias) {
1281 // Subclass which supports index based resource access to implement this method
1282 protected UResourceBundle handleGetImpl(int index, HashMap<String, String> table, UResourceBundle requested,
1283 boolean[] isAlias) {
1288 // TODO Below is a set of workarounds created for org.unicode.cldr.icu.ICU2LDMLWriter
1290 * Calling getKeys() on a table that has alias's can throw a NullPointerException if parent is not set,
1291 * see trac bug: 6514
1292 * -Brian Rower - IBM - Sept. 2008
1296 * Returns the resource handle for the given key within the calling resource table.
1299 * @deprecated This API is ICU internal only and a workaround see ticket #6514.
1300 * @author Brian Rower
1302 protected int getTableResource(String resKey) {
1305 protected int getTableResource(int index) {
1310 * Determines if the object at the specified index of the calling resource table
1311 * is an alias. If it is, returns true
1313 * @param index The index of the resource to check
1314 * @returns True if the resource at 'index' is an alias, false otherwise.
1317 * @deprecated This API is ICU internal only and part of a work around see ticket #6514
1318 * @author Brian Rower
1320 public boolean isAlias(int index)
1322 //TODO this is part of a workaround for ticket #6514
1323 //if index is out of the resource, return false.
1324 return ICUResourceBundleReader.RES_GET_TYPE(getTableResource(index)) == ALIAS;
1330 * @deprecated This API is ICU internal only and part of a workaround see ticket #6514.
1331 * @author Brian Rower
1333 public boolean isAlias()
1335 //TODO this is part of a workaround for ticket #6514
1336 return ICUResourceBundleReader.RES_GET_TYPE(resource) == ALIAS;
1340 * Determines if the object with the specified key
1341 * is an alias. If it is, returns true
1343 * @returns True if the resource with 'key' is an alias, false otherwise.
1346 * @deprecated This API is ICU internal only and part of a workaround see ticket #6514.
1347 * @author Brian Rower
1349 public boolean isAlias(String k)
1351 //TODO this is part of a workaround for ticket #6514
1352 //this only applies to tables
1353 return ICUResourceBundleReader.RES_GET_TYPE(getTableResource(k)) == ALIAS;
1357 * This method can be used to retrieve the underlying alias path (aka where the alias points to)
1358 * This method was written to allow conversion from ICU back to LDML format.
1360 * @param index The index where the alias path points to.
1361 * @return The alias path.
1362 * @author Brian Rower
1364 * @deprecated This API is ICU internal only.
1365 * @author Brian Rower
1367 public String getAliasPath(int index)
1369 return getAliasValue(getTableResource(index));
1375 * @deprecated This API is ICU internal only
1376 * @author Brian Rower
1378 public String getAliasPath()
1380 //TODO cannot allow alias path to end up in public API
1381 return getAliasValue(resource);
1387 * @deprecated This API is ICU internal only
1388 * @author Brian Rower
1390 public String getAliasPath(String k)
1392 //TODO cannot allow alias path to end up in public API
1393 return getAliasValue(getTableResource(k));
1397 * Helper method for getKeysSafe
1399 protected String getKey(int index) {
1404 * Returns an Enumeration of the keys belonging to this table or array.
1405 * This method differs from the getKeys() method by not following alias paths. This method exposes
1406 * underlying alias's. For all general purposes of the ICU resource bundle please use getKeys().
1408 * @return Keys in this table or array.
1410 * @deprecated This API is ICU internal only and a workaround see ticket #6514.
1411 * @author Brian Rower
1413 public Enumeration<String> getKeysSafe()
1415 //TODO this is part of a workaround for ticket #6514
1416 //the safeness only applies to tables, so use the other method if it's not a table
1417 if(!ICUResourceBundleReader.URES_IS_TABLE(resource))
1421 List<String> v = new ArrayList<String>();
1422 int size = getSize();
1423 for(int index = 0; index < size; index++)
1425 String curKey = getKey(index);
1429 //TODO we should use Iterator or List as the return type
1430 // instead of Enumeration
1432 return Collections.enumeration(v);
1435 // This is the worker function for the public getKeys().
1436 // TODO: Now that UResourceBundle uses handleKeySet(), this function is obsolete.
1437 // It is also not inherited from ResourceBundle, and it is not implemented
1438 // by ResourceBundleWrapper despite its documentation requiring all subclasses to
1440 // Consider deprecating UResourceBundle.handleGetKeys(), and consider making it always return null.
1441 protected Enumeration<String> handleGetKeys() {
1442 return Collections.enumeration(handleKeySet());
1445 protected boolean isTopLevelResource() {
1446 return resPath.length() == 0;