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