Home | History | Annotate | Download | only in util
      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) 2004-2016, International Business Machines Corporation and
      6  * others. All Rights Reserved.
      7  *******************************************************************************
      8  */
      9 
     10 package com.ibm.icu.util;
     11 
     12 import java.nio.ByteBuffer;
     13 import java.util.Collections;
     14 import java.util.Enumeration;
     15 import java.util.HashMap;
     16 import java.util.Locale;
     17 import java.util.Map;
     18 import java.util.MissingResourceException;
     19 import java.util.ResourceBundle;
     20 import java.util.Set;
     21 import java.util.TreeSet;
     22 import java.util.concurrent.ConcurrentHashMap;
     23 
     24 import com.ibm.icu.impl.ICUData;
     25 import com.ibm.icu.impl.ICUResourceBundle;
     26 import com.ibm.icu.impl.ICUResourceBundleReader;
     27 import com.ibm.icu.impl.ResourceBundleWrapper;
     28 
     29 /**
     30  * {@icuenhanced java.util.ResourceBundle}.{@icu _usage_}
     31  *
     32  * <p>A class representing a collection of resource information pertaining to a given
     33  * locale. A resource bundle provides a way of accessing locale- specific information in a
     34  * data file. You create a resource bundle that manages the resources for a given locale
     35  * and then ask it for individual resources.
     36  *
     37  * <p>In ResourceBundle, an object is created and the sub-items are fetched using the
     38  * getString and getObject methods.  In UResourceBundle, each individual element of a
     39  * resource is a resource by itself.
     40  *
     41  * <p>Resource bundles in ICU are currently defined using text files that conform to the
     42  * following <a
     43  * href="http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt">BNF
     44  * definition</a>.  More on resource bundle concepts and syntax can be found in the <a
     45  * href="http://www.icu-project.org/userguide/ResourceManagement.html">Users Guide</a>.
     46  *
     47  * <p>The packaging of ICU *.res files can be of two types
     48  * ICU4C:
     49  * <pre>
     50  *       root.res
     51  *         |
     52  *      --------
     53  *     |        |
     54  *   fr.res  en.res
     55  *     |
     56  *   --------
     57  *  |        |
     58  * fr_CA.res fr_FR.res
     59  * </pre>
     60  * JAVA/JDK:
     61  * <pre>
     62  *    LocaleElements.res
     63  *         |
     64  *      -------------------
     65  *     |                   |
     66  * LocaleElements_fr.res  LocaleElements_en.res
     67  *     |
     68  *   ---------------------------
     69  *  |                            |
     70  * LocaleElements_fr_CA.res   LocaleElements_fr_FR.res
     71  * </pre>
     72  *
     73  * Depending on the organization of your resources, the syntax to getBundleInstance will
     74  * change.  To open ICU style organization use:
     75  *
     76  * <pre>
     77  *      UResourceBundle bundle =
     78  *          UResourceBundle.getBundleInstance("com/mycompany/resources",
     79  *                                            "en_US", myClassLoader);
     80  * </pre>
     81  * To open Java/JDK style organization use:
     82  * <pre>
     83  *      UResourceBundle bundle =
     84  *          UResourceBundle.getBundleInstance("com.mycompany.resources.LocaleElements",
     85  *                                            "en_US", myClassLoader);
     86  * </pre>
     87  *
     88  * <p>Note: Please use pass a class loader for loading non-ICU resources. Java security does not
     89  * allow loading of resources across jar files. You must provide your class loader
     90  * to load the resources
     91 
     92  * @stable ICU 3.0
     93  * @author ram
     94  */
     95 public abstract class UResourceBundle extends ResourceBundle {
     96 
     97 
     98     /**
     99      * {@icu} Creates a resource bundle using the specified base name and locale.
    100      * ICU_DATA_CLASS is used as the default root.
    101      * @param baseName string containing the name of the data package.
    102      *                    If null the default ICU package name is used.
    103      * @param localeName the locale for which a resource bundle is desired
    104      * @throws MissingResourceException If no resource bundle for the specified base name
    105      * can be found
    106      * @return a resource bundle for the given base name and locale
    107      * @stable ICU 3.0
    108      */
    109     public static UResourceBundle getBundleInstance(String baseName, String localeName){
    110         return getBundleInstance(baseName, localeName, ICUResourceBundle.ICU_DATA_CLASS_LOADER,
    111                                  false);
    112     }
    113 
    114     /**
    115      * {@icu} Creates a resource bundle using the specified base name, locale, and class root.
    116      *
    117      * @param baseName string containing the name of the data package.
    118      *                    If null the default ICU package name is used.
    119      * @param localeName the locale for which a resource bundle is desired
    120      * @param root the class object from which to load the resource bundle
    121      * @throws MissingResourceException If no resource bundle for the specified base name
    122      * can be found
    123      * @return a resource bundle for the given base name and locale
    124      * @stable ICU 3.0
    125      */
    126     public static UResourceBundle getBundleInstance(String baseName, String localeName,
    127                                                     ClassLoader root){
    128         return getBundleInstance(baseName, localeName, root, false);
    129     }
    130 
    131     /**
    132      * {@icu} Creates a resource bundle using the specified base name, locale, and class
    133      * root.
    134      *
    135      * @param baseName string containing the name of the data package.
    136      *                    If null the default ICU package name is used.
    137      * @param localeName the locale for which a resource bundle is desired
    138      * @param root the class object from which to load the resource bundle
    139      * @param disableFallback Option to disable locale inheritence.
    140      *                          If true the fallback chain will not be built.
    141      * @throws MissingResourceException
    142      *     if no resource bundle for the specified base name can be found
    143      * @return a resource bundle for the given base name and locale
    144      * @stable ICU 3.0
    145      *
    146      */
    147     protected static UResourceBundle getBundleInstance(String baseName, String localeName,
    148                                                        ClassLoader root, boolean disableFallback) {
    149         return instantiateBundle(baseName, localeName, root, disableFallback);
    150     }
    151 
    152     /**
    153      * {@icu} Sole constructor.  (For invocation by subclass constructors, typically
    154      * implicit.)  This is public for compatibility with Java, whose compiler
    155      * will generate public default constructors for an abstract class.
    156      * @stable ICU 3.0
    157      */
    158     public UResourceBundle() {
    159     }
    160 
    161     /**
    162      * {@icu} Creates a UResourceBundle for the locale specified, from which users can extract
    163      * resources by using their corresponding keys.
    164      * @param locale  specifies the locale for which we want to open the resource.
    165      *                If null the bundle for default locale is opened.
    166      * @return a resource bundle for the given locale
    167      * @stable ICU 3.0
    168      */
    169     public static UResourceBundle getBundleInstance(ULocale locale) {
    170         if (locale==null) {
    171             locale = ULocale.getDefault();
    172         }
    173         return getBundleInstance(ICUData.ICU_BASE_NAME, locale.getBaseName(),
    174                                  ICUResourceBundle.ICU_DATA_CLASS_LOADER, false);
    175     }
    176 
    177     /**
    178      * {@icu} Creates a UResourceBundle for the default locale and specified base name,
    179      * from which users can extract resources by using their corresponding keys.
    180      * @param baseName string containing the name of the data package.
    181      *                    If null the default ICU package name is used.
    182      * @return a resource bundle for the given base name and default locale
    183      * @stable ICU 3.0
    184      */
    185     public static UResourceBundle getBundleInstance(String baseName) {
    186         if (baseName == null) {
    187             baseName = ICUData.ICU_BASE_NAME;
    188         }
    189         ULocale uloc = ULocale.getDefault();
    190         return getBundleInstance(baseName, uloc.getBaseName(), ICUResourceBundle.ICU_DATA_CLASS_LOADER,
    191                                  false);
    192     }
    193 
    194     /**
    195      * {@icu} Creates a UResourceBundle for the specified locale and specified base name,
    196      * from which users can extract resources by using their corresponding keys.
    197      * @param baseName string containing the name of the data package.
    198      *                    If null the default ICU package name is used.
    199      * @param locale  specifies the locale for which we want to open the resource.
    200      *                If null the bundle for default locale is opened.
    201      * @return a resource bundle for the given base name and locale
    202      * @stable ICU 3.0
    203      */
    204 
    205     public static UResourceBundle getBundleInstance(String baseName, Locale locale) {
    206         if (baseName == null) {
    207             baseName = ICUData.ICU_BASE_NAME;
    208         }
    209         ULocale uloc = locale == null ? ULocale.getDefault() : ULocale.forLocale(locale);
    210 
    211         return getBundleInstance(baseName, uloc.getBaseName(),
    212                                  ICUResourceBundle.ICU_DATA_CLASS_LOADER, false);
    213     }
    214 
    215     /**
    216      * {@icu} Creates a UResourceBundle, from which users can extract resources by using
    217      * their corresponding keys.
    218      * @param baseName string containing the name of the data package.
    219      *                    If null the default ICU package name is used.
    220      * @param locale  specifies the locale for which we want to open the resource.
    221      *                If null the bundle for default locale is opened.
    222      * @return a resource bundle for the given base name and locale
    223      * @stable ICU 3.0
    224      */
    225     public static UResourceBundle getBundleInstance(String baseName, ULocale locale) {
    226         if (baseName == null) {
    227             baseName = ICUData.ICU_BASE_NAME;
    228         }
    229         if (locale == null) {
    230             locale = ULocale.getDefault();
    231         }
    232         return getBundleInstance(baseName, locale.getBaseName(),
    233                                  ICUResourceBundle.ICU_DATA_CLASS_LOADER, false);
    234     }
    235 
    236     /**
    237      * {@icu} Creates a UResourceBundle for the specified locale and specified base name,
    238      * from which users can extract resources by using their corresponding keys.
    239      * @param baseName string containing the name of the data package.
    240      *                    If null the default ICU package name is used.
    241      * @param locale  specifies the locale for which we want to open the resource.
    242      *                If null the bundle for default locale is opened.
    243      * @param loader  the loader to use
    244      * @return a resource bundle for the given base name and locale
    245      * @stable ICU 3.8
    246      */
    247     public static UResourceBundle getBundleInstance(String baseName, Locale locale,
    248                                                     ClassLoader loader) {
    249         if (baseName == null) {
    250             baseName = ICUData.ICU_BASE_NAME;
    251         }
    252         ULocale uloc = locale == null ? ULocale.getDefault() : ULocale.forLocale(locale);
    253         return getBundleInstance(baseName, uloc.getBaseName(), loader, false);
    254     }
    255 
    256     /**
    257      * {@icu} Creates a UResourceBundle, from which users can extract resources by using
    258      * their corresponding keys.<br><br>
    259      * Note: Please use this API for loading non-ICU resources. Java security does not
    260      * allow loading of resources across jar files. You must provide your class loader
    261      * to load the resources
    262      * @param baseName string containing the name of the data package.
    263      *                    If null the default ICU package name is used.
    264      * @param locale  specifies the locale for which we want to open the resource.
    265      *                If null the bundle for default locale is opened.
    266      * @param loader  the loader to use
    267      * @return a resource bundle for the given base name and locale
    268      * @stable ICU 3.8
    269      */
    270     public static UResourceBundle getBundleInstance(String baseName, ULocale locale,
    271                                                     ClassLoader loader) {
    272         if (baseName == null) {
    273             baseName = ICUData.ICU_BASE_NAME;
    274         }
    275         if (locale == null) {
    276             locale = ULocale.getDefault();
    277         }
    278         return getBundleInstance(baseName, locale.getBaseName(), loader, false);
    279     }
    280 
    281     /**
    282      * {@icu} Returns the RFC 3066 conformant locale id of this resource bundle.
    283      * This method can be used after a call to getBundleInstance() to
    284      * determine whether the resource bundle returned really
    285      * corresponds to the requested locale or is a fallback.
    286      *
    287      * @return the locale of this resource bundle
    288      * @stable ICU 3.0
    289      */
    290     public abstract ULocale getULocale();
    291 
    292     /**
    293      * {@icu} Returns the localeID
    294      * @return The string representation of the localeID
    295      * @stable ICU 3.0
    296      */
    297     protected abstract String getLocaleID();
    298 
    299     /**
    300      * {@icu} Returns the base name of the resource bundle
    301      * @return The string representation of the base name
    302      * @stable ICU 3.0
    303      */
    304     protected abstract String getBaseName();
    305 
    306     /**
    307      * {@icu} Returns the parent bundle
    308      * @return The parent bundle
    309      * @stable ICU 3.0
    310      */
    311     protected abstract UResourceBundle getParent();
    312 
    313 
    314     /**
    315      * Returns the locale of this bundle
    316      * @return the locale of this resource bundle
    317      * @stable ICU 3.0
    318      */
    319     @Override
    320     public Locale getLocale(){
    321         return getULocale().toLocale();
    322     }
    323 
    324     private enum RootType { MISSING, ICU, JAVA }
    325 
    326     private static Map<String, RootType> ROOT_CACHE = new ConcurrentHashMap<String, RootType>();
    327 
    328     private static RootType getRootType(String baseName, ClassLoader root) {
    329         RootType rootType = ROOT_CACHE.get(baseName);
    330 
    331         if (rootType == null) {
    332             String rootLocale = (baseName.indexOf('.')==-1) ? "root" : "";
    333             try{
    334                 ICUResourceBundle.getBundleInstance(baseName, rootLocale, root, true);
    335                 rootType = RootType.ICU;
    336             }catch(MissingResourceException ex){
    337                 try{
    338                     ResourceBundleWrapper.getBundleInstance(baseName, rootLocale, root, true);
    339                     rootType = RootType.JAVA;
    340                 }catch(MissingResourceException e){
    341                     //throw away the exception
    342                     rootType = RootType.MISSING;
    343                 }
    344             }
    345 
    346             ROOT_CACHE.put(baseName, rootType);
    347         }
    348 
    349         return rootType;
    350     }
    351 
    352     private static void setRootType(String baseName, RootType rootType) {
    353         ROOT_CACHE.put(baseName, rootType);
    354     }
    355 
    356     /**
    357      * {@icu} Loads a new resource bundle for the given base name, locale and class loader.
    358      * Optionally will disable loading of fallback bundles.
    359      * @param baseName string containing the name of the data package.
    360      *                    If null the default ICU package name is used.
    361      * @param localeName the locale for which a resource bundle is desired
    362      * @param root the class object from which to load the resource bundle
    363      * @param disableFallback disables loading of fallback lookup chain
    364      * @throws MissingResourceException If no resource bundle for the specified base name
    365      * can be found
    366      * @return a resource bundle for the given base name and locale
    367      * @stable ICU 3.0
    368      */
    369     protected static UResourceBundle instantiateBundle(String baseName, String localeName,
    370                                                        ClassLoader root, boolean disableFallback) {
    371         RootType rootType = getRootType(baseName, root);
    372 
    373         switch (rootType) {
    374         case ICU:
    375             return ICUResourceBundle.getBundleInstance(baseName, localeName, root, disableFallback);
    376 
    377         case JAVA:
    378             return ResourceBundleWrapper.getBundleInstance(baseName, localeName, root,
    379                                                            disableFallback);
    380 
    381         case MISSING:
    382         default:
    383             UResourceBundle b;
    384             try{
    385                 b = ICUResourceBundle.getBundleInstance(baseName, localeName, root,
    386                                                         disableFallback);
    387                 setRootType(baseName, RootType.ICU);
    388             }catch(MissingResourceException ex){
    389                 b = ResourceBundleWrapper.getBundleInstance(baseName, localeName, root,
    390                                                             disableFallback);
    391                 setRootType(baseName, RootType.JAVA);
    392             }
    393             return b;
    394         }
    395     }
    396 
    397     /**
    398      * {@icu} Returns a binary data item from a binary resource, as a read-only ByteBuffer.
    399      *
    400      * @return a pointer to a chunk of unsigned bytes which live in a memory mapped/DLL
    401      * file.
    402      * @see #getIntVector
    403      * @see #getInt
    404      * @throws MissingResourceException If no resource bundle can be found.
    405      * @throws UResourceTypeMismatchException If the resource has a type mismatch.
    406      * @stable ICU 3.8
    407      */
    408     public ByteBuffer getBinary() {
    409         throw new UResourceTypeMismatchException("");
    410     }
    411 
    412     /**
    413      * Returns a string from a string resource type
    414      *
    415      * @return a string
    416      * @see #getBinary()
    417      * @see #getIntVector
    418      * @see #getInt
    419      * @throws MissingResourceException If resource bundle is missing.
    420      * @throws UResourceTypeMismatchException If resource bundle has a type mismatch.
    421      * @stable ICU 3.8
    422      */
    423     public String getString() {
    424         throw new UResourceTypeMismatchException("");
    425     }
    426 
    427     /**
    428      * Returns a string array from a array resource type
    429      *
    430      * @return a string
    431      * @see #getString()
    432      * @see #getIntVector
    433      * @throws MissingResourceException If resource bundle is missing.
    434      * @throws UResourceTypeMismatchException If resource bundle has a type mismatch.
    435      * @stable ICU 3.8
    436      */
    437     public String[] getStringArray() {
    438         throw new UResourceTypeMismatchException("");
    439     }
    440 
    441     /**
    442      * {@icu} Returns a binary data from a binary resource, as a byte array with a copy
    443      * of the bytes from the resource bundle.
    444      *
    445      * @param ba  The byte array to write the bytes to. A null variable is OK.
    446      * @return an array of bytes containing the binary data from the resource.
    447      * @see #getIntVector
    448      * @see #getInt
    449      * @throws MissingResourceException If resource bundle is missing.
    450      * @throws UResourceTypeMismatchException If resource bundle has a type mismatch.
    451      * @stable ICU 3.8
    452      */
    453     public byte[] getBinary(byte[] ba) {
    454         throw new UResourceTypeMismatchException("");
    455     }
    456 
    457     /**
    458      * {@icu} Returns a 32 bit integer array from a resource.
    459      *
    460      * @return a pointer to a chunk of unsigned bytes which live in a memory mapped/DLL file.
    461      * @see #getBinary()
    462      * @see #getInt
    463      * @throws MissingResourceException If resource bundle is missing.
    464      * @throws UResourceTypeMismatchException If resource bundle has a type mismatch.
    465      * @stable ICU 3.8
    466      */
    467     public int[] getIntVector() {
    468         throw new UResourceTypeMismatchException("");
    469     }
    470 
    471     /**
    472      * {@icu} Returns a signed integer from a resource.
    473      *
    474      * @return an integer value
    475      * @see #getIntVector
    476      * @see #getBinary()
    477      * @throws MissingResourceException If resource bundle is missing.
    478      * @throws UResourceTypeMismatchException If resource bundle type mismatch.
    479      * @stable ICU 3.8
    480      */
    481     public int getInt() {
    482         throw new UResourceTypeMismatchException("");
    483     }
    484 
    485     /**
    486      * {@icu} Returns a unsigned integer from a resource.
    487      * This integer is originally 28 bit and the sign gets propagated.
    488      *
    489      * @return an integer value
    490      * @see #getIntVector
    491      * @see #getBinary()
    492      * @throws MissingResourceException If resource bundle is missing.
    493      * @throws UResourceTypeMismatchException If resource bundle type mismatch.
    494      * @stable ICU 3.8
    495      */
    496     public int getUInt() {
    497         throw new UResourceTypeMismatchException("");
    498     }
    499 
    500     /**
    501      * {@icu} Returns a resource in a given resource that has a given key.
    502      *
    503      * @param aKey               a key associated with the wanted resource
    504      * @return                  a resource bundle object representing the resource
    505      * @throws MissingResourceException If resource bundle is missing.
    506      * @stable ICU 3.8
    507      */
    508     public UResourceBundle get(String aKey) {
    509         UResourceBundle obj = findTopLevel(aKey);
    510         if (obj == null) {
    511             String fullName = ICUResourceBundleReader.getFullName(getBaseName(), getLocaleID());
    512             throw new MissingResourceException(
    513                     "Can't find resource for bundle " + fullName + ", key "
    514                     + aKey, this.getClass().getName(), aKey);
    515         }
    516         return obj;
    517     }
    518 
    519     /**
    520      * Returns a resource in a given resource that has a given key, or null if the
    521      * resource is not found.
    522      *
    523      * @param aKey the key associated with the wanted resource
    524      * @return the resource, or null
    525      * @see #get(String)
    526      * @internal
    527      * @deprecated This API is ICU internal only.
    528      */
    529     @Deprecated
    530     protected UResourceBundle findTopLevel(String aKey) {
    531         // NOTE: this only works for top-level resources.  For resources at lower
    532         // levels, it fails when you fall back to the parent, since you're now
    533         // looking at root resources, not at the corresponding nested resource.
    534         for (UResourceBundle res = this; res != null; res = res.getParent()) {
    535             UResourceBundle obj = res.handleGet(aKey, null, this);
    536             if (obj != null) {
    537                 return obj;
    538             }
    539         }
    540         return null;
    541     }
    542 
    543     /**
    544      * Returns the string in a given resource at the specified index.
    545      *
    546      * @param index an index to the wanted string.
    547      * @return a string which lives in the resource.
    548      * @throws IndexOutOfBoundsException If the index value is out of bounds of accepted values.
    549      * @throws UResourceTypeMismatchException If resource bundle type mismatch.
    550      * @stable ICU 3.8
    551      */
    552     public String getString(int index) {
    553         ICUResourceBundle temp = (ICUResourceBundle)get(index);
    554         if (temp.getType() == STRING) {
    555             return temp.getString();
    556         }
    557         throw new UResourceTypeMismatchException("");
    558     }
    559 
    560     /**
    561      * {@icu} Returns the resource in a given resource at the specified index.
    562      *
    563      * @param index an index to the wanted resource.
    564      * @return the sub resource UResourceBundle object
    565      * @throws IndexOutOfBoundsException If the index value is out of bounds of accepted values.
    566      * @throws MissingResourceException If the resource bundle is missing.
    567      * @stable ICU 3.8
    568      */
    569     public UResourceBundle get(int index) {
    570         UResourceBundle obj = handleGet(index, null, this);
    571         if (obj == null) {
    572             obj = getParent();
    573             if (obj != null) {
    574                 obj = obj.get(index);
    575             }
    576             if (obj == null)
    577                 throw new MissingResourceException(
    578                         "Can't find resource for bundle "
    579                                 + this.getClass().getName() + ", key "
    580                                 + getKey(), this.getClass().getName(), getKey());
    581         }
    582         return obj;
    583     }
    584 
    585     /**
    586      * Returns a resource in a given resource that has a given index, or null if the
    587      * resource is not found.
    588      *
    589      * @param index the index of the resource
    590      * @return the resource, or null
    591      * @see #get(int)
    592      * @internal
    593      * @deprecated This API is ICU internal only.
    594      */
    595     @Deprecated
    596     protected UResourceBundle findTopLevel(int index) {
    597         // NOTE: this _barely_ works for top-level resources.  For resources at lower
    598         // levels, it fails when you fall back to the parent, since you're now
    599         // looking at root resources, not at the corresponding nested resource.
    600         // Not only that, but unless the indices correspond 1-to-1, the index will
    601         // lose meaning.  Essentially this only works if the child resource arrays
    602         // are prefixes of their parent arrays.
    603         for (UResourceBundle res = this; res != null; res = res.getParent()) {
    604             UResourceBundle obj = res.handleGet(index, null, this);
    605             if (obj != null) {
    606                 return obj;
    607             }
    608         }
    609         return null;
    610     }
    611 
    612     /**
    613      * Returns the keys in this bundle as an enumeration
    614      * @return an enumeration containing key strings,
    615      *         which is empty if this is not a bundle or a table resource
    616      * @stable ICU 3.8
    617      */
    618     @Override
    619     public Enumeration<String> getKeys() {
    620         return Collections.enumeration(keySet());
    621     }
    622 
    623     /**
    624      * Returns a Set of all keys contained in this ResourceBundle and its parent bundles.
    625      * @return a Set of all keys contained in this ResourceBundle and its parent bundles,
    626      *         which is empty if this is not a bundle or a table resource
    627      * @internal
    628      * @deprecated This API is ICU internal only.
    629      */
    630     @Override
    631     @Deprecated
    632     public Set<String> keySet() {
    633         // TODO: Java 6 ResourceBundle has keySet() which calls handleKeySet()
    634         // and caches the results.
    635         // When we upgrade to Java 6, we still need to check for isTopLevelResource().
    636         // Keep the else branch as is. The if body should just return super.keySet().
    637         // Remove then-redundant caching of the keys.
    638         Set<String> keys = null;
    639         ICUResourceBundle icurb = null;
    640         if(isTopLevelResource() && this instanceof ICUResourceBundle) {
    641             // We do not cache the top-level keys in this base class so that
    642             // not every string/int/binary... resource has to have a keys cache field.
    643             icurb = (ICUResourceBundle)this;
    644             keys = icurb.getTopLevelKeySet();
    645         }
    646         if(keys == null) {
    647             if(isTopLevelResource()) {
    648                 TreeSet<String> newKeySet;
    649                 if(parent == null) {
    650                     newKeySet = new TreeSet<String>();
    651                 } else if(parent instanceof UResourceBundle) {
    652                     newKeySet = new TreeSet<String>(((UResourceBundle)parent).keySet());
    653                 } else {
    654                     // TODO: Java 6 ResourceBundle has keySet(); use it when we upgrade to Java 6
    655                     // and remove this else branch.
    656                     newKeySet = new TreeSet<String>();
    657                     Enumeration<String> parentKeys = parent.getKeys();
    658                     while(parentKeys.hasMoreElements()) {
    659                         newKeySet.add(parentKeys.nextElement());
    660                     }
    661                 }
    662                 newKeySet.addAll(handleKeySet());
    663                 keys = Collections.unmodifiableSet(newKeySet);
    664                 if(icurb != null) {
    665                     icurb.setTopLevelKeySet(keys);
    666                 }
    667             } else {
    668                 return handleKeySet();
    669             }
    670         }
    671         return keys;
    672     }
    673 
    674     /**
    675      * Returns a Set of the keys contained <i>only</i> in this ResourceBundle.
    676      * This does not include further keys from parent bundles.
    677      * @return a Set of the keys contained only in this ResourceBundle,
    678      *         which is empty if this is not a bundle or a table resource
    679      * @internal
    680      * @deprecated This API is ICU internal only.
    681      */
    682     @Override
    683     @Deprecated
    684     protected Set<String> handleKeySet() {
    685         return Collections.emptySet();
    686     }
    687 
    688     /**
    689      * {@icu} Returns the size of a resource. Size for scalar types is always 1, and for
    690      * vector/table types is the number of child resources.
    691      *
    692      * <br><b>Note:</b> Integer array is treated as a scalar type. There are no APIs to
    693      * access individual members of an integer array. It is always returned as a whole.
    694      * @return number of resources in a given resource.
    695      * @stable ICU 3.8
    696      */
    697     public int getSize() {
    698         return 1;
    699     }
    700 
    701     /**
    702      * {@icu} Returns the type of a resource.
    703      * Available types are {@link #INT INT}, {@link #ARRAY ARRAY},
    704      * {@link #BINARY BINARY}, {@link #INT_VECTOR INT_VECTOR},
    705      * {@link #STRING STRING}, {@link #TABLE TABLE}.
    706      *
    707      * @return type of the given resource.
    708      * @stable ICU 3.8
    709      */
    710     public int getType() {
    711         return NONE;
    712     }
    713 
    714     /**
    715      * {@icu} Return the version number associated with this UResourceBundle as an
    716      * VersionInfo object.
    717      * @return VersionInfo object containing the version of the bundle
    718      * @stable ICU 3.8
    719      */
    720     public VersionInfo getVersion() {
    721         return null;
    722     }
    723 
    724     /**
    725      * {@icu} Returns the iterator which iterates over this
    726      * resource bundle
    727      * @return UResourceBundleIterator that iterates over the resources in the bundle
    728      * @stable ICU 3.8
    729      */
    730     public UResourceBundleIterator getIterator() {
    731         return new UResourceBundleIterator(this);
    732     }
    733 
    734     /**
    735      * {@icu} Returns the key associated with a given resource. Not all the resources have
    736      * a key - only those that are members of a table.
    737      * @return a key associated to this resource, or null if it doesn't have a key
    738      * @stable ICU 3.8
    739      */
    740     public String getKey() {
    741         return null;
    742     }
    743 
    744     /**
    745      * {@icu} Resource type constant for "no resource".
    746      * @stable ICU 3.8
    747      */
    748     public static final int NONE = -1;
    749 
    750     /**
    751      * {@icu} Resource type constant for strings.
    752      * @stable ICU 3.8
    753      */
    754     public static final int STRING = 0;
    755 
    756     /**
    757      * {@icu} Resource type constant for binary data.
    758      * @stable ICU 3.8
    759      */
    760     public static final int BINARY = 1;
    761 
    762     /**
    763      * {@icu} Resource type constant for tables of key-value pairs.
    764      * @stable ICU 3.8
    765      */
    766     public static final int TABLE = 2;
    767 
    768     /**
    769      * {@icu} Resource type constant for a single 28-bit integer, interpreted as
    770      * signed or unsigned by the getInt() function.
    771      * @see #getInt
    772      * @stable ICU 3.8
    773      */
    774     public static final int INT = 7;
    775 
    776     /**
    777      * {@icu} Resource type constant for arrays of resources.
    778      * @stable ICU 3.8
    779      */
    780     public static final int ARRAY = 8;
    781 
    782     /**
    783      * Resource type constant for vectors of 32-bit integers.
    784      * @see #getIntVector
    785      * @stable ICU 3.8
    786      */
    787     public static final int INT_VECTOR = 14;
    788 
    789     //====== protected members ==============
    790 
    791     /**
    792      * {@icu} Actual worker method for fetching a resource based on the given key.
    793      * Sub classes must override this method if they support resources with keys.
    794      * @param aKey the key string of the resource to be fetched
    795      * @param aliasesVisited hashtable object to hold references of resources already seen
    796      * @param requested the original resource bundle object on which the get method was invoked.
    797      *                  The requested bundle and the bundle on which this method is invoked
    798      *                  are the same, except in the cases where aliases are involved.
    799      * @return UResourceBundle a resource associated with the key
    800      * @stable ICU 3.8
    801      */
    802     protected UResourceBundle handleGet(String aKey, HashMap<String, String> aliasesVisited,
    803                                         UResourceBundle requested) {
    804         return null;
    805     }
    806 
    807     /**
    808      * {@icu} Actual worker method for fetching a resource based on the given index.
    809      * Sub classes must override this method if they support arrays of resources.
    810      * @param index the index of the resource to be fetched
    811      * @param aliasesVisited hashtable object to hold references of resources already seen
    812      * @param requested the original resource bundle object on which the get method was invoked.
    813      *                  The requested bundle and the bundle on which this method is invoked
    814      *                  are the same, except in the cases where aliases are involved.
    815      * @return UResourceBundle a resource associated with the index
    816      * @stable ICU 3.8
    817      */
    818     protected UResourceBundle handleGet(int index, HashMap<String, String> aliasesVisited,
    819                                         UResourceBundle requested) {
    820         return null;
    821     }
    822 
    823     /**
    824      * {@icu} Actual worker method for fetching the array of strings in a resource.
    825      * Sub classes must override this method if they support arrays of strings.
    826      * @return String[] An array of strings containing strings
    827      * @stable ICU 3.8
    828      */
    829     protected String[] handleGetStringArray() {
    830         return null;
    831     }
    832 
    833     /**
    834      * {@icu} Actual worker method for fetching the keys of resources contained in the resource.
    835      * Sub classes must override this method if they support keys and associated resources.
    836      *
    837      * @return Enumeration An enumeration of all the keys in this resource.
    838      * @stable ICU 3.8
    839      */
    840     protected Enumeration<String> handleGetKeys(){
    841         return null;
    842     }
    843 
    844     /**
    845      * {@inheritDoc}
    846      * @stable ICU 3.8
    847      */
    848     // this method is declared in ResourceBundle class
    849     // so cannot change the signature
    850     // Override this method
    851     @Override
    852     protected Object handleGetObject(String aKey) {
    853         return handleGetObjectImpl(aKey, this);
    854     }
    855 
    856     /**
    857      * Override the superclass method
    858      */
    859     // To facilitate XPath style aliases we need a way to pass the reference
    860     // to requested locale. The only way I could figure out is to implement
    861     // the look up logic here. This has a disadvantage that if the client
    862     // loads an ICUResourceBundle, calls ResourceBundle.getObject method
    863     // with a key that does not exist in the bundle then the lookup is
    864     // done twice before throwing a MissingResourceExpection.
    865     private Object handleGetObjectImpl(String aKey, UResourceBundle requested) {
    866         Object obj = resolveObject(aKey, requested);
    867         if (obj == null) {
    868             UResourceBundle parentBundle = getParent();
    869             if (parentBundle != null) {
    870                 obj = parentBundle.handleGetObjectImpl(aKey, requested);
    871             }
    872             if (obj == null)
    873                 throw new MissingResourceException(
    874                     "Can't find resource for bundle "
    875                     + this.getClass().getName() + ", key " + aKey,
    876                     this.getClass().getName(), aKey);
    877         }
    878         return obj;
    879     }
    880 
    881     // Routine for figuring out the type of object to be returned
    882     // string or string array
    883     private Object resolveObject(String aKey, UResourceBundle requested) {
    884         if (getType() == STRING) {
    885             return getString();
    886         }
    887         UResourceBundle obj = handleGet(aKey, null, requested);
    888         if (obj != null) {
    889             if (obj.getType() == STRING) {
    890                 return obj.getString();
    891             }
    892             try {
    893                 if (obj.getType() == ARRAY) {
    894                     return obj.handleGetStringArray();
    895                 }
    896             } catch (UResourceTypeMismatchException ex) {
    897                 return obj;
    898             }
    899         }
    900         return obj;
    901     }
    902 
    903     /**
    904      * Is this a top-level resource, that is, a whole bundle?
    905      * @return true if this is a top-level resource
    906      * @internal
    907      * @deprecated This API is ICU internal only.
    908      */
    909     @Deprecated
    910     protected boolean isTopLevelResource() {
    911         return true;
    912     }
    913 }
    914