2 * *****************************************************************************
\r
3 * Copyright (C) 2005-2009, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 * *****************************************************************************
\r
8 package com.ibm.icu.impl;
\r
10 import java.io.BufferedReader;
\r
11 import java.io.IOException;
\r
12 import java.io.InputStream;
\r
13 import java.io.InputStreamReader;
\r
14 import java.lang.ref.SoftReference;
\r
15 import java.net.URL;
\r
16 import java.util.ArrayList;
\r
17 import java.util.Arrays;
\r
18 import java.util.Collections;
\r
19 import java.util.Enumeration;
\r
20 import java.util.HashMap;
\r
21 import java.util.HashSet;
\r
22 import java.util.Locale;
\r
23 import java.util.Map;
\r
24 import java.util.MissingResourceException;
\r
25 import java.util.ResourceBundle;
\r
26 import java.util.Set;
\r
27 import java.util.Vector;
\r
29 import com.ibm.icu.impl.URLHandler.URLVisitor;
\r
30 import com.ibm.icu.util.StringTokenizer;
\r
31 import com.ibm.icu.util.ULocale;
\r
32 import com.ibm.icu.util.UResourceBundle;
\r
33 import com.ibm.icu.util.UResourceBundleIterator;
\r
34 import com.ibm.icu.util.VersionInfo;
\r
36 public class ICUResourceBundle extends UResourceBundle {
\r
38 * The data path to be used with getBundleInstance API
\r
40 protected static final String ICU_DATA_PATH = "com/ibm/icu/impl/";
\r
42 * The data path to be used with getBundleInstance API
\r
44 public static final String ICU_BUNDLE = "data/icudt" + VersionInfo.ICU_DATA_VERSION;
\r
47 * The base name of ICU data to be used with getBundleInstance API
\r
49 public static final String ICU_BASE_NAME = ICU_DATA_PATH + ICU_BUNDLE;
\r
52 * The base name of collation data to be used with getBundleInstance API
\r
54 public static final String ICU_COLLATION_BASE_NAME = ICU_BASE_NAME + "/coll";
\r
57 * The base name of rbbi data to be used with getData API
\r
59 public static final String ICU_BRKITR_NAME = "/brkitr";
\r
62 * The base name of rbbi data to be used with getBundleInstance API
\r
64 public static final String ICU_BRKITR_BASE_NAME = ICU_BASE_NAME + ICU_BRKITR_NAME;
\r
67 * The base name of rbnf data to be used with getBundleInstance API
\r
69 public static final String ICU_RBNF_BASE_NAME = ICU_BASE_NAME + "/rbnf";
\r
72 * The base name of transliterator data to be used with getBundleInstance API
\r
74 public static final String ICU_TRANSLIT_BASE_NAME = ICU_BASE_NAME + "/translit";
\r
77 * The actual path of the resource
\r
79 protected String resPath;
\r
81 protected static final long UNSIGNED_INT_MASK = 0xffffffffL;
\r
84 * The class loader constant to be used with getBundleInstance API
\r
86 public static final ClassLoader ICU_DATA_CLASS_LOADER;
\r
88 ClassLoader loader = ICUData.class.getClassLoader();
\r
89 if (loader == null) { // boot class loader
\r
90 loader = ClassLoader.getSystemClassLoader();
\r
92 ICU_DATA_CLASS_LOADER = loader;
\r
96 * The name of the resource containing the installed locales
\r
98 protected static final String INSTALLED_LOCALES = "InstalledLocales";
\r
100 public static final int FROM_FALLBACK = 1, FROM_ROOT = 2, FROM_DEFAULT = 3, FROM_LOCALE = 4;
\r
102 private int loadingStatus = -1;
\r
104 public void setLoadingStatus(int newStatus) {
\r
105 loadingStatus = newStatus;
\r
108 * Returns the loading status of a particular resource.
\r
110 * @return FROM_FALLBACK if the resource is fetched from fallback bundle
\r
111 * FROM_ROOT if the resource is fetched from root bundle.
\r
112 * FROM_DEFAULT if the resource is fetched from the default locale.
\r
114 public int getLoadingStatus() {
\r
115 return loadingStatus;
\r
118 public void setLoadingStatus(String requestedLocale){
\r
119 String locale = getLocaleID();
\r
120 if(locale.equals("root")) {
\r
121 setLoadingStatus(FROM_ROOT);
\r
122 } else if(locale.equals(requestedLocale)) {
\r
123 setLoadingStatus(FROM_LOCALE);
\r
125 setLoadingStatus(FROM_FALLBACK);
\r
131 * Returns the respath of this bundle
\r
134 public String getResPath(){
\r
139 * Returns a functionally equivalent locale, considering keywords as well, for the specified keyword.
\r
140 * @param baseName resource specifier
\r
141 * @param resName top level resource to consider (such as "collations")
\r
142 * @param keyword a particular keyword to consider (such as "collation" )
\r
143 * @param locID The requested locale
\r
144 * @param isAvailable If non-null, 1-element array of fillin parameter that indicates whether the
\r
145 * requested locale was available. The locale is defined as 'available' if it physically
\r
146 * exists within the specified tree and included in 'InstalledLocales'.
\r
147 * @param omitDefault if true, omit keyword and value if default.
\r
148 * 'de_DE\@collation=standard' -> 'de_DE'
\r
149 * @return the locale
\r
150 * @internal ICU 3.0
\r
152 public static final ULocale getFunctionalEquivalent(String baseName,
\r
153 String resName, String keyword, ULocale locID,
\r
154 boolean isAvailable[], boolean omitDefault) {
\r
155 String kwVal = locID.getKeywordValue(keyword);
\r
156 String baseLoc = locID.getBaseName();
\r
157 String defStr = null;
\r
158 ULocale parent = new ULocale(baseLoc);
\r
159 ULocale defLoc = null; // locale where default (found) resource is
\r
160 boolean lookForDefault = false; // true if kwVal needs to be set
\r
161 ULocale fullBase = null; // base locale of found (target) resource
\r
162 int defDepth = 0; // depth of 'default' marker
\r
163 int resDepth = 0; // depth of found resource;
\r
165 if ((kwVal == null) || (kwVal.length() == 0)
\r
166 || kwVal.equals(DEFAULT_TAG)) {
\r
167 kwVal = ""; // default tag is treated as no keyword
\r
168 lookForDefault = true;
\r
171 // Check top level locale first
\r
172 ICUResourceBundle r = null;
\r
174 r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
\r
175 if (isAvailable != null) {
\r
176 isAvailable[0] = false;
\r
177 ULocale[] availableULocales = getAvailEntry(baseName).getULocaleList();
\r
178 for (int i = 0; i < availableULocales.length; i++) {
\r
179 if (parent.equals(availableULocales[i])) {
\r
180 isAvailable[0] = true;
\r
185 // determine in which locale (if any) the currently relevant 'default' is
\r
188 ICUResourceBundle irb = (ICUResourceBundle) r.get(resName);
\r
189 defStr = irb.getString(DEFAULT_TAG);
\r
190 if (lookForDefault == true) {
\r
192 lookForDefault = false;
\r
194 defLoc = r.getULocale();
\r
195 } catch (MissingResourceException t) {
\r
196 // Ignore error and continue search.
\r
198 if (defLoc == null) {
\r
199 r = (ICUResourceBundle) r.getParent();
\r
202 } while ((r != null) && (defLoc == null));
\r
204 // Now, search for the named resource
\r
205 parent = new ULocale(baseLoc);
\r
206 r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
\r
207 // determine in which locale (if any) the named resource is located
\r
210 ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
\r
211 /* UResourceBundle urb = */irb.get(kwVal);
\r
212 fullBase = irb.getULocale();
\r
213 // If the get() completed, we have the full base locale
\r
214 // If we fell back to an ancestor of the old 'default',
\r
215 // we need to re calculate the "default" keyword.
\r
216 if ((fullBase != null) && ((resDepth) > defDepth)) {
\r
217 defStr = irb.getString(DEFAULT_TAG);
\r
218 defLoc = r.getULocale();
\r
219 defDepth = resDepth;
\r
221 } catch (MissingResourceException t) {
\r
224 if (fullBase == null) {
\r
225 r = (ICUResourceBundle) r.getParent();
\r
228 } while ((r != null) && (fullBase == null));
\r
230 if (fullBase == null && // Could not find resource 'kwVal'
\r
231 (defStr != null) && // default was defined
\r
232 !defStr.equals(kwVal)) { // kwVal is not default
\r
233 // couldn't find requested resource. Fall back to default.
\r
234 kwVal = defStr; // Fall back to default.
\r
235 parent = new ULocale(baseLoc);
\r
236 r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
\r
238 // determine in which locale (if any) the named resource is located
\r
241 ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
\r
242 UResourceBundle urb = irb.get(kwVal);
\r
244 // if we didn't fail before this..
\r
245 fullBase = r.getULocale();
\r
247 // If the fetched item (urb) is in a different locale than our outer locale (r/fullBase)
\r
248 // then we are in a 'fallback' situation. treat as a missing resource situation.
\r
249 if(!fullBase.toString().equals(urb.getLocale().toString())) {
\r
250 fullBase = null; // fallback condition. Loop and try again.
\r
253 // If we fell back to an ancestor of the old 'default',
\r
254 // we need to re calculate the "default" keyword.
\r
255 if ((fullBase != null) && ((resDepth) > defDepth)) {
\r
256 defStr = irb.getString(DEFAULT_TAG);
\r
257 defLoc = r.getULocale();
\r
258 defDepth = resDepth;
\r
260 } catch (MissingResourceException t) {
\r
261 // Ignore error, continue search.
\r
263 if (fullBase == null) {
\r
264 r = (ICUResourceBundle) r.getParent();
\r
267 } while ((r != null) && (fullBase == null));
\r
270 if (fullBase == null) {
\r
271 throw new MissingResourceException(
\r
272 "Could not find locale containing requested or default keyword.",
\r
273 baseName, keyword + "=" + kwVal);
\r
277 && defStr.equals(kwVal) // if default was requested and
\r
278 && resDepth <= defDepth) { // default was set in same locale or child
\r
279 return fullBase; // Keyword value is default - no keyword needed in locale
\r
281 return new ULocale(fullBase.toString() + "@" + keyword + "=" + kwVal);
\r
286 * Given a tree path and keyword, return a string enumeration of all possible values for that keyword.
\r
287 * @param baseName resource specifier
\r
288 * @param keyword a particular keyword to consider, must match a top level resource name
\r
289 * within the tree. (i.e. "collations")
\r
290 * @internal ICU 3.0
\r
292 public static final String[] getKeywordValues(String baseName, String keyword) {
\r
293 Set keywords = new HashSet();
\r
294 ULocale locales[] = createULocaleList(baseName, ICU_DATA_CLASS_LOADER);
\r
297 for (i = 0; i < locales.length; i++) {
\r
299 UResourceBundle b = UResourceBundle.getBundleInstance(baseName, locales[i]);
\r
300 // downcast to ICUResourceBundle?
\r
301 ICUResourceBundle irb = (ICUResourceBundle) (b.getObject(keyword));
\r
302 Enumeration e = irb.getKeys();
\r
304 while (e.hasMoreElements()) {
\r
305 s = e.nextElement();
\r
306 if ((s instanceof String) && !DEFAULT_TAG.equals(s)) {
\r
307 // don't add 'default' items
\r
311 } catch (Throwable t) {
\r
312 //System.err.println("Error in - " + new Integer(i).toString()
\r
313 // + " - " + t.toString());
\r
314 // ignore the err - just skip that resource
\r
317 return (String[])keywords.toArray(new String[0]);
\r
321 * This method performs multilevel fallback for fetching items from the
\r
322 * bundle e.g: If resource is in the form de__PHONEBOOK{ collations{
\r
323 * default{ "phonebook"} } } If the value of "default" key needs to be
\r
324 * accessed, then do: <code>
\r
325 * UResourceBundle bundle = UResourceBundle.getBundleInstance("de__PHONEBOOK");
\r
326 * ICUResourceBundle result = null;
\r
327 * if(bundle instanceof ICUResourceBundle){
\r
328 * result = ((ICUResourceBundle) bundle).getWithFallback("collations/default");
\r
333 * The path to the required resource key
\r
334 * @return resource represented by the key
\r
335 * @exception MissingResourceException
\r
337 public ICUResourceBundle getWithFallback(String path)
\r
338 throws MissingResourceException {
\r
339 ICUResourceBundle result = null;
\r
340 ICUResourceBundle actualBundle = this;
\r
342 // now recuse to pick up sub levels of the items
\r
343 result = findResourceWithFallback(path, actualBundle, null);
\r
345 if (result == null) {
\r
346 throw new MissingResourceException(
\r
347 "Can't find resource for bundle "
\r
348 + this.getClass().getName() + ", key " + getType(),
\r
354 // will throw type mismatch exception if the resource is not a string
\r
355 public String getStringWithFallback(String path) throws MissingResourceException {
\r
356 return getWithFallback(path).getString();
\r
360 * Return a set of the locale names supported by a collection of resource
\r
363 * @param bundlePrefix the prefix of the resource bundles to use.
\r
365 public static Set getAvailableLocaleNameSet(String bundlePrefix) {
\r
366 return getAvailEntry(bundlePrefix).getLocaleNameSet();
\r
370 * Return a set of all the locale names supported by a collection of
\r
371 * resource bundles.
\r
373 public static Set getFullLocaleNameSet() {
\r
374 return getFullLocaleNameSet(ICU_BASE_NAME);
\r
378 * Return a set of all the locale names supported by a collection of
\r
379 * resource bundles.
\r
381 * @param bundlePrefix the prefix of the resource bundles to use.
\r
383 public static Set getFullLocaleNameSet(String bundlePrefix) {
\r
384 return getAvailEntry(bundlePrefix).getFullLocaleNameSet();
\r
388 * Return a set of the locale names supported by a collection of resource
\r
391 public static Set getAvailableLocaleNameSet() {
\r
392 return getAvailableLocaleNameSet(ICU_BASE_NAME);
\r
396 * Get the set of Locales installed in the specified bundles.
\r
397 * @return the list of available locales
\r
399 public static final ULocale[] getAvailableULocales(String baseName) {
\r
400 return getAvailEntry(baseName).getULocaleList();
\r
404 * Get the set of ULocales installed the base bundle.
\r
405 * @return the list of available locales
\r
407 public static final ULocale[] getAvailableULocales() {
\r
408 return getAvailableULocales(ICU_BASE_NAME);
\r
412 * Get the set of Locales installed in the specified bundles.
\r
413 * @return the list of available locales
\r
415 public static final Locale[] getAvailableLocales(String baseName) {
\r
416 return getAvailEntry(baseName).getLocaleList();
\r
420 * Get the set of Locales installed the base bundle.
\r
421 * @return the list of available locales
\r
423 public static final Locale[] getAvailableLocales() {
\r
424 return getAvailEntry(ICU_BASE_NAME).getLocaleList();
\r
428 * Convert a list of ULocales to a list of Locales. ULocales with a script code will not be converted
\r
429 * since they cannot be represented as a Locale. This means that the two lists will <b>not</b> match
\r
430 * one-to-one, and that the returned list might be shorter than the input list.
\r
431 * @param ulocales a list of ULocales to convert to a list of Locales.
\r
432 * @return the list of converted ULocales
\r
434 public static final Locale[] getLocaleList(ULocale[] ulocales) {
\r
435 ArrayList list = new ArrayList();
\r
436 HashSet uniqueSet = new HashSet();
\r
437 for (int i = 0; i < ulocales.length; i++) {
\r
438 Locale loc = ulocales[i].toLocale();
\r
439 if (!uniqueSet.contains(loc)) {
\r
441 uniqueSet.add(loc);
\r
444 return (Locale[]) list.toArray(new Locale[list.size()]);
\r
448 * Returns the locale of this resource bundle. This method can be used after
\r
449 * a call to getBundle() to determine whether the resource bundle returned
\r
450 * really corresponds to the requested locale or is a fallback.
\r
452 * @return the locale of this resource bundle
\r
454 public Locale getLocale() {
\r
455 return getULocale().toLocale();
\r
459 // ========== privates ==========
\r
460 private static final String ICU_RESOURCE_INDEX = "res_index";
\r
462 private static final String DEFAULT_TAG = "default";
\r
464 // Flag for enabling/disabling debugging code
\r
465 private static final boolean DEBUG = ICUDebug.enabled("localedata");
\r
467 // Cache for getAvailableLocales
\r
468 private static SoftReference GET_AVAILABLE_CACHE;
\r
469 private static final ULocale[] createULocaleList(String baseName,
\r
470 ClassLoader root) {
\r
471 // the canned list is a subset of all the available .res files, the idea
\r
472 // is we don't export them
\r
473 // all. gotta be a better way to do this, since to add a locale you have
\r
474 // to update this list,
\r
475 // and it's embedded in our binary resources.
\r
476 ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
\r
478 bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
\r
479 int length = bundle.getSize();
\r
481 ULocale[] locales = new ULocale[length];
\r
482 UResourceBundleIterator iter = bundle.getIterator();
\r
484 while (iter.hasNext()) {
\r
485 locales[i++] = new ULocale(iter.next().getKey());
\r
491 private static final Locale[] createLocaleList(String baseName) {
\r
492 ULocale[] ulocales = getAvailEntry(baseName).getULocaleList();
\r
493 return getLocaleList(ulocales);
\r
496 private static final String[] createLocaleNameArray(String baseName,
\r
497 ClassLoader root) {
\r
498 ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle( baseName, ICU_RESOURCE_INDEX, root, true);
\r
499 bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
\r
500 int length = bundle.getSize();
\r
502 String[] locales = new String[length];
\r
503 UResourceBundleIterator iter = bundle.getIterator();
\r
505 while (iter.hasNext()) {
\r
506 locales[i++] = iter.next().getKey();
\r
512 private static final ArrayList createFullLocaleNameArray(
\r
513 final String baseName, final ClassLoader root) {
\r
515 ArrayList list = (ArrayList) java.security.AccessController
\r
516 .doPrivileged(new java.security.PrivilegedAction() {
\r
517 public Object run() {
\r
518 // WebSphere class loader will return null for a raw
\r
519 // directory name without trailing slash
\r
520 String bn = baseName.endsWith("/")
\r
524 // look for prebuilt indices first
\r
526 InputStream s = root.getResourceAsStream(bn + ICU_RESOURCE_INDEX + ".txt");
\r
528 ArrayList lst = new ArrayList();
\r
529 BufferedReader br = new BufferedReader(new InputStreamReader(s, "ASCII"));
\r
531 while ((line = br.readLine()) != null) {
\r
532 if (line.length() != 0 && !line.startsWith("#")) {
\r
538 } catch (IOException e) {
\r
542 URL url = root.getResource(bn);
\r
543 URLHandler handler = URLHandler.get(url);
\r
544 if (handler != null) {
\r
545 final ArrayList lst = new ArrayList();
\r
546 URLVisitor v = new URLVisitor() {
\r
547 public void visit(String s) {
\r
548 if (s.endsWith(".res") && !"res_index.res".equals(s)) {
\r
549 lst.add(s.substring(0, s.length() - 4)); // strip '.res'
\r
553 handler.guide(v, false);
\r
564 private static Set createFullLocaleNameSet(String baseName) {
\r
565 ArrayList list = createFullLocaleNameArray(baseName,ICU_DATA_CLASS_LOADER);
\r
566 HashSet set = new HashSet();
\r
568 throw new MissingResourceException("Could not find "+ ICU_RESOURCE_INDEX, "", "");
\r
571 return Collections.unmodifiableSet(set);
\r
574 private static Set createLocaleNameSet(String baseName) {
\r
576 String[] locales = createLocaleNameArray(baseName, ICU_DATA_CLASS_LOADER);
\r
578 HashSet set = new HashSet();
\r
579 set.addAll(Arrays.asList(locales));
\r
580 return Collections.unmodifiableSet(set);
\r
581 } catch (MissingResourceException e) {
\r
583 System.out.println("couldn't find index for bundleName: " + baseName);
\r
584 Thread.dumpStack();
\r
587 return Collections.EMPTY_SET;
\r
591 * Holds the prefix, and lazily creates the Locale[] list or the locale name
\r
594 private static final class AvailEntry {
\r
595 private String prefix;
\r
596 private ULocale[] ulocales;
\r
597 private Locale[] locales;
\r
598 private Set nameSet;
\r
599 private Set fullNameSet;
\r
601 AvailEntry(String prefix) {
\r
602 this.prefix = prefix;
\r
605 ULocale[] getULocaleList() {
\r
606 if (ulocales == null) {
\r
607 ulocales = createULocaleList(prefix, ICU_DATA_CLASS_LOADER);
\r
611 Locale[] getLocaleList() {
\r
612 if (locales == null) {
\r
613 locales = createLocaleList(prefix);
\r
617 Set getLocaleNameSet() {
\r
618 if (nameSet == null) {
\r
619 nameSet = createLocaleNameSet(prefix);
\r
623 Set getFullLocaleNameSet() {
\r
624 if (fullNameSet == null) {
\r
625 fullNameSet = createFullLocaleNameSet(prefix);
\r
627 return fullNameSet;
\r
632 * Stores the locale information in a cache accessed by key (bundle prefix).
\r
633 * The cached objects are AvailEntries. The cache is held by a SoftReference
\r
634 * so it can be GC'd.
\r
636 private static AvailEntry getAvailEntry(String key) {
\r
637 AvailEntry ae = null;
\r
639 if (GET_AVAILABLE_CACHE != null) {
\r
640 lcache = (Map) GET_AVAILABLE_CACHE.get();
\r
641 if (lcache != null) {
\r
642 ae = (AvailEntry) lcache.get(key);
\r
647 ae = new AvailEntry(key);
\r
648 if (lcache == null) {
\r
649 lcache = new HashMap();
\r
650 lcache.put(key, ae);
\r
651 GET_AVAILABLE_CACHE = new SoftReference(lcache);
\r
653 lcache.put(key, ae);
\r
660 protected static final ICUResourceBundle findResourceWithFallback(String path,
\r
661 UResourceBundle actualBundle, UResourceBundle requested) {
\r
662 ICUResourceBundle sub = null;
\r
663 if (requested == null) {
\r
664 requested = actualBundle;
\r
666 while (actualBundle != null) {
\r
667 StringTokenizer st = new StringTokenizer(path, "/");
\r
668 ICUResourceBundle current = (ICUResourceBundle) actualBundle;
\r
669 while (st.hasMoreTokens()) {
\r
670 String subKey = st.nextToken();
\r
671 sub = (ICUResourceBundle)current.handleGet(subKey, null, requested);
\r
681 if (((ICUResourceBundle)actualBundle).resPath.length() != 0) {
\r
682 path = ((ICUResourceBundle)actualBundle).resPath + "/" + path;
\r
684 // if not try the parent bundle
\r
685 actualBundle = ((ICUResourceBundle) actualBundle).getParent();
\r
689 sub.setLoadingStatus(((ICUResourceBundle)requested).getLocaleID());
\r
693 public boolean equals(Object other) {
\r
694 if (other instanceof ICUResourceBundle) {
\r
695 ICUResourceBundle o = (ICUResourceBundle) other;
\r
696 if (getBaseName().equals(o.getBaseName())
\r
697 && getLocaleID().equals(o.getLocaleID())) {
\r
703 // This method is for super class's instantiateBundle method
\r
704 public static UResourceBundle getBundleInstance(String baseName, String localeID,
\r
705 ClassLoader root, boolean disableFallback){
\r
706 UResourceBundle b = instantiateBundle(baseName, localeID, root, disableFallback);
\r
708 throw new MissingResourceException("Could not find the bundle "+ baseName+"/"+ localeID+".res","","");
\r
712 // recursively build bundle .. over-ride super class method.
\r
713 protected synchronized static UResourceBundle instantiateBundle(String baseName, String localeID,
\r
714 ClassLoader root, boolean disableFallback){
\r
715 ULocale defaultLocale = ULocale.getDefault();
\r
716 String localeName = localeID;
\r
717 if(localeName.indexOf('@')>0){
\r
718 localeName = ULocale.getBaseName(localeID);
\r
720 String fullName = ICUResourceBundleReader.getFullName(baseName, localeName);
\r
721 ICUResourceBundle b = (ICUResourceBundle)loadFromCache(root, fullName, defaultLocale);
\r
723 // here we assume that java type resource bundle organization
\r
724 // is required then the base name contains '.' else
\r
725 // the resource organization is of ICU type
\r
726 // so clients can instantiate resources of the type
\r
727 // com.mycompany.data.MyLocaleElements_en.res and
\r
728 // com.mycompany.data.MyLocaleElements.res
\r
730 final String rootLocale = (baseName.indexOf('.')==-1) ? "root" : "";
\r
731 final String defaultID = ULocale.getDefault().toString();
\r
733 if(localeName.equals("")){
\r
734 localeName = rootLocale;
\r
736 if(DEBUG) System.out.println("Creating "+fullName+ " currently b is "+b);
\r
738 b = ICUResourceBundle.createBundle(baseName, localeName, root);
\r
740 if(DEBUG)System.out.println("The bundle created is: "+b+" and disableFallback="+disableFallback+" and bundle.getNoFallback="+(b!=null && b.getNoFallback()));
\r
741 if(disableFallback || (b!=null && b.getNoFallback())){
\r
742 addToCache(root, fullName, defaultLocale, b);
\r
743 // no fallback because the caller said so or because the bundle says so
\r
747 // fallback to locale ID parent
\r
749 int i = localeName.lastIndexOf('_');
\r
751 String temp = localeName.substring(0, i);
\r
752 b = (ICUResourceBundle)instantiateBundle(baseName, temp, root, disableFallback);
\r
753 if(b!=null && b.getULocale().equals(temp)){
\r
754 b.setLoadingStatus(ICUResourceBundle.FROM_FALLBACK);
\r
757 if(defaultID.indexOf(localeName)==-1){
\r
758 b = (ICUResourceBundle)instantiateBundle(baseName, defaultID, root, disableFallback);
\r
760 b.setLoadingStatus(ICUResourceBundle.FROM_DEFAULT);
\r
762 }else if(rootLocale.length()!=0){
\r
763 b = ICUResourceBundle.createBundle(baseName, rootLocale, root);
\r
765 b.setLoadingStatus(ICUResourceBundle.FROM_ROOT);
\r
770 UResourceBundle parent = null;
\r
771 localeName = b.getLocaleID();
\r
772 int i = localeName.lastIndexOf('_');
\r
774 addToCache(root, fullName, defaultLocale, b);
\r
777 parent = instantiateBundle(baseName, localeName.substring(0, i), root, disableFallback);
\r
778 }else if(!localeName.equals(rootLocale)){
\r
779 parent = instantiateBundle(baseName, rootLocale, root, true);
\r
782 if(!b.equals(parent)){
\r
783 b.setParent(parent);
\r
789 UResourceBundle get(String aKey, HashMap table, UResourceBundle requested) {
\r
790 ICUResourceBundle obj = (ICUResourceBundle)handleGet(aKey, table, requested);
\r
792 obj = (ICUResourceBundle)getParent();
\r
794 //call the get method to recursively fetch the resource
\r
795 obj = (ICUResourceBundle)obj.get(aKey, table, requested);
\r
798 String fullName = ICUResourceBundleReader.getFullName(
\r
799 getBaseName(), getLocaleID());
\r
800 throw new MissingResourceException(
\r
801 "Can't find resource for bundle " + fullName + ", key "
\r
802 + aKey, this.getClass().getName(), aKey);
\r
805 ((ICUResourceBundle)obj).setLoadingStatus(((ICUResourceBundle)requested).getLocaleID());
\r
808 //protected byte[] version;
\r
809 protected byte[] rawData;
\r
810 protected long rootResource;
\r
811 protected boolean noFallback;
\r
813 protected String localeID;
\r
814 protected String baseName;
\r
815 protected ULocale ulocale;
\r
816 protected ClassLoader loader;
\r
818 //protected static final boolean ASSERT = false;
\r
825 * @return the new bundle
\r
827 public static ICUResourceBundle createBundle(String baseName,
\r
828 String localeID, ClassLoader root) {
\r
830 ICUResourceBundleReader reader = ICUResourceBundleReader.getReader( baseName, localeID, root);
\r
831 // could not open the .res file so return null
\r
832 if (reader == null) {
\r
835 return getBundle(reader, baseName, localeID, root);
\r
838 protected String getLocaleID() {
\r
842 protected String getBaseName() {
\r
846 public ULocale getULocale() {
\r
850 public UResourceBundle getParent() {
\r
851 return (UResourceBundle) parent;
\r
854 protected void setParent(ResourceBundle parent) {
\r
855 this.parent = parent;
\r
859 * Get the noFallback flag specified in the loaded bundle.
\r
860 * @return The noFallback flag.
\r
862 protected boolean getNoFallback() {
\r
866 private static ICUResourceBundle getBundle(ICUResourceBundleReader reader, String baseName, String localeID, ClassLoader loader) {
\r
868 long rootResource = (UNSIGNED_INT_MASK) & reader.getRootResource();
\r
870 int type = RES_GET_TYPE(rootResource);
\r
871 if (type == TABLE) {
\r
872 ICUResourceBundleImpl.ResourceTable table = new ICUResourceBundleImpl.ResourceTable(reader, baseName, localeID, loader);
\r
873 if(table.getSize()>=1){ // ticket#5683 ICU4J 3.6 data for zh_xx contains an entry other than %%ALIAS
\r
874 UResourceBundle b = table.handleGetImpl(0, null, table, null); // handleGet will cache the bundle with no parent set
\r
875 String itemKey = b.getKey();
\r
877 // %%ALIAS is such a hack!
\r
878 if (itemKey.equals("%%ALIAS")) {
\r
879 String locale = b.getString();
\r
880 UResourceBundle actual = UResourceBundle.getBundleInstance(baseName, locale);
\r
881 return (ICUResourceBundleImpl.ResourceTable) actual;
\r
888 } else if (type == TABLE32) {
\r
890 // genrb does not generate Table32 with %%ALIAS
\r
891 return new ICUResourceBundleImpl.ResourceTable32(reader, baseName, localeID, loader);
\r
893 throw new IllegalStateException("Invalid format error");
\r
896 // private constructor for inner classes
\r
897 protected ICUResourceBundle(){}
\r
899 public static final int RES_GET_TYPE(long res) {
\r
900 return (int) ((res) >> 28L);
\r
902 protected static final int RES_GET_OFFSET(long res) {
\r
903 return (int) ((res & 0x0fffffff) << 2); // * 4
\r
905 /* get signed and unsigned integer values directly from the Resource handle */
\r
906 protected static final int RES_GET_INT(long res) {
\r
907 return (((int) ((res) << 4)) >> 4);
\r
909 static final long RES_GET_UINT(long res) {
\r
910 long t = ((res) & 0x0fffffffL);
\r
913 static final StringBuffer RES_GET_KEY(byte[] rawData,
\r
916 StringBuffer key = new StringBuffer();
\r
917 while ((ch = (char) rawData[keyOffset]) != 0) {
\r
923 protected static final int getIntOffset(int offset) {
\r
924 return (offset << 2); // * 4
\r
926 static final int getCharOffset(int offset) {
\r
927 return (offset << 1); // * 2
\r
929 protected final ICUResourceBundle createBundleObject(String _key,
\r
930 long _resource, String _resPath, HashMap table,
\r
931 UResourceBundle requested, ICUResourceBundle bundle, boolean[] isAlias) {
\r
932 if (isAlias != null) {
\r
933 isAlias[0] = false;
\r
935 //if (resource != RES_BOGUS) {
\r
936 switch (RES_GET_TYPE(_resource)) {
\r
938 return new ICUResourceBundleImpl.ResourceString(_key, _resPath, _resource, this);
\r
941 return new ICUResourceBundleImpl.ResourceBinary(_key, _resPath, _resource, this);
\r
944 if (isAlias != null) {
\r
947 return findResource(_key, _resource, table, requested);
\r
950 return new ICUResourceBundleImpl.ResourceInt(_key, _resPath, _resource, this);
\r
952 case INT_VECTOR : {
\r
953 return new ICUResourceBundleImpl.ResourceIntVector(_key, _resPath, _resource, this);
\r
956 return new ICUResourceBundleImpl.ResourceArray(_key, _resPath, _resource, this);
\r
959 return new ICUResourceBundleImpl.ResourceTable32(_key, _resPath, _resource, this);
\r
962 return new ICUResourceBundleImpl.ResourceTable(_key, _resPath, _resource, this);
\r
965 throw new IllegalStateException("The resource type is unknown");
\r
971 static final void assign(ICUResourceBundle b1, ICUResourceBundle b2){
\r
972 b1.rawData = b2.rawData;
\r
973 b1.rootResource = b2.rootResource;
\r
974 b1.noFallback = b2.noFallback;
\r
975 b1.baseName = b2.baseName;
\r
976 b1.localeID = b2.localeID;
\r
977 b1.ulocale = b2.ulocale;
\r
978 b1.loader = b2.loader;
\r
979 b1.parent = b2.parent;
\r
982 int findKey(int siz, int currentOffset, ICUResourceBundle res, String target) {
\r
983 int mid = 0, start = 0, limit = siz;
\r
986 int targetLength = target.length();
\r
991 //int myCharOffset = 0, keyOffset = 0;
\r
993 mid = ((start + limit) >> 1); // compute average
\r
994 if (lastMid == mid) { /* Have we moved? */
\r
995 break; /* We haven't moved, and it wasn't found. */
\r
999 offset = res.getOffset(currentOffset, mid);
\r
1001 // compare a segment of rawData with targetArray
\r
1002 for (int i=0; i<targetLength; i++) {
\r
1003 targetChar = target.charAt(i);
\r
1004 actualChar = (char)rawData[offset];
\r
1005 if (actualChar == 0 || targetChar > actualChar ) {
\r
1010 if (targetChar < actualChar) {
\r
1015 // target == data so far...
\r
1018 actualChar = (char)rawData[offset];
\r
1019 if (actualChar != 0) {
\r
1024 // target == data, we're sure now
\r
1030 public int getOffset(int currentOfset, int index){
\r
1034 private static final char makeChar(byte[] data, int offset) {
\r
1035 return (char)((data[offset++] << 8) | (data[offset] & 0xff));
\r
1037 static char getChar(byte[]data, int offset){
\r
1038 return makeChar(data, offset);
\r
1040 private static final int makeInt(byte[] data, int offset) {
\r
1041 // | is left-associative
\r
1042 return (int) ((data[offset++] << 24) | ((data[offset++] & 0xff) << 16) | ((data[offset++] & 0xff) << 8) | ((data[offset] & 0xff)));
\r
1045 protected static int getInt(byte[] data, int offset){
\r
1046 //if (ASSERT) Assert.assrt("offset < data.length", offset < data.length);
\r
1047 return makeInt(data, offset);
\r
1050 String getStringValue(long res) {
\r
1053 * The data structure is documented as supporting resource==0 for empty strings.
\r
1054 * Return a fixed pointer in such a case.
\r
1055 * This was dropped in uresdata.c 1.17 as part of Jitterbug 1005 work
\r
1056 * on code coverage for ICU 2.0.
\r
1057 * Re-added for consistency with the design and with other code.
\r
1061 int offset = RES_GET_OFFSET(res);
\r
1062 int length = getInt(rawData,offset);
\r
1063 int stringOffset = offset + getIntOffset(1);
\r
1064 char[] dst = new char[length];
\r
1065 //if (ASSERT) Assert.assrt("(stringOffset+getCharOffset(length)) < rawData.length", (stringOffset+getCharOffset(length)) < rawData.length);
\r
1066 for(int i=0; i<length; i++){
\r
1067 dst[i]=getChar(rawData, stringOffset+getCharOffset(i));
\r
1069 return new String(dst);
\r
1071 private static final char RES_PATH_SEP_CHAR = '/';
\r
1072 private static final String RES_PATH_SEP_STR = "/";
\r
1073 private static final String ICUDATA = "ICUDATA";
\r
1074 private static final char HYPHEN = '-';
\r
1075 private static final String LOCALE = "LOCALE";
\r
1077 protected static final int getIndex(String s) {
\r
1078 if (s.length() >= 1) {
\r
1079 return Integer.valueOf(s).intValue();
\r
1083 private ICUResourceBundle findResource(String _key, long _resource,
\r
1085 UResourceBundle requested) {
\r
1086 ClassLoader loaderToUse = loader;
\r
1087 String locale = null, keyPath = null;
\r
1088 String bundleName;
\r
1089 String rpath = getStringValue(_resource);
\r
1090 if (table == null) {
\r
1091 table = new HashMap();
\r
1093 if (table.get(rpath) != null) {
\r
1094 throw new IllegalArgumentException(
\r
1095 "Circular references in the resource bundles");
\r
1097 table.put(rpath, "");
\r
1098 if (rpath.indexOf(RES_PATH_SEP_CHAR) == 0) {
\r
1099 int i = rpath.indexOf(RES_PATH_SEP_CHAR, 1);
\r
1100 int j = rpath.indexOf(RES_PATH_SEP_CHAR, i + 1);
\r
1101 bundleName = rpath.substring(1, i);
\r
1102 locale = rpath.substring(i + 1);
\r
1104 locale = rpath.substring(i + 1, j);
\r
1105 keyPath = rpath.substring(j + 1, rpath.length());
\r
1107 //there is a path included
\r
1108 if (bundleName.equals(ICUDATA)) {
\r
1109 bundleName = ICU_BASE_NAME;
\r
1110 loaderToUse = ICU_DATA_CLASS_LOADER;
\r
1111 }else if(bundleName.indexOf(ICUDATA)>-1){
\r
1112 int idx = bundleName.indexOf(HYPHEN);
\r
1114 bundleName = ICU_BASE_NAME+RES_PATH_SEP_STR+bundleName.substring(idx+1,bundleName.length());
\r
1115 loaderToUse = ICU_DATA_CLASS_LOADER;
\r
1119 //no path start with locale
\r
1120 int i = rpath.indexOf(RES_PATH_SEP_CHAR);
\r
1121 keyPath = rpath.substring(i + 1);
\r
1123 locale = rpath.substring(0, i);
\r
1126 keyPath = null;//keyPath.substring(i, keyPath.length());
\r
1128 bundleName = baseName;
\r
1130 ICUResourceBundle bundle = null;
\r
1131 ICUResourceBundle sub = null;
\r
1132 if(bundleName.equals(LOCALE)){
\r
1133 bundleName = baseName;
\r
1134 bundle = (ICUResourceBundle)requested;
\r
1135 keyPath = rpath.substring(LOCALE.length() + 2/* prepending and appending / */, rpath.length());
\r
1136 locale = ((ICUResourceBundle)requested).getLocaleID();
\r
1137 sub = ICUResourceBundle.findResourceWithFallback(keyPath, requested, null);
\r
1138 sub.resPath = "/" + sub.getLocaleID() + "/" + keyPath;
\r
1140 if (locale == null) {
\r
1141 // {dlf} must use requestor's class loader to get resources from same jar
\r
1142 bundle = (ICUResourceBundle) getBundleInstance(bundleName, "",
\r
1143 loaderToUse, false);
\r
1145 bundle = (ICUResourceBundle) getBundleInstance(bundleName, locale,
\r
1146 loaderToUse, false);
\r
1148 if (keyPath != null) {
\r
1149 StringTokenizer st = new StringTokenizer(keyPath, "/");
\r
1150 ICUResourceBundle current = bundle;
\r
1151 while (st.hasMoreTokens()) {
\r
1152 String subKey = st.nextToken();
\r
1153 sub = (ICUResourceBundle)((ICUResourceBundle) current).get(subKey, table, requested);
\r
1154 if (sub == null) {
\r
1160 // if the sub resource is not found
\r
1161 // try fetching the sub resource with
\r
1162 // the key of this alias resource
\r
1163 sub = (ICUResourceBundle)bundle.get(_key);
\r
1165 sub.resPath = rpath;
\r
1167 if (sub == null) {
\r
1168 throw new MissingResourceException(localeID, baseName, _key);
\r
1173 // Resource bundle lookup cache, which may be used by subclasses
\r
1174 // which have nested resources
\r
1175 protected ICUCache lookup;
\r
1176 private static final int MAX_INITIAL_LOOKUP_SIZE = 64;
\r
1178 protected void createLookupCache() {
\r
1179 lookup = new SimpleCache(ICUCache.WEAK, Math.max(size*2, MAX_INITIAL_LOOKUP_SIZE));
\r
1182 protected UResourceBundle handleGet(String resKey, HashMap table, UResourceBundle requested) {
\r
1183 UResourceBundle res = null;
\r
1184 if (lookup != null) {
\r
1185 res = (UResourceBundle)lookup.get(resKey);
\r
1187 if (res == null) {
\r
1188 int[] index = new int[1];
\r
1189 boolean[] alias = new boolean[1];
\r
1190 res = handleGetImpl(resKey, table, requested, index, alias);
\r
1191 if (res != null && lookup != null && !alias[0]) {
\r
1192 // We do not want to cache a result from alias entry
\r
1193 lookup.put(resKey, res);
\r
1194 lookup.put(Utility.integerValueOf(index[0]), res);
\r
1200 protected UResourceBundle handleGet(int index, HashMap table, UResourceBundle requested) {
\r
1201 UResourceBundle res = null;
\r
1202 Integer indexKey = null;
\r
1203 if (lookup != null) {
\r
1204 indexKey = Utility.integerValueOf(index);
\r
1205 res = (UResourceBundle)lookup.get(indexKey);
\r
1207 if (res == null) {
\r
1208 boolean[] alias = new boolean[1];
\r
1209 res = handleGetImpl(index, table, requested, alias);
\r
1210 if (res != null && lookup != null && !alias[0]) {
\r
1211 // We do not want to cache a result from alias entry
\r
1212 lookup.put(res.getKey(), res);
\r
1213 lookup.put(indexKey, res);
\r
1219 // Subclass which supports key based resource access to implement this method
\r
1220 protected UResourceBundle handleGetImpl(String resKey, HashMap table, UResourceBundle requested,
\r
1221 int[] index, boolean[] isAlias) {
\r
1225 // Subclass which supports index based resource access to implement this method
\r
1226 protected UResourceBundle handleGetImpl(int index, HashMap table, UResourceBundle requested,
\r
1227 boolean[] isAlias) {
\r
1232 // TODO Below is a set of workarounds created for org.unicode.cldr.icu.ICU2LDMLWriter
\r
1234 * Calling getKeys() on a table that has alias's can throw a NullPointerException if parent is not set,
\r
1235 * see trac bug: 6514
\r
1236 * -Brian Rower - IBM - Sept. 2008
\r
1240 * Returns the resource handle for the given index within the calling resource table.
\r
1243 * @deprecated This API is ICU internal only and a workaround see ticket #6514.
\r
1244 * @author Brian Rower
\r
1246 private long getResourceHandle(int index)
\r
1248 //TODO this is part of a workaround for ticket #6514
\r
1249 //if it's out of range, return -1
\r
1250 if(index > this.size)
\r
1254 //get the offset of the calling tables resource
\r
1255 int offset = RES_GET_OFFSET(resource);
\r
1257 //move past the 2 byte count number
\r
1258 offset += getCharOffset(1);
\r
1259 //move past the array of 2 byte key string offsets
\r
1260 offset += getCharOffset(size);
\r
1261 //move past the padding if it exists...it's either 2 bytes or no bytes
\r
1262 offset += getCharOffset(~size & 1);
\r
1264 //and then to the proper int in the array of resources
\r
1265 offset += getIntOffset(index);
\r
1266 return (UNSIGNED_INT_MASK) & ICUResourceBundle.getInt(rawData, offset);
\r
1270 * Determines if the object at the specified index of the calling resource table
\r
1271 * is an alias. If it is, returns true
\r
1273 * @param index The index of the resource to check
\r
1274 * @returns True if the resource at 'index' is an alias, false otherwise.
\r
1277 * @deprecated This API is ICU internal only and part of a work around see ticket #6514
\r
1278 * @author Brian Rower
\r
1280 public boolean isAlias(int index)
\r
1282 //TODO this is part of a workaround for ticket #6514
\r
1283 //if index is out of the resource, return false.
\r
1288 //parent resource must be a table to call this
\r
1289 if(RES_GET_TYPE(this.resource) != TABLE)
\r
1293 long res = getResourceHandle(index);
\r
1294 return RES_GET_TYPE(res) == ALIAS ? true : false;
\r
1300 * @deprecated This API is ICU internal only and part of a workaround see ticket #6514.
\r
1301 * @author Brian Rower
\r
1303 public boolean isAlias()
\r
1305 //TODO this is part of a workaround for ticket #6514
\r
1306 return RES_GET_TYPE(this.resource) == ALIAS;
\r
1310 * Determines if the object with the specified key
\r
1311 * is an alias. If it is, returns true
\r
1313 * @param key The key of the resource to check
\r
1314 * @returns True if the resource with 'key' is an alias, false otherwise.
\r
1317 * @deprecated This API is ICU internal only and part of a workaround see ticket #6514.
\r
1318 * @author Brian Rower
\r
1320 public boolean isAlias(String k)
\r
1322 //TODO this is part of a workaround for ticket #6514
\r
1323 //this only applies to tables
\r
1324 if(RES_GET_TYPE(this.resource) != TABLE)
\r
1328 int i = getIndexOfKey(k);
\r
1329 if(i > size || i < 0)
\r
1333 return isAlias(i);
\r
1336 private int getIndexOfKey(String k)
\r
1338 //TODO this is part of a workaround for ticket #6514
\r
1339 if(RES_GET_TYPE(this.resource) != TABLE)
\r
1344 for(index = 0; index < size; index++)
\r
1346 String curKey = getKey(index);
\r
1347 if(k.equals(curKey))
\r
1356 * This method can be used to retrieve the underlying alias path (aka where the alias points to)
\r
1357 * This method was written to allow conversion from ICU back to LDML format.
\r
1361 * @author Brian Rower
\r
1363 * @deprecated This API is ICU internal only.
\r
1364 * @author Brian Rower
\r
1366 public String getAliasPath(int index)
\r
1368 //TODO cannot allow alias path to to end up in public API
\r
1369 if(!isAlias(index) || index > this.size)
\r
1374 return getStringValue(getResourceHandle(index));
\r
1381 * @deprecated This API is ICU internal only
\r
1382 * @author Brian Rower
\r
1384 public String getAliasPath()
\r
1386 //TODO cannot allow alias path to to end up in public API
\r
1387 return getStringValue(resource);
\r
1393 * @deprecated This API is ICU internal only
\r
1394 * @author Brian Rower
\r
1396 public String getAliasPath(String k)
\r
1398 //TODO cannot allow alias path to to end up in public API
\r
1399 return getAliasPath(getIndexOfKey(k));
\r
1403 * Helper method for getKeysSafe
\r
1405 private String getKey(int index)
\r
1407 //TODO this is part of a workaround for ticket #6514
\r
1408 if(index > this.size)
\r
1412 //the offset of the table
\r
1413 int offset = RES_GET_OFFSET(resource);
\r
1415 //move past the 2 byte number for the count
\r
1416 offset += getCharOffset(1);
\r
1418 //grab the key string offset from the array
\r
1419 offset = getOffset(offset, index);
\r
1421 return RES_GET_KEY(rawData, offset).toString();
\r
1425 * Returns an Enumeration of the keys belonging to this table or array.
\r
1426 * This method differs from the getKeys() method by not following alias paths. This method exposes
\r
1427 * underlying alias's. For all general purposes of the ICU resource bundle please use getKeys().
\r
1429 * @return Keys in this table or array.
\r
1431 * @deprecated This API is ICU internal only and a workaround see ticket #6514.
\r
1432 * @author Brian Rower
\r
1434 public Enumeration getKeysSafe()
\r
1436 //TODO this is part of a workaround for ticket #6514
\r
1437 //the safeness only applies to tables, so use the other method if it's not a table
\r
1438 if(RES_GET_TYPE(this.resource) != TABLE)
\r
1442 Vector v = new Vector();
\r
1444 for(index = 0; index < size; index++)
\r
1446 String curKey = getKey(index);
\r
1449 return v.elements();
\r