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