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