• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4  * *****************************************************************************
5  * Copyright (C) 2005-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  * *****************************************************************************
8  */
9 
10 package com.ibm.icu.impl;
11 
12 import java.io.BufferedReader;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.InputStreamReader;
16 import java.net.URL;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Enumeration;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.Locale;
24 import java.util.MissingResourceException;
25 import java.util.ResourceBundle;
26 import java.util.Set;
27 
28 import com.ibm.icu.impl.ICUResourceBundleReader.ReaderValue;
29 import com.ibm.icu.impl.URLHandler.URLVisitor;
30 import com.ibm.icu.util.ULocale;
31 import com.ibm.icu.util.UResourceBundle;
32 import com.ibm.icu.util.UResourceBundleIterator;
33 import com.ibm.icu.util.UResourceTypeMismatchException;
34 
35 public  class ICUResourceBundle extends UResourceBundle {
36     /**
37      * CLDR string value "∅∅∅" prevents fallback to the parent bundle.
38      */
39     public static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205";
40 
41     /**
42      * The class loader constant to be used with getBundleInstance API
43      */
44     public static final ClassLoader ICU_DATA_CLASS_LOADER = ClassLoaderUtil.getClassLoader(ICUData.class);
45 
46     /**
47      * The name of the resource containing the installed locales
48      */
49     protected static final String INSTALLED_LOCALES = "InstalledLocales";
50 
51     /**
52      * Fields for a whole bundle, rather than any specific resource in the bundle.
53      * Corresponds roughly to ICU4C/source/common/uresimp.h struct UResourceDataEntry.
54      */
55     protected static final class WholeBundle {
WholeBundle(String baseName, String localeID, ClassLoader loader, ICUResourceBundleReader reader)56         WholeBundle(String baseName, String localeID, ClassLoader loader,
57                 ICUResourceBundleReader reader) {
58             this.baseName = baseName;
59             this.localeID = localeID;
60             this.ulocale = new ULocale(localeID);
61             this.loader = loader;
62             this.reader = reader;
63         }
64 
65         String baseName;
66         String localeID;
67         ULocale ulocale;
68         ClassLoader loader;
69 
70         /**
71          * Access to the bits and bytes of the resource bundle.
72          * Hides low-level details.
73          */
74         ICUResourceBundleReader reader;
75 
76         // TODO: Remove topLevelKeys when we upgrade to Java 6 where ResourceBundle caches the keySet().
77         Set<String> topLevelKeys;
78     }
79 
80     WholeBundle wholeBundle;
81     private ICUResourceBundle container;
82 
83     /** Loader for bundle instances, for caching. */
84     private static abstract class Loader {
load()85         abstract ICUResourceBundle load();
86     }
87 
88     private static CacheBase<String, ICUResourceBundle, Loader> BUNDLE_CACHE =
89             new SoftCache<String, ICUResourceBundle, Loader>() {
90         @Override
91         protected ICUResourceBundle createInstance(String unusedKey, Loader loader) {
92             return loader.load();
93         }
94     };
95 
96     /**
97      * Returns a functionally equivalent locale, considering keywords as well, for the specified keyword.
98      * @param baseName resource specifier
99      * @param resName top level resource to consider (such as "collations")
100      * @param keyword a particular keyword to consider (such as "collation" )
101      * @param locID The requested locale
102      * @param isAvailable If non-null, 1-element array of fillin parameter that indicates whether the
103      * requested locale was available. The locale is defined as 'available' if it physically
104      * exists within the specified tree and included in 'InstalledLocales'.
105      * @param omitDefault  if true, omit keyword and value if default.
106      * 'de_DE\@collation=standard' -> 'de_DE'
107      * @return the locale
108      * @internal ICU 3.0
109      */
getFunctionalEquivalent(String baseName, ClassLoader loader, String resName, String keyword, ULocale locID, boolean isAvailable[], boolean omitDefault)110     public static final ULocale getFunctionalEquivalent(String baseName, ClassLoader loader,
111             String resName, String keyword, ULocale locID,
112             boolean isAvailable[], boolean omitDefault) {
113         String kwVal = locID.getKeywordValue(keyword);
114         String baseLoc = locID.getBaseName();
115         String defStr = null;
116         ULocale parent = new ULocale(baseLoc);
117         ULocale defLoc = null; // locale where default (found) resource is
118         boolean lookForDefault = false; // true if kwVal needs to be set
119         ULocale fullBase = null; // base locale of found (target) resource
120         int defDepth = 0; // depth of 'default' marker
121         int resDepth = 0; // depth of found resource;
122 
123         if ((kwVal == null) || (kwVal.length() == 0)
124                 || kwVal.equals(DEFAULT_TAG)) {
125             kwVal = ""; // default tag is treated as no keyword
126             lookForDefault = true;
127         }
128 
129         // Check top level locale first
130         ICUResourceBundle r = null;
131 
132         r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
133         if (isAvailable != null) {
134             isAvailable[0] = false;
135             ULocale[] availableULocales = getAvailEntry(baseName, loader).getULocaleList();
136             for (int i = 0; i < availableULocales.length; i++) {
137                 if (parent.equals(availableULocales[i])) {
138                     isAvailable[0] = true;
139                     break;
140                 }
141             }
142         }
143         // determine in which locale (if any) the currently relevant 'default' is
144         do {
145             try {
146                 ICUResourceBundle irb = (ICUResourceBundle) r.get(resName);
147                 defStr = irb.getString(DEFAULT_TAG);
148                 if (lookForDefault == true) {
149                     kwVal = defStr;
150                     lookForDefault = false;
151                 }
152                 defLoc = r.getULocale();
153             } catch (MissingResourceException t) {
154                 // Ignore error and continue search.
155             }
156             if (defLoc == null) {
157                 r = r.getParent();
158                 defDepth++;
159             }
160         } while ((r != null) && (defLoc == null));
161 
162         // Now, search for the named resource
163         parent = new ULocale(baseLoc);
164         r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
165         // determine in which locale (if any) the named resource is located
166         do {
167             try {
168                 ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
169                 /* UResourceBundle urb = */irb.get(kwVal);
170                 fullBase = irb.getULocale();
171                 // If the get() completed, we have the full base locale
172                 // If we fell back to an ancestor of the old 'default',
173                 // we need to re calculate the "default" keyword.
174                 if ((fullBase != null) && ((resDepth) > defDepth)) {
175                     defStr = irb.getString(DEFAULT_TAG);
176                     defLoc = r.getULocale();
177                     defDepth = resDepth;
178                 }
179             } catch (MissingResourceException t) {
180                 // Ignore error,
181             }
182             if (fullBase == null) {
183                 r = r.getParent();
184                 resDepth++;
185             }
186         } while ((r != null) && (fullBase == null));
187 
188         if (fullBase == null && // Could not find resource 'kwVal'
189                 (defStr != null) && // default was defined
190                 !defStr.equals(kwVal)) { // kwVal is not default
191             // couldn't find requested resource. Fall back to default.
192             kwVal = defStr; // Fall back to default.
193             parent = new ULocale(baseLoc);
194             r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent);
195             resDepth = 0;
196             // determine in which locale (if any) the named resource is located
197             do {
198                 try {
199                     ICUResourceBundle irb = (ICUResourceBundle)r.get(resName);
200                     ICUResourceBundle urb = (ICUResourceBundle)irb.get(kwVal);
201 
202                     // if we didn't fail before this..
203                     fullBase = r.getULocale();
204 
205                     // If the fetched item (urb) is in a different locale than our outer locale (r/fullBase)
206                     // then we are in a 'fallback' situation. treat as a missing resource situation.
207                     if(!fullBase.getBaseName().equals(urb.getULocale().getBaseName())) {
208                         fullBase = null; // fallback condition. Loop and try again.
209                     }
210 
211                     // If we fell back to an ancestor of the old 'default',
212                     // we need to re calculate the "default" keyword.
213                     if ((fullBase != null) && ((resDepth) > defDepth)) {
214                         defStr = irb.getString(DEFAULT_TAG);
215                         defLoc = r.getULocale();
216                         defDepth = resDepth;
217                     }
218                 } catch (MissingResourceException t) {
219                     // Ignore error, continue search.
220                 }
221                 if (fullBase == null) {
222                     r = r.getParent();
223                     resDepth++;
224                 }
225             } while ((r != null) && (fullBase == null));
226         }
227 
228         if (fullBase == null) {
229             throw new MissingResourceException(
230                 "Could not find locale containing requested or default keyword.",
231                 baseName, keyword + "=" + kwVal);
232         }
233 
234         if (omitDefault
235             && defStr.equals(kwVal) // if default was requested and
236             && resDepth <= defDepth) { // default was set in same locale or child
237             return fullBase; // Keyword value is default - no keyword needed in locale
238         } else {
239             return new ULocale(fullBase.getBaseName() + "@" + keyword + "=" + kwVal);
240         }
241     }
242 
243     /**
244      * Given a tree path and keyword, return a string enumeration of all possible values for that keyword.
245      * @param baseName resource specifier
246      * @param keyword a particular keyword to consider, must match a top level resource name
247      * within the tree. (i.e. "collations")
248      * @internal ICU 3.0
249      */
getKeywordValues(String baseName, String keyword)250     public static final String[] getKeywordValues(String baseName, String keyword) {
251         Set<String> keywords = new HashSet<String>();
252         ULocale locales[] = getAvailEntry(baseName, ICU_DATA_CLASS_LOADER).getULocaleList();
253         int i;
254 
255         for (i = 0; i < locales.length; i++) {
256             try {
257                 UResourceBundle b = UResourceBundle.getBundleInstance(baseName, locales[i]);
258                 // downcast to ICUResourceBundle?
259                 ICUResourceBundle irb = (ICUResourceBundle) (b.getObject(keyword));
260                 Enumeration<String> e = irb.getKeys();
261                 while (e.hasMoreElements()) {
262                     String s = e.nextElement();
263                     if (!DEFAULT_TAG.equals(s) && !s.startsWith("private-")) {
264                         // don't add 'default' items, nor unlisted types
265                         keywords.add(s);
266                     }
267                 }
268             } catch (Throwable t) {
269                 //System.err.println("Error in - " + new Integer(i).toString()
270                 // + " - " + t.toString());
271                 // ignore the err - just skip that resource
272             }
273         }
274         return keywords.toArray(new String[0]);
275     }
276 
277     /**
278      * This method performs multilevel fallback for fetching items from the
279      * bundle e.g: If resource is in the form de__PHONEBOOK{ collations{
280      * default{ "phonebook"} } } If the value of "default" key needs to be
281      * accessed, then do: <code>
282      *  UResourceBundle bundle = UResourceBundle.getBundleInstance("de__PHONEBOOK");
283      *  ICUResourceBundle result = null;
284      *  if(bundle instanceof ICUResourceBundle){
285      *      result = ((ICUResourceBundle) bundle).getWithFallback("collations/default");
286      *  }
287      * </code>
288      *
289      * @param path The path to the required resource key
290      * @return resource represented by the key
291      * @exception MissingResourceException If a resource was not found.
292      */
getWithFallback(String path)293     public ICUResourceBundle getWithFallback(String path) throws MissingResourceException {
294         ICUResourceBundle actualBundle = this;
295 
296         // now recurse to pick up sub levels of the items
297         ICUResourceBundle result = findResourceWithFallback(path, actualBundle, null);
298 
299         if (result == null) {
300             throw new MissingResourceException(
301                 "Can't find resource for bundle "
302                 + this.getClass().getName() + ", key " + getType(),
303                 path, getKey());
304         }
305 
306         if (result.getType() == STRING && result.getString().equals(NO_INHERITANCE_MARKER)) {
307             throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey());
308         }
309 
310         return result;
311     }
312 
at(int index)313     public ICUResourceBundle at(int index) {
314         return (ICUResourceBundle) handleGet(index, null, this);
315     }
316 
at(String key)317     public ICUResourceBundle at(String key) {
318         // don't ever presume the key is an int in disguise, like ResourceArray does.
319         if (this instanceof ICUResourceBundleImpl.ResourceTable) {
320             return (ICUResourceBundle) handleGet(key, null, this);
321         }
322         return null;
323     }
324 
325     @Override
findTopLevel(int index)326     public ICUResourceBundle findTopLevel(int index) {
327         return (ICUResourceBundle) super.findTopLevel(index);
328     }
329 
330     @Override
findTopLevel(String aKey)331     public ICUResourceBundle findTopLevel(String aKey) {
332         return (ICUResourceBundle) super.findTopLevel(aKey);
333     }
334 
335     /**
336      * Like getWithFallback, but returns null if the resource is not found instead of
337      * throwing an exception.
338      * @param path the path to the resource
339      * @return the resource, or null
340      */
findWithFallback(String path)341     public ICUResourceBundle findWithFallback(String path) {
342         return findResourceWithFallback(path, this, null);
343     }
findStringWithFallback(String path)344     public String findStringWithFallback(String path) {
345         return findStringWithFallback(path, this, null);
346     }
347 
348     // will throw type mismatch exception if the resource is not a string
getStringWithFallback(String path)349     public String getStringWithFallback(String path) throws MissingResourceException {
350         // Optimized form of getWithFallback(path).getString();
351         ICUResourceBundle actualBundle = this;
352         String result = findStringWithFallback(path, actualBundle, null);
353 
354         if (result == null) {
355             throw new MissingResourceException(
356                 "Can't find resource for bundle "
357                 + this.getClass().getName() + ", key " + getType(),
358                 path, getKey());
359         }
360 
361         if (result.equals(NO_INHERITANCE_MARKER)) {
362             throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey());
363         }
364         return result;
365     }
366 
getAllItemsWithFallbackNoFail(String path, UResource.Sink sink)367     public void getAllItemsWithFallbackNoFail(String path, UResource.Sink sink) {
368         try {
369             getAllItemsWithFallback(path, sink);
370         } catch (MissingResourceException e) {
371             // Quietly ignore the exception.
372         }
373     }
374 
getAllItemsWithFallback(String path, UResource.Sink sink)375     public void getAllItemsWithFallback(String path, UResource.Sink sink)
376             throws MissingResourceException {
377         // Collect existing and parsed key objects into an array of keys,
378         // rather than assembling and parsing paths.
379         int numPathKeys = countPathKeys(path);  // How much deeper does the path go?
380         ICUResourceBundle rb;
381         if (numPathKeys == 0) {
382             rb = this;
383         } else {
384             // Get the keys for finding the target.
385             int depth = getResDepth();  // How deep are we in this bundle?
386             String[] pathKeys = new String[depth + numPathKeys];
387             getResPathKeys(path, numPathKeys, pathKeys, depth);
388             rb = findResourceWithFallback(pathKeys, depth, this, null);
389             if (rb == null) {
390                 throw new MissingResourceException(
391                     "Can't find resource for bundle "
392                     + this.getClass().getName() + ", key " + getType(),
393                     path, getKey());
394             }
395         }
396         UResource.Key key = new UResource.Key();
397         ReaderValue readerValue = new ReaderValue();
398         rb.getAllItemsWithFallback(key, readerValue, sink);
399     }
400 
getAllItemsWithFallback( UResource.Key key, ReaderValue readerValue, UResource.Sink sink)401     private void getAllItemsWithFallback(
402             UResource.Key key, ReaderValue readerValue, UResource.Sink sink) {
403         // We recursively enumerate child-first,
404         // only storing parent items in the absence of child items.
405         // The sink needs to store a placeholder value for the no-fallback/no-inheritance marker
406         // to prevent a parent item from being stored.
407         //
408         // It would be possible to recursively enumerate parent-first,
409         // overriding parent items with child items.
410         // When the sink sees the no-fallback/no-inheritance marker,
411         // then it would remove the parent's item.
412         // We would deserialize parent values even though they are overridden in a child bundle.
413         ICUResourceBundleImpl impl = (ICUResourceBundleImpl)this;
414         readerValue.reader = impl.wholeBundle.reader;
415         readerValue.res = impl.getResource();
416         key.setString(this.key != null ? this.key : "");
417         sink.put(key, readerValue, parent == null);
418         if (parent != null) {
419             // We might try to query the sink whether
420             // any fallback from the parent bundle is still possible.
421             ICUResourceBundle parentBundle = (ICUResourceBundle)parent;
422             ICUResourceBundle rb;
423             int depth = getResDepth();
424             if (depth == 0) {
425                 rb = parentBundle;
426             } else {
427                 // Re-fetch the path keys: They may differ from the original ones
428                 // if we had followed an alias.
429                 String[] pathKeys = new String[depth];
430                 getResPathKeys(pathKeys, depth);
431                 rb = findResourceWithFallback(pathKeys, 0, parentBundle, null);
432             }
433             if (rb != null) {
434                 rb.getAllItemsWithFallback(key, readerValue, sink);
435             }
436         }
437     }
438 
439     /**
440      * Return a set of the locale names supported by a collection of resource
441      * bundles.
442      *
443      * @param bundlePrefix the prefix of the resource bundles to use.
444      */
getAvailableLocaleNameSet(String bundlePrefix, ClassLoader loader)445     public static Set<String> getAvailableLocaleNameSet(String bundlePrefix, ClassLoader loader) {
446         return getAvailEntry(bundlePrefix, loader).getLocaleNameSet();
447     }
448 
449     /**
450      * Return a set of all the locale names supported by a collection of
451      * resource bundles.
452      */
getFullLocaleNameSet()453     public static Set<String> getFullLocaleNameSet() {
454         return getFullLocaleNameSet(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
455     }
456 
457     /**
458      * Return a set of all the locale names supported by a collection of
459      * resource bundles.
460      *
461      * @param bundlePrefix the prefix of the resource bundles to use.
462      */
getFullLocaleNameSet(String bundlePrefix, ClassLoader loader)463     public static Set<String> getFullLocaleNameSet(String bundlePrefix, ClassLoader loader) {
464         return getAvailEntry(bundlePrefix, loader).getFullLocaleNameSet();
465     }
466 
467     /**
468      * Return a set of the locale names supported by a collection of resource
469      * bundles.
470      */
getAvailableLocaleNameSet()471     public static Set<String> getAvailableLocaleNameSet() {
472         return getAvailableLocaleNameSet(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
473     }
474 
475     /**
476      * Get the set of Locales installed in the specified bundles.
477      * @return the list of available locales
478      */
getAvailableULocales(String baseName, ClassLoader loader)479     public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader) {
480         return getAvailEntry(baseName, loader).getULocaleList();
481     }
482 
483     /**
484      * Get the set of ULocales installed the base bundle.
485      * @return the list of available locales
486      */
getAvailableULocales()487     public static final ULocale[] getAvailableULocales() {
488         return getAvailableULocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER);
489     }
490 
491     /**
492      * Get the set of Locales installed in the specified bundles.
493      * @return the list of available locales
494      */
getAvailableLocales(String baseName, ClassLoader loader)495     public static final Locale[] getAvailableLocales(String baseName, ClassLoader loader) {
496         return getAvailEntry(baseName, loader).getLocaleList();
497     }
498 
499    /**
500      * Get the set of Locales installed the base bundle.
501      * @return the list of available locales
502      */
getAvailableLocales()503     public static final Locale[] getAvailableLocales() {
504         return getAvailEntry(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER).getLocaleList();
505     }
506 
507     /**
508      * Convert a list of ULocales to a list of Locales.  ULocales with a script code will not be converted
509      * since they cannot be represented as a Locale.  This means that the two lists will <b>not</b> match
510      * one-to-one, and that the returned list might be shorter than the input list.
511      * @param ulocales a list of ULocales to convert to a list of Locales.
512      * @return the list of converted ULocales
513      */
getLocaleList(ULocale[] ulocales)514     public static final Locale[] getLocaleList(ULocale[] ulocales) {
515         ArrayList<Locale> list = new ArrayList<Locale>(ulocales.length);
516         HashSet<Locale> uniqueSet = new HashSet<Locale>();
517         for (int i = 0; i < ulocales.length; i++) {
518             Locale loc = ulocales[i].toLocale();
519             if (!uniqueSet.contains(loc)) {
520                 list.add(loc);
521                 uniqueSet.add(loc);
522             }
523         }
524         return list.toArray(new Locale[list.size()]);
525     }
526 
527     /**
528      * Returns the locale of this resource bundle. This method can be used after
529      * a call to getBundle() to determine whether the resource bundle returned
530      * really corresponds to the requested locale or is a fallback.
531      *
532      * @return the locale of this resource bundle
533      */
534     @Override
getLocale()535     public Locale getLocale() {
536         return getULocale().toLocale();
537     }
538 
539 
540     // ========== privates ==========
541     private static final String ICU_RESOURCE_INDEX = "res_index";
542 
543     private static final String DEFAULT_TAG = "default";
544 
545     // The name of text file generated by ICU4J build script including all locale names
546     // (canonical, alias and root)
547     private static final String FULL_LOCALE_NAMES_LIST = "fullLocaleNames.lst";
548 
549     // Flag for enabling/disabling debugging code
550     private static final boolean DEBUG = ICUDebug.enabled("localedata");
551 
createULocaleList(String baseName, ClassLoader root)552     private static final ULocale[] createULocaleList(String baseName,
553             ClassLoader root) {
554         // the canned list is a subset of all the available .res files, the idea
555         // is we don't export them
556         // all. gotta be a better way to do this, since to add a locale you have
557         // to update this list,
558         // and it's embedded in our binary resources.
559         ICUResourceBundle bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
560 
561         bundle = (ICUResourceBundle)bundle.get(INSTALLED_LOCALES);
562         int length = bundle.getSize();
563         int i = 0;
564         ULocale[] locales = new ULocale[length];
565         UResourceBundleIterator iter = bundle.getIterator();
566         iter.reset();
567         while (iter.hasNext()) {
568             String locstr = iter.next().getKey();
569             if (locstr.equals("root")) {
570                 locales[i++] = ULocale.ROOT;
571             } else {
572                 locales[i++] = new ULocale(locstr);
573             }
574         }
575         bundle = null;
576         return locales;
577     }
578 
579     // Same as createULocaleList() but catches the MissingResourceException
580     // and returns the data in a different form.
addLocaleIDsFromIndexBundle(String baseName, ClassLoader root, Set<String> locales)581     private static final void addLocaleIDsFromIndexBundle(String baseName,
582             ClassLoader root, Set<String> locales) {
583         ICUResourceBundle bundle;
584         try {
585             bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true);
586             bundle = (ICUResourceBundle) bundle.get(INSTALLED_LOCALES);
587         } catch (MissingResourceException e) {
588             if (DEBUG) {
589                 System.out.println("couldn't find " + baseName + '/' + ICU_RESOURCE_INDEX + ".res");
590                 Thread.dumpStack();
591             }
592             return;
593         }
594         UResourceBundleIterator iter = bundle.getIterator();
595         iter.reset();
596         while (iter.hasNext()) {
597             String locstr = iter.next(). getKey();
598             locales.add(locstr);
599         }
600     }
601 
addBundleBaseNamesFromClassLoader( final String bn, final ClassLoader root, final Set<String> names)602     private static final void addBundleBaseNamesFromClassLoader(
603             final String bn, final ClassLoader root, final Set<String> names) {
604         java.security.AccessController
605             .doPrivileged(new java.security.PrivilegedAction<Void>() {
606                 @Override
607                 public Void run() {
608                     try {
609                         // bn has a trailing slash: The WebSphere class loader would return null
610                         // for a raw directory name without it.
611                         Enumeration<URL> urls = root.getResources(bn);
612                         if (urls == null) {
613                             return null;
614                         }
615                         URLVisitor v = new URLVisitor() {
616                             @Override
617                             public void visit(String s) {
618                                 if (s.endsWith(".res")) {
619                                     String locstr = s.substring(0, s.length() - 4);
620                                     names.add(locstr);
621                                 }
622                             }
623                         };
624                         while (urls.hasMoreElements()) {
625                             URL url = urls.nextElement();
626                             URLHandler handler = URLHandler.get(url);
627                             if (handler != null) {
628                                 handler.guide(v, false);
629                             } else {
630                                 if (DEBUG) System.out.println("handler for " + url + " is null");
631                             }
632                         }
633                     } catch (IOException e) {
634                         if (DEBUG) System.out.println("ouch: " + e.getMessage());
635                     }
636                     return null;
637                 }
638             });
639     }
640 
addLocaleIDsFromListFile(String bn, ClassLoader root, Set<String> locales)641     private static void addLocaleIDsFromListFile(String bn, ClassLoader root, Set<String> locales) {
642         try {
643             InputStream s = root.getResourceAsStream(bn + FULL_LOCALE_NAMES_LIST);
644             if (s != null) {
645                 BufferedReader br = new BufferedReader(new InputStreamReader(s, "ASCII"));
646                 try {
647                     String line;
648                     while ((line = br.readLine()) != null) {
649                         if (line.length() != 0 && !line.startsWith("#")) {
650                             locales.add(line);
651                         }
652                     }
653                 }
654                 finally {
655                     br.close();
656                 }
657             }
658         } catch (IOException ignored) {
659             // swallow it
660         }
661     }
662 
createFullLocaleNameSet(String baseName, ClassLoader loader)663     private static Set<String> createFullLocaleNameSet(String baseName, ClassLoader loader) {
664         String bn = baseName.endsWith("/") ? baseName : baseName + "/";
665         Set<String> set = new HashSet<String>();
666         String skipScan = ICUConfig.get("com.ibm.icu.impl.ICUResourceBundle.skipRuntimeLocaleResourceScan", "false");
667         if (!skipScan.equalsIgnoreCase("true")) {
668             // scan available locale resources under the base url first
669             addBundleBaseNamesFromClassLoader(bn, loader, set);
670             if (baseName.startsWith(ICUData.ICU_BASE_NAME)) {
671                 String folder;
672                 if (baseName.length() == ICUData.ICU_BASE_NAME.length()) {
673                     folder = "";
674                 } else if (baseName.charAt(ICUData.ICU_BASE_NAME.length()) == '/') {
675                     folder = baseName.substring(ICUData.ICU_BASE_NAME.length() + 1);
676                 } else {
677                     folder = null;
678                 }
679                 if (folder != null) {
680                     ICUBinary.addBaseNamesInFileFolder(folder, ".res", set);
681                 }
682             }
683             set.remove(ICU_RESOURCE_INDEX);  // "res_index"
684             // HACK: TODO: Figure out how we can distinguish locale data from other data items.
685             Iterator<String> iter = set.iterator();
686             while (iter.hasNext()) {
687                 String name = iter.next();
688                 if ((name.length() == 1 || name.length() > 3) && name.indexOf('_') < 0) {
689                     // Does not look like a locale ID.
690                     iter.remove();
691                 }
692             }
693         }
694         // look for prebuilt full locale names list next
695         if (set.isEmpty()) {
696             if (DEBUG) System.out.println("unable to enumerate data files in " + baseName);
697             addLocaleIDsFromListFile(bn, loader, set);
698         }
699         if (set.isEmpty()) {
700             // Use locale name set as the last resort fallback
701             addLocaleIDsFromIndexBundle(baseName, loader, set);
702         }
703         // We need to have the root locale in the set, but not as "root".
704         set.remove("root");
705         set.add(ULocale.ROOT.toString());  // ""
706         return Collections.unmodifiableSet(set);
707     }
708 
createLocaleNameSet(String baseName, ClassLoader loader)709     private static Set<String> createLocaleNameSet(String baseName, ClassLoader loader) {
710         HashSet<String> set = new HashSet<String>();
711         addLocaleIDsFromIndexBundle(baseName, loader, set);
712         return Collections.unmodifiableSet(set);
713     }
714 
715     /**
716      * Holds the prefix, and lazily creates the Locale[] list or the locale name
717      * Set as needed.
718      */
719     private static final class AvailEntry {
720         private String prefix;
721         private ClassLoader loader;
722         private volatile ULocale[] ulocales;
723         private volatile Locale[] locales;
724         private volatile Set<String> nameSet;
725         private volatile Set<String> fullNameSet;
726 
AvailEntry(String prefix, ClassLoader loader)727         AvailEntry(String prefix, ClassLoader loader) {
728             this.prefix = prefix;
729             this.loader = loader;
730         }
731 
getULocaleList()732         ULocale[] getULocaleList() {
733             if (ulocales == null) {
734                 synchronized(this) {
735                     if (ulocales == null) {
736                         ulocales = createULocaleList(prefix, loader);
737                     }
738                 }
739             }
740             return ulocales;
741         }
getLocaleList()742         Locale[] getLocaleList() {
743             if (locales == null) {
744                 getULocaleList();
745                 synchronized(this) {
746                     if (locales == null) {
747                         locales = ICUResourceBundle.getLocaleList(ulocales);
748                     }
749                 }
750             }
751             return locales;
752         }
getLocaleNameSet()753         Set<String> getLocaleNameSet() {
754             if (nameSet == null) {
755                 synchronized(this) {
756                     if (nameSet == null) {
757                         nameSet = createLocaleNameSet(prefix, loader);
758                     }
759                 }
760             }
761             return nameSet;
762         }
getFullLocaleNameSet()763         Set<String> getFullLocaleNameSet() {
764             // When there's no prebuilt index, we iterate through the jar files
765             // and read the contents to build it.  If many threads try to read the
766             // same jar at the same time, java thrashes.  Synchronize here
767             // so that we can avoid this problem. We don't synchronize on the
768             // other methods since they don't do this.
769             //
770             // This is the common entry point for access into the code that walks
771             // through the resources, and is cached.  So it's a good place to lock
772             // access.  Locking in the URLHandler doesn't give us a common object
773             // to lock.
774             if (fullNameSet == null) {
775                 synchronized(this) {
776                     if (fullNameSet == null) {
777                         fullNameSet = createFullLocaleNameSet(prefix, loader);
778                     }
779                 }
780             }
781             return fullNameSet;
782         }
783     }
784 
785 
786     /*
787      * Cache used for AvailableEntry
788      */
789     private static CacheBase<String, AvailEntry, ClassLoader> GET_AVAILABLE_CACHE =
790         new SoftCache<String, AvailEntry, ClassLoader>()  {
791             @Override
792             protected AvailEntry createInstance(String key, ClassLoader loader) {
793                 return new AvailEntry(key, loader);
794             }
795         };
796 
797     /**
798      * Stores the locale information in a cache accessed by key (bundle prefix).
799      * The cached objects are AvailEntries. The cache is implemented by SoftCache
800      * so it can be GC'd.
801      */
getAvailEntry(String key, ClassLoader loader)802     private static AvailEntry getAvailEntry(String key, ClassLoader loader) {
803         return GET_AVAILABLE_CACHE.getInstance(key, loader);
804     }
805 
findResourceWithFallback(String path, UResourceBundle actualBundle, UResourceBundle requested)806     private static final ICUResourceBundle findResourceWithFallback(String path,
807             UResourceBundle actualBundle, UResourceBundle requested) {
808         if (path.length() == 0) {
809             return null;
810         }
811         ICUResourceBundle base = (ICUResourceBundle) actualBundle;
812         // Collect existing and parsed key objects into an array of keys,
813         // rather than assembling and parsing paths.
814         int depth = base.getResDepth();
815         int numPathKeys = countPathKeys(path);
816         assert numPathKeys > 0;
817         String[] keys = new String[depth + numPathKeys];
818         getResPathKeys(path, numPathKeys, keys, depth);
819         return findResourceWithFallback(keys, depth, base, requested);
820     }
821 
findResourceWithFallback( String[] keys, int depth, ICUResourceBundle base, UResourceBundle requested)822     private static final ICUResourceBundle findResourceWithFallback(
823             String[] keys, int depth,
824             ICUResourceBundle base, UResourceBundle requested) {
825         if (requested == null) {
826             requested = base;
827         }
828 
829         for (;;) {  // Iterate over the parent bundles.
830             for (;;) {  // Iterate over the keys on the requested path, within a bundle.
831                 String subKey = keys[depth++];
832                 ICUResourceBundle sub = (ICUResourceBundle) base.handleGet(subKey, null, requested);
833                 if (sub == null) {
834                     --depth;
835                     break;
836                 }
837                 if (depth == keys.length) {
838                     // We found it.
839                     return sub;
840                 }
841                 base = sub;
842             }
843             // Try the parent bundle of the last-found resource.
844             ICUResourceBundle nextBase = base.getParent();
845             if (nextBase == null) {
846                 return null;
847             }
848             // If we followed an alias, then we may have switched bundle (locale) and key path.
849             // Set the lower parts of the path according to the last-found resource.
850             // This relies on a resource found via alias to have its original location information,
851             // rather than the location of the alias.
852             int baseDepth = base.getResDepth();
853             if (depth != baseDepth) {
854                 String[] newKeys = new String[baseDepth + (keys.length - depth)];
855                 System.arraycopy(keys, depth, newKeys, baseDepth, keys.length - depth);
856                 keys = newKeys;
857             }
858             base.getResPathKeys(keys, baseDepth);
859             base = nextBase;
860             depth = 0;  // getParent() returned a top level table resource.
861         }
862     }
863 
864     /**
865      * Like findResourceWithFallback(...).getString() but with minimal creation of intermediate
866      * ICUResourceBundle objects.
867      */
findStringWithFallback(String path, UResourceBundle actualBundle, UResourceBundle requested)868     private static final String findStringWithFallback(String path,
869             UResourceBundle actualBundle, UResourceBundle requested) {
870         if (path.length() == 0) {
871             return null;
872         }
873         if (!(actualBundle instanceof ICUResourceBundleImpl.ResourceContainer)) {
874             return null;
875         }
876         if (requested == null) {
877             requested = actualBundle;
878         }
879 
880         ICUResourceBundle base = (ICUResourceBundle) actualBundle;
881         ICUResourceBundleReader reader = base.wholeBundle.reader;
882         int res = RES_BOGUS;
883 
884         // Collect existing and parsed key objects into an array of keys,
885         // rather than assembling and parsing paths.
886         int baseDepth = base.getResDepth();
887         int depth = baseDepth;
888         int numPathKeys = countPathKeys(path);
889         assert numPathKeys > 0;
890         String[] keys = new String[depth + numPathKeys];
891         getResPathKeys(path, numPathKeys, keys, depth);
892 
893         for (;;) {  // Iterate over the parent bundles.
894             for (;;) {  // Iterate over the keys on the requested path, within a bundle.
895                 ICUResourceBundleReader.Container readerContainer;
896                 if (res == RES_BOGUS) {
897                     int type = base.getType();
898                     if (type == TABLE || type == ARRAY) {
899                         readerContainer = ((ICUResourceBundleImpl.ResourceContainer)base).value;
900                     } else {
901                         break;
902                     }
903                 } else {
904                     int type = ICUResourceBundleReader.RES_GET_TYPE(res);
905                     if (ICUResourceBundleReader.URES_IS_TABLE(type)) {
906                         readerContainer = reader.getTable(res);
907                     } else if (ICUResourceBundleReader.URES_IS_ARRAY(type)) {
908                         readerContainer = reader.getArray(res);
909                     } else {
910                         res = RES_BOGUS;
911                         break;
912                     }
913                 }
914                 String subKey = keys[depth++];
915                 res = readerContainer.getResource(reader, subKey);
916                 if (res == RES_BOGUS) {
917                     --depth;
918                     break;
919                 }
920                 ICUResourceBundle sub;
921                 if (ICUResourceBundleReader.RES_GET_TYPE(res) == ALIAS) {
922                     base.getResPathKeys(keys, baseDepth);
923                     sub = getAliasedResource(base, keys, depth, subKey, res, null, requested);
924                 } else {
925                     sub = null;
926                 }
927                 if (depth == keys.length) {
928                     // We found it.
929                     if (sub != null) {
930                         return sub.getString();  // string from alias handling
931                     } else {
932                         String s = reader.getString(res);
933                         if (s == null) {
934                             throw new UResourceTypeMismatchException("");
935                         }
936                         return s;
937                     }
938                 }
939                 if (sub != null) {
940                     base = sub;
941                     reader = base.wholeBundle.reader;
942                     res = RES_BOGUS;
943                     // If we followed an alias, then we may have switched bundle (locale) and key path.
944                     // Reserve space for the lower parts of the path according to the last-found resource.
945                     // This relies on a resource found via alias to have its original location information,
946                     // rather than the location of the alias.
947                     baseDepth = base.getResDepth();
948                     if (depth != baseDepth) {
949                         String[] newKeys = new String[baseDepth + (keys.length - depth)];
950                         System.arraycopy(keys, depth, newKeys, baseDepth, keys.length - depth);
951                         keys = newKeys;
952                         depth = baseDepth;
953                     }
954                 }
955             }
956             // Try the parent bundle of the last-found resource.
957             ICUResourceBundle nextBase = base.getParent();
958             if (nextBase == null) {
959                 return null;
960             }
961             // We probably have not yet set the lower parts of the key path.
962             base.getResPathKeys(keys, baseDepth);
963             base = nextBase;
964             reader = base.wholeBundle.reader;
965             depth = baseDepth = 0;  // getParent() returned a top level table resource.
966         }
967     }
968 
getResDepth()969     private int getResDepth() {
970         return (container == null) ? 0 : container.getResDepth() + 1;
971     }
972 
973     /**
974      * Fills some of the keys array with the keys on the path to this resource object.
975      * Writes the top-level key into index 0 and increments from there.
976      *
977      * @param keys
978      * @param depth must be {@link #getResDepth()}
979      */
getResPathKeys(String[] keys, int depth)980     private void getResPathKeys(String[] keys, int depth) {
981         ICUResourceBundle b = this;
982         while (depth > 0) {
983             keys[--depth] = b.key;
984             b = b.container;
985             assert (depth == 0) == (b.container == null);
986         }
987     }
988 
countPathKeys(String path)989     private static int countPathKeys(String path) {
990         if (path.isEmpty()) {
991             return 0;
992         }
993         int num = 1;
994         for (int i = 0; i < path.length(); ++i) {
995             if (path.charAt(i) == RES_PATH_SEP_CHAR) {
996                 ++num;
997             }
998         }
999         return num;
1000     }
1001 
1002     /**
1003      * Fills some of the keys array (from start) with the num keys from the path string.
1004      *
1005      * @param path path string
1006      * @param num must be {@link #countPathKeys(String)}
1007      * @param keys
1008      * @param start index where the first path key is stored
1009      */
getResPathKeys(String path, int num, String[] keys, int start)1010     private static void getResPathKeys(String path, int num, String[] keys, int start) {
1011         if (num == 0) {
1012             return;
1013         }
1014         if (num == 1) {
1015             keys[start] = path;
1016             return;
1017         }
1018         int i = 0;
1019         for (;;) {
1020             int j = path.indexOf(RES_PATH_SEP_CHAR, i);
1021             assert j >= i;
1022             keys[start++] = path.substring(i, j);
1023             if (num == 2) {
1024                 assert path.indexOf(RES_PATH_SEP_CHAR, j + 1) < 0;
1025                 keys[start] = path.substring(j + 1);
1026                 break;
1027             } else {
1028                 i = j + 1;
1029                 --num;
1030             }
1031         }
1032     }
1033 
1034     @Override
1035     public boolean equals(Object other) {
1036         if (this == other) {
1037             return true;
1038         }
1039         if (other instanceof ICUResourceBundle) {
1040             ICUResourceBundle o = (ICUResourceBundle) other;
1041             if (getBaseName().equals(o.getBaseName())
1042                     && getLocaleID().equals(o.getLocaleID())) {
1043                 return true;
1044             }
1045         }
1046         return false;
1047     }
1048 
1049     @Override
1050     public int hashCode() {
1051         assert false : "hashCode not designed";
1052         return 42;
1053     }
1054 
1055     public enum OpenType {  // C++ uresbund.cpp: enum UResOpenType
1056         /**
1057          * Open a resource bundle for the locale;
1058          * if there is not even a base language bundle, then fall back to the default locale;
1059          * if there is no bundle for that either, then load the root bundle.
1060          *
1061          * <p>This is the default bundle loading behavior.
1062          */
1063         LOCALE_DEFAULT_ROOT,
1064         // TODO: ICU ticket #11271 "consistent default locale across locale trees"
1065         // Add an option to look at the main locale tree for whether to
1066         // fall back to root directly (if the locale has main data) or
1067         // fall back to the default locale first (if the locale does not even have main data).
1068         /**
1069          * Open a resource bundle for the locale;
1070          * if there is not even a base language bundle, then load the root bundle;
1071          * never fall back to the default locale.
1072          *
1073          * <p>This is used for algorithms that have good pan-Unicode default behavior,
1074          * such as case mappings, collation, and segmentation (BreakIterator).
1075          */
1076         LOCALE_ROOT,
1077         /**
1078          * Open a resource bundle for the locale;
1079          * if there is not even a base language bundle, then fail;
1080          * never fall back to the default locale nor to the root locale.
1081          *
1082          * <p>This is used when fallback to another language is not desired
1083          * and the root locale is not generally useful.
1084          * For example, {@link com.ibm.icu.util.LocaleData#setNoSubstitute(boolean)}
1085          * or currency display names for {@link com.ibm.icu.text.LocaleDisplayNames}.
1086          */
1087         LOCALE_ONLY,
1088         /**
1089          * Open a resource bundle for the exact bundle name as requested;
1090          * no fallbacks, do not load parent bundles.
1091          *
1092          * <p>This is used for supplemental (non-locale) data.
1093          */
1094         DIRECT
1095     };
1096 
1097     // This method is for super class's instantiateBundle method
1098     public static ICUResourceBundle getBundleInstance(String baseName, String localeID,
1099             ClassLoader root, boolean disableFallback) {
1100         return getBundleInstance(baseName, localeID, root,
1101                 disableFallback ? OpenType.DIRECT : OpenType.LOCALE_DEFAULT_ROOT);
1102     }
1103 
1104     public static ICUResourceBundle getBundleInstance(
1105             String baseName, ULocale locale, OpenType openType) {
1106         if (locale == null) {
1107             locale = ULocale.getDefault();
1108         }
1109         return getBundleInstance(baseName, locale.getBaseName(),
1110                 ICUResourceBundle.ICU_DATA_CLASS_LOADER, openType);
1111     }
1112 
1113     public static ICUResourceBundle getBundleInstance(String baseName, String localeID,
1114             ClassLoader root, OpenType openType) {
1115         if (baseName == null) {
1116             baseName = ICUData.ICU_BASE_NAME;
1117         }
1118         localeID = ULocale.getBaseName(localeID);
1119         ICUResourceBundle b;
1120         if (openType == OpenType.LOCALE_DEFAULT_ROOT) {
1121             b = instantiateBundle(baseName, localeID, ULocale.getDefault().getBaseName(),
1122                     root, openType);
1123         } else {
1124             b = instantiateBundle(baseName, localeID, null, root, openType);
1125         }
1126         if(b==null){
1127             throw new MissingResourceException(
1128                     "Could not find the bundle "+ baseName+"/"+ localeID+".res","","");
1129         }
1130         return b;
1131     }
1132 
1133     private static boolean localeIDStartsWithLangSubtag(String localeID, String lang) {
1134         return localeID.startsWith(lang) &&
1135                 (localeID.length() == lang.length() || localeID.charAt(lang.length()) == '_');
1136     }
1137 
1138     private static ICUResourceBundle instantiateBundle(
1139             final String baseName, final String localeID, final String defaultID,
1140             final ClassLoader root, final OpenType openType) {
1141         assert localeID.indexOf('@') < 0;
1142         assert defaultID == null || defaultID.indexOf('@') < 0;
1143         final String fullName = ICUResourceBundleReader.getFullName(baseName, localeID);
1144         char openTypeChar = (char)('0' + openType.ordinal());
1145         String cacheKey = openType != OpenType.LOCALE_DEFAULT_ROOT ?
1146                 fullName + '#' + openTypeChar :
1147                     fullName + '#' + openTypeChar + '#' + defaultID;
1148         return BUNDLE_CACHE.getInstance(cacheKey, new Loader() {
1149                 @Override
1150                 public ICUResourceBundle load() {
1151             if(DEBUG) System.out.println("Creating "+fullName);
1152             // here we assume that java type resource bundle organization
1153             // is required then the base name contains '.' else
1154             // the resource organization is of ICU type
1155             // so clients can instantiate resources of the type
1156             // com.mycompany.data.MyLocaleElements_en.res and
1157             // com.mycompany.data.MyLocaleElements.res
1158             //
1159             final String rootLocale = (baseName.indexOf('.')==-1) ? "root" : "";
1160             String localeName = localeID.isEmpty() ? rootLocale : localeID;
1161             ICUResourceBundle b = ICUResourceBundle.createBundle(baseName, localeName, root);
1162 
1163             if(DEBUG)System.out.println("The bundle created is: "+b+" and openType="+openType+" and bundle.getNoFallback="+(b!=null && b.getNoFallback()));
1164             if (openType == OpenType.DIRECT || (b != null && b.getNoFallback())) {
1165                 // no fallback because the caller said so or because the bundle says so
1166                 //
1167                 // TODO for b!=null: In C++, ures_openDirect() builds the parent chain
1168                 // for its bundle unless its nofallback flag is set.
1169                 // Otherwise we get test failures.
1170                 // For example, item aliases are followed via ures_openDirect(),
1171                 // and fail if the target bundle needs fallbacks but the chain is not set.
1172                 // Figure out why Java does not build the parent chain
1173                 // for a bundle that does not have nofallback.
1174                 // Are the relevant test cases just disabled?
1175                 // Do item aliases not get followed via "direct" loading?
1176                 return b;
1177             }
1178 
1179             // fallback to locale ID parent
1180             if(b == null){
1181                 int i = localeName.lastIndexOf('_');
1182                 if (i != -1) {
1183                     // Chop off the last underscore and the subtag after that.
1184                     String temp = localeName.substring(0, i);
1185                     b = instantiateBundle(baseName, temp, defaultID, root, openType);
1186                 }else{
1187                     // No underscore, only a base language subtag.
1188                     if(openType == OpenType.LOCALE_DEFAULT_ROOT &&
1189                             !localeIDStartsWithLangSubtag(defaultID, localeName)) {
1190                         // Go to the default locale before root.
1191                         b = instantiateBundle(baseName, defaultID, defaultID, root, openType);
1192                     } else if(openType != OpenType.LOCALE_ONLY && !rootLocale.isEmpty()) {
1193                         // Ultimately go to root.
1194                         b = ICUResourceBundle.createBundle(baseName, rootLocale, root);
1195                     }
1196                 }
1197             }else{
1198                 UResourceBundle parent = null;
1199                 localeName = b.getLocaleID();
1200                 int i = localeName.lastIndexOf('_');
1201 
1202                 // TODO: C++ uresbund.cpp also checks for %%ParentIsRoot. Why not Java?
1203                 String parentLocaleName = ((ICUResourceBundleImpl.ResourceTable)b).findString("%%Parent");
1204                 if (parentLocaleName != null) {
1205                     parent = instantiateBundle(baseName, parentLocaleName, defaultID, root, openType);
1206                 } else if (i != -1) {
1207                     parent = instantiateBundle(baseName, localeName.substring(0, i), defaultID, root, openType);
1208                 } else if (!localeName.equals(rootLocale)){
1209                     parent = instantiateBundle(baseName, rootLocale, defaultID, root, openType);
1210                 }
1211 
1212                 if (!b.equals(parent)){
1213                     b.setParent(parent);
1214                 }
1215             }
1216             return b;
1217         }});
1218     }
1219 
1220     ICUResourceBundle get(String aKey, HashMap<String, String> aliasesVisited, UResourceBundle requested) {
1221         ICUResourceBundle obj = (ICUResourceBundle)handleGet(aKey, aliasesVisited, requested);
1222         if (obj == null) {
1223             obj = getParent();
1224             if (obj != null) {
1225                 //call the get method to recursively fetch the resource
1226                 obj = obj.get(aKey, aliasesVisited, requested);
1227             }
1228             if (obj == null) {
1229                 String fullName = ICUResourceBundleReader.getFullName(getBaseName(), getLocaleID());
1230                 throw new MissingResourceException(
1231                         "Can't find resource for bundle " + fullName + ", key "
1232                                 + aKey, this.getClass().getName(), aKey);
1233             }
1234         }
1235         return obj;
1236     }
1237 
1238     /** Data member where the subclasses store the key. */
1239     protected String key;
1240 
1241     /**
1242      * A resource word value that means "no resource".
1243      * Note: 0xffffffff == -1
1244      * This has the same value as UResourceBundle.NONE, but they are semantically
1245      * different and should be used appropriately according to context:
1246      * NONE means "no type".
1247      * (The type of RES_BOGUS is RES_RESERVED=15 which was defined in ICU4C ures.h.)
1248      */
1249     public static final int RES_BOGUS = 0xffffffff;
1250     //blic static final int RES_MAX_OFFSET = 0x0fffffff;
1251 
1252     /**
1253      * Resource type constant for aliases;
1254      * internally stores a string which identifies the actual resource
1255      * storing the data (can be in a different resource bundle).
1256      * Resolved internally before delivering the actual resource through the API.
1257      */
1258     public static final int ALIAS = 3;
1259 
1260     /** Resource type constant for tables with 32-bit count, key offsets and values. */
1261     public static final int TABLE32 = 4;
1262 
1263     /**
1264      * Resource type constant for tables with 16-bit count, key offsets and values.
1265      * All values are STRING_V2 strings.
1266      */
1267     public static final int TABLE16 = 5;
1268 
1269     /** Resource type constant for 16-bit Unicode strings in formatVersion 2. */
1270     public static final int STRING_V2 = 6;
1271 
1272     /**
1273      * Resource type constant for arrays with 16-bit count and values.
1274      * All values are STRING_V2 strings.
1275      */
1276     public static final int ARRAY16 = 9;
1277 
1278     /* Resource type 15 is not defined but effectively used by RES_BOGUS=0xffffffff. */
1279 
1280     /**
1281     * Create a bundle using a reader.
1282     * @param baseName The name for the bundle.
1283     * @param localeID The locale identification.
1284     * @param root The ClassLoader object root.
1285     * @return the new bundle
1286     */
1287     public static ICUResourceBundle createBundle(String baseName, String localeID, ClassLoader root) {
1288         ICUResourceBundleReader reader = ICUResourceBundleReader.getReader(baseName, localeID, root);
1289         if (reader == null) {
1290             // could not open the .res file
1291             return null;
1292         }
1293         return getBundle(reader, baseName, localeID, root);
1294     }
1295 
1296     @Override
1297     protected String getLocaleID() {
1298         return wholeBundle.localeID;
1299     }
1300 
1301     @Override
1302     protected String getBaseName() {
1303         return wholeBundle.baseName;
1304     }
1305 
1306     @Override
1307     public ULocale getULocale() {
1308         return wholeBundle.ulocale;
1309     }
1310 
1311     /**
1312      * Returns true if this is the root bundle, or an item in the root bundle.
1313      */
1314     public boolean isRoot() {
1315         return wholeBundle.localeID.isEmpty() || wholeBundle.localeID.equals("root");
1316     }
1317 
1318     @Override
1319     public ICUResourceBundle getParent() {
1320         return (ICUResourceBundle) parent;
1321     }
1322 
1323     @Override
1324     protected void setParent(ResourceBundle parent) {
1325         this.parent = parent;
1326     }
1327 
1328     @Override
1329     public String getKey() {
1330         return key;
1331     }
1332 
1333     /**
1334      * Get the noFallback flag specified in the loaded bundle.
1335      * @return The noFallback flag.
1336      */
1337     private boolean getNoFallback() {
1338         return wholeBundle.reader.getNoFallback();
1339     }
1340 
1341     private static ICUResourceBundle getBundle(ICUResourceBundleReader reader,
1342                                                String baseName, String localeID,
1343                                                ClassLoader loader) {
1344         ICUResourceBundleImpl.ResourceTable rootTable;
1345         int rootRes = reader.getRootResource();
1346         if(ICUResourceBundleReader.URES_IS_TABLE(ICUResourceBundleReader.RES_GET_TYPE(rootRes))) {
1347             WholeBundle wb = new WholeBundle(baseName, localeID, loader, reader);
1348             rootTable = new ICUResourceBundleImpl.ResourceTable(wb, rootRes);
1349         } else {
1350             throw new IllegalStateException("Invalid format error");
1351         }
1352         String aliasString = rootTable.findString("%%ALIAS");
1353         if(aliasString != null) {
1354             return (ICUResourceBundle)UResourceBundle.getBundleInstance(baseName, aliasString);
1355         } else {
1356             return rootTable;
1357         }
1358     }
1359     /**
1360      * Constructor for the root table of a bundle.
1361      */
1362     protected ICUResourceBundle(WholeBundle wholeBundle) {
1363         this.wholeBundle = wholeBundle;
1364     }
1365     // constructor for inner classes
1366     protected ICUResourceBundle(ICUResourceBundle container, String key) {
1367         this.key = key;
1368         wholeBundle = container.wholeBundle;
1369         this.container = container;
1370         parent = container.parent;
1371     }
1372 
1373     private static final char RES_PATH_SEP_CHAR = '/';
1374     private static final String RES_PATH_SEP_STR = "/";
1375     private static final String ICUDATA = "ICUDATA";
1376     private static final char HYPHEN = '-';
1377     private static final String LOCALE = "LOCALE";
1378 
1379     /**
1380      * Returns the resource object referred to from the alias _resource int's path string.
1381      * Throws MissingResourceException if not found.
1382      *
1383      * If the alias path does not contain a key path:
1384      * If keys != null then keys[:depth] is used.
1385      * Otherwise the base key path plus the key parameter is used.
1386      *
1387      * @param base A direct or indirect container of the alias.
1388      * @param keys The key path to the alias, or null. (const)
1389      * @param depth The length of the key path, if keys != null.
1390      * @param key The alias' own key within this current container, if keys == null.
1391      * @param _resource The alias resource int.
1392      * @param aliasesVisited Set of alias path strings already visited, for detecting loops.
1393      *        We cannot change the type (e.g., to Set<String>) because it is used
1394      *        in protected/@stable UResourceBundle methods.
1395      * @param requested The original resource object from which the lookup started,
1396      *        which is the starting point for "/LOCALE/..." aliases.
1397      * @return the aliased resource object
1398      */
1399     protected static ICUResourceBundle getAliasedResource(
1400             ICUResourceBundle base, String[] keys, int depth,
1401             String key, int _resource,
1402             HashMap<String, String> aliasesVisited,
1403             UResourceBundle requested) {
1404         WholeBundle wholeBundle = base.wholeBundle;
1405         ClassLoader loaderToUse = wholeBundle.loader;
1406         String locale;
1407         String keyPath = null;
1408         String bundleName;
1409         String rpath = wholeBundle.reader.getAlias(_resource);
1410         if (aliasesVisited == null) {
1411             aliasesVisited = new HashMap<String, String>();
1412         }
1413         if (aliasesVisited.get(rpath) != null) {
1414             throw new IllegalArgumentException(
1415                     "Circular references in the resource bundles");
1416         }
1417         aliasesVisited.put(rpath, "");
1418         if (rpath.indexOf(RES_PATH_SEP_CHAR) == 0) {
1419             int i = rpath.indexOf(RES_PATH_SEP_CHAR, 1);
1420             int j = rpath.indexOf(RES_PATH_SEP_CHAR, i + 1);
1421             bundleName = rpath.substring(1, i);
1422             if (j < 0) {
1423                 locale = rpath.substring(i + 1);
1424             } else {
1425                 locale = rpath.substring(i + 1, j);
1426                 keyPath = rpath.substring(j + 1, rpath.length());
1427             }
1428             //there is a path included
1429             if (bundleName.equals(ICUDATA)) {
1430                 bundleName = ICUData.ICU_BASE_NAME;
1431                 loaderToUse = ICU_DATA_CLASS_LOADER;
1432             }else if(bundleName.indexOf(ICUDATA)>-1){
1433                 int idx = bundleName.indexOf(HYPHEN);
1434                 if(idx>-1){
1435                     bundleName = ICUData.ICU_BASE_NAME+RES_PATH_SEP_STR+bundleName.substring(idx+1,bundleName.length());
1436                     loaderToUse = ICU_DATA_CLASS_LOADER;
1437                 }
1438             }
1439         } else {
1440             //no path start with locale
1441             int i = rpath.indexOf(RES_PATH_SEP_CHAR);
1442             if (i != -1) {
1443                 locale = rpath.substring(0, i);
1444                 keyPath = rpath.substring(i + 1);
1445             } else {
1446                 locale = rpath;
1447             }
1448             bundleName = wholeBundle.baseName;
1449         }
1450         ICUResourceBundle bundle = null;
1451         ICUResourceBundle sub = null;
1452         if(bundleName.equals(LOCALE)){
1453             bundleName = wholeBundle.baseName;
1454             keyPath = rpath.substring(LOCALE.length() + 2/* prepending and appending / */, rpath.length());
1455 
1456             // Get the top bundle of the requested bundle
1457             bundle = (ICUResourceBundle)requested;
1458             while (bundle.container != null) {
1459                 bundle = bundle.container;
1460             }
1461             sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null);
1462         }else{
1463             bundle = getBundleInstance(bundleName, locale, loaderToUse, false);
1464 
1465             int numKeys;
1466             if (keyPath != null) {
1467                 numKeys = countPathKeys(keyPath);
1468                 if (numKeys > 0) {
1469                     keys = new String[numKeys];
1470                     getResPathKeys(keyPath, numKeys, keys, 0);
1471                 }
1472             } else if (keys != null) {
1473                 numKeys = depth;
1474             } else {
1475                 depth = base.getResDepth();
1476                 numKeys = depth + 1;
1477                 keys = new String[numKeys];
1478                 base.getResPathKeys(keys, depth);
1479                 keys[depth] = key;
1480             }
1481             if (numKeys > 0) {
1482                 sub = bundle;
1483                 for (int i = 0; sub != null && i < numKeys; ++i) {
1484                     sub = sub.get(keys[i], aliasesVisited, requested);
1485                 }
1486             }
1487         }
1488         if (sub == null) {
1489             throw new MissingResourceException(wholeBundle.localeID, wholeBundle.baseName, key);
1490         }
1491         // TODO: If we know that sub is not cached,
1492         // then we should set its container and key to the alias' location,
1493         // so that it behaves as if its value had been copied into the alias location.
1494         // However, findResourceWithFallback() must reroute its bundle and key path
1495         // to where the alias data comes from.
1496         return sub;
1497     }
1498 
1499     /**
1500      * @internal
1501      * @deprecated This API is ICU internal only.
1502      */
1503     @Deprecated
getTopLevelKeySet()1504     public final Set<String> getTopLevelKeySet() {
1505         return wholeBundle.topLevelKeys;
1506     }
1507 
1508     /**
1509      * @internal
1510      * @deprecated This API is ICU internal only.
1511      */
1512     @Deprecated
setTopLevelKeySet(Set<String> keySet)1513     public final void setTopLevelKeySet(Set<String> keySet) {
1514         wholeBundle.topLevelKeys = keySet;
1515     }
1516 
1517     // This is the worker function for the public getKeys().
1518     // TODO: Now that UResourceBundle uses handleKeySet(), this function is obsolete.
1519     // It is also not inherited from ResourceBundle, and it is not implemented
1520     // by ResourceBundleWrapper despite its documentation requiring all subclasses to
1521     // implement it.
1522     // Consider deprecating UResourceBundle.handleGetKeys(), and consider making it always return null.
1523     @Override
handleGetKeys()1524     protected Enumeration<String> handleGetKeys() {
1525         return Collections.enumeration(handleKeySet());
1526     }
1527 
1528     @Override
isTopLevelResource()1529     protected boolean isTopLevelResource() {
1530         return container == null;
1531     }
1532 }
1533