| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| * ***************************************************************************** |
| * Copyright (C) 2005-2016, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| * ***************************************************************************** |
| */ |
| |
| package com.ibm.icu.impl; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumMap; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Locale; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| import java.util.Set; |
| |
| import com.ibm.icu.impl.ICUResourceBundleReader.ReaderValue; |
| import com.ibm.icu.impl.URLHandler.URLVisitor; |
| import com.ibm.icu.util.ULocale; |
| import com.ibm.icu.util.UResourceBundle; |
| import com.ibm.icu.util.UResourceBundleIterator; |
| import com.ibm.icu.util.UResourceTypeMismatchException; |
| |
| public class ICUResourceBundle extends UResourceBundle { |
| /** |
| * CLDR string value "∅∅∅" prevents fallback to the parent bundle. |
| */ |
| public static final String NO_INHERITANCE_MARKER = "\u2205\u2205\u2205"; |
| |
| /** |
| * The class loader constant to be used with getBundleInstance API |
| */ |
| public static final ClassLoader ICU_DATA_CLASS_LOADER = ClassLoaderUtil.getClassLoader(ICUData.class); |
| |
| /** |
| * The name of the resource containing the installed locales |
| */ |
| protected static final String INSTALLED_LOCALES = "InstalledLocales"; |
| |
| /** |
| * Fields for a whole bundle, rather than any specific resource in the bundle. |
| * Corresponds roughly to ICU4C/source/common/uresimp.h struct UResourceDataEntry. |
| */ |
| protected static final class WholeBundle { |
| WholeBundle(String baseName, String localeID, ClassLoader loader, |
| ICUResourceBundleReader reader) { |
| this.baseName = baseName; |
| this.localeID = localeID; |
| this.ulocale = new ULocale(localeID); |
| this.loader = loader; |
| this.reader = reader; |
| } |
| |
| String baseName; |
| String localeID; |
| ULocale ulocale; |
| ClassLoader loader; |
| |
| /** |
| * Access to the bits and bytes of the resource bundle. |
| * Hides low-level details. |
| */ |
| ICUResourceBundleReader reader; |
| |
| // TODO: Remove topLevelKeys when we upgrade to Java 6 where ResourceBundle caches the keySet(). |
| Set<String> topLevelKeys; |
| } |
| |
| WholeBundle wholeBundle; |
| private ICUResourceBundle container; |
| |
| /** Loader for bundle instances, for caching. */ |
| private static abstract class Loader { |
| abstract ICUResourceBundle load(); |
| } |
| |
| private static CacheBase<String, ICUResourceBundle, Loader> BUNDLE_CACHE = |
| new SoftCache<String, ICUResourceBundle, Loader>() { |
| @Override |
| protected ICUResourceBundle createInstance(String unusedKey, Loader loader) { |
| return loader.load(); |
| } |
| }; |
| |
| /** |
| * Returns a functionally equivalent locale, considering keywords as well, for the specified keyword. |
| * @param baseName resource specifier |
| * @param resName top level resource to consider (such as "collations") |
| * @param keyword a particular keyword to consider (such as "collation" ) |
| * @param locID The requested locale |
| * @param isAvailable If non-null, 1-element array of fillin parameter that indicates whether the |
| * requested locale was available. The locale is defined as 'available' if it physically |
| * exists within the specified tree and included in 'InstalledLocales'. |
| * @param omitDefault if true, omit keyword and value if default. |
| * 'de_DE\@collation=standard' -> 'de_DE' |
| * @return the locale |
| * @internal ICU 3.0 |
| */ |
| public static final ULocale getFunctionalEquivalent(String baseName, ClassLoader loader, |
| String resName, String keyword, ULocale locID, |
| boolean isAvailable[], boolean omitDefault) { |
| String kwVal = locID.getKeywordValue(keyword); |
| String baseLoc = locID.getBaseName(); |
| String defStr = null; |
| ULocale parent = new ULocale(baseLoc); |
| ULocale defLoc = null; // locale where default (found) resource is |
| boolean lookForDefault = false; // true if kwVal needs to be set |
| ULocale fullBase = null; // base locale of found (target) resource |
| int defDepth = 0; // depth of 'default' marker |
| int resDepth = 0; // depth of found resource; |
| |
| if ((kwVal == null) || (kwVal.length() == 0) |
| || kwVal.equals(DEFAULT_TAG)) { |
| kwVal = ""; // default tag is treated as no keyword |
| lookForDefault = true; |
| } |
| |
| // Check top level locale first |
| ICUResourceBundle r = null; |
| |
| r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent); |
| if (isAvailable != null) { |
| isAvailable[0] = false; |
| ULocale[] availableULocales = getAvailEntry(baseName, loader) |
| .getULocaleList(ULocale.AvailableType.DEFAULT); |
| for (int i = 0; i < availableULocales.length; i++) { |
| if (parent.equals(availableULocales[i])) { |
| isAvailable[0] = true; |
| break; |
| } |
| } |
| } |
| // determine in which locale (if any) the currently relevant 'default' is |
| do { |
| try { |
| ICUResourceBundle irb = (ICUResourceBundle) r.get(resName); |
| defStr = irb.getString(DEFAULT_TAG); |
| if (lookForDefault == true) { |
| kwVal = defStr; |
| lookForDefault = false; |
| } |
| defLoc = r.getULocale(); |
| } catch (MissingResourceException t) { |
| // Ignore error and continue search. |
| } |
| if (defLoc == null) { |
| r = r.getParent(); |
| defDepth++; |
| } |
| } while ((r != null) && (defLoc == null)); |
| |
| // Now, search for the named resource |
| parent = new ULocale(baseLoc); |
| r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent); |
| // determine in which locale (if any) the named resource is located |
| do { |
| try { |
| ICUResourceBundle irb = (ICUResourceBundle)r.get(resName); |
| /* UResourceBundle urb = */irb.get(kwVal); |
| fullBase = irb.getULocale(); |
| // If the get() completed, we have the full base locale |
| // If we fell back to an ancestor of the old 'default', |
| // we need to re calculate the "default" keyword. |
| if ((fullBase != null) && ((resDepth) > defDepth)) { |
| defStr = irb.getString(DEFAULT_TAG); |
| defLoc = r.getULocale(); |
| defDepth = resDepth; |
| } |
| } catch (MissingResourceException t) { |
| // Ignore error, |
| } |
| if (fullBase == null) { |
| r = r.getParent(); |
| resDepth++; |
| } |
| } while ((r != null) && (fullBase == null)); |
| |
| if (fullBase == null && // Could not find resource 'kwVal' |
| (defStr != null) && // default was defined |
| !defStr.equals(kwVal)) { // kwVal is not default |
| // couldn't find requested resource. Fall back to default. |
| kwVal = defStr; // Fall back to default. |
| parent = new ULocale(baseLoc); |
| r = (ICUResourceBundle) UResourceBundle.getBundleInstance(baseName, parent); |
| resDepth = 0; |
| // determine in which locale (if any) the named resource is located |
| do { |
| try { |
| ICUResourceBundle irb = (ICUResourceBundle)r.get(resName); |
| ICUResourceBundle urb = (ICUResourceBundle)irb.get(kwVal); |
| |
| // if we didn't fail before this.. |
| fullBase = r.getULocale(); |
| |
| // If the fetched item (urb) is in a different locale than our outer locale (r/fullBase) |
| // then we are in a 'fallback' situation. treat as a missing resource situation. |
| if(!fullBase.getBaseName().equals(urb.getULocale().getBaseName())) { |
| fullBase = null; // fallback condition. Loop and try again. |
| } |
| |
| // If we fell back to an ancestor of the old 'default', |
| // we need to re calculate the "default" keyword. |
| if ((fullBase != null) && ((resDepth) > defDepth)) { |
| defStr = irb.getString(DEFAULT_TAG); |
| defLoc = r.getULocale(); |
| defDepth = resDepth; |
| } |
| } catch (MissingResourceException t) { |
| // Ignore error, continue search. |
| } |
| if (fullBase == null) { |
| r = r.getParent(); |
| resDepth++; |
| } |
| } while ((r != null) && (fullBase == null)); |
| } |
| |
| if (fullBase == null) { |
| throw new MissingResourceException( |
| "Could not find locale containing requested or default keyword.", |
| baseName, keyword + "=" + kwVal); |
| } |
| |
| if (omitDefault |
| && defStr.equals(kwVal) // if default was requested and |
| && resDepth <= defDepth) { // default was set in same locale or child |
| return fullBase; // Keyword value is default - no keyword needed in locale |
| } else { |
| return new ULocale(fullBase.getBaseName() + "@" + keyword + "=" + kwVal); |
| } |
| } |
| |
| /** |
| * Given a tree path and keyword, return a string enumeration of all possible values for that keyword. |
| * @param baseName resource specifier |
| * @param keyword a particular keyword to consider, must match a top level resource name |
| * within the tree. (i.e. "collations") |
| * @internal ICU 3.0 |
| */ |
| public static final String[] getKeywordValues(String baseName, String keyword) { |
| Set<String> keywords = new HashSet<>(); |
| ULocale locales[] = getAvailEntry(baseName, ICU_DATA_CLASS_LOADER) |
| .getULocaleList(ULocale.AvailableType.DEFAULT); |
| int i; |
| |
| for (i = 0; i < locales.length; i++) { |
| try { |
| UResourceBundle b = UResourceBundle.getBundleInstance(baseName, locales[i]); |
| // downcast to ICUResourceBundle? |
| ICUResourceBundle irb = (ICUResourceBundle) (b.getObject(keyword)); |
| Enumeration<String> e = irb.getKeys(); |
| while (e.hasMoreElements()) { |
| String s = e.nextElement(); |
| if (!DEFAULT_TAG.equals(s) && !s.startsWith("private-")) { |
| // don't add 'default' items, nor unlisted types |
| keywords.add(s); |
| } |
| } |
| } catch (Throwable t) { |
| //System.err.println("Error in - " + new Integer(i).toString() |
| // + " - " + t.toString()); |
| // ignore the err - just skip that resource |
| } |
| } |
| return keywords.toArray(new String[0]); |
| } |
| |
| /** |
| * This method performs multilevel fallback for fetching items from the |
| * bundle e.g: If resource is in the form de__PHONEBOOK{ collations{ |
| * default{ "phonebook"} } } If the value of "default" key needs to be |
| * accessed, then do: <code> |
| * UResourceBundle bundle = UResourceBundle.getBundleInstance("de__PHONEBOOK"); |
| * ICUResourceBundle result = null; |
| * if(bundle instanceof ICUResourceBundle){ |
| * result = ((ICUResourceBundle) bundle).getWithFallback("collations/default"); |
| * } |
| * </code> |
| * |
| * @param path The path to the required resource key |
| * @return resource represented by the key |
| * @exception MissingResourceException If a resource was not found. |
| */ |
| public ICUResourceBundle getWithFallback(String path) throws MissingResourceException { |
| ICUResourceBundle actualBundle = this; |
| |
| // now recurse to pick up sub levels of the items |
| ICUResourceBundle result = findResourceWithFallback(path, actualBundle, null); |
| |
| if (result == null) { |
| throw new MissingResourceException( |
| "Can't find resource for bundle " |
| + this.getClass().getName() + ", key " + getType(), |
| path, getKey()); |
| } |
| |
| if (result.getType() == STRING && result.getString().equals(NO_INHERITANCE_MARKER)) { |
| throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey()); |
| } |
| |
| return result; |
| } |
| |
| public ICUResourceBundle at(int index) { |
| return (ICUResourceBundle) handleGet(index, null, this); |
| } |
| |
| public ICUResourceBundle at(String key) { |
| // don't ever presume the key is an int in disguise, like ResourceArray does. |
| if (this instanceof ICUResourceBundleImpl.ResourceTable) { |
| return (ICUResourceBundle) handleGet(key, null, this); |
| } |
| return null; |
| } |
| |
| @Override |
| public ICUResourceBundle findTopLevel(int index) { |
| return (ICUResourceBundle) super.findTopLevel(index); |
| } |
| |
| @Override |
| public ICUResourceBundle findTopLevel(String aKey) { |
| return (ICUResourceBundle) super.findTopLevel(aKey); |
| } |
| |
| /** |
| * Like getWithFallback, but returns null if the resource is not found instead of |
| * throwing an exception. |
| * @param path the path to the resource |
| * @return the resource, or null |
| */ |
| public ICUResourceBundle findWithFallback(String path) { |
| return findResourceWithFallback(path, this, null); |
| } |
| public String findStringWithFallback(String path) { |
| return findStringWithFallback(path, this, null); |
| } |
| |
| // will throw type mismatch exception if the resource is not a string |
| public String getStringWithFallback(String path) throws MissingResourceException { |
| // Optimized form of getWithFallback(path).getString(); |
| ICUResourceBundle actualBundle = this; |
| String result = findStringWithFallback(path, actualBundle, null); |
| |
| if (result == null) { |
| throw new MissingResourceException( |
| "Can't find resource for bundle " |
| + this.getClass().getName() + ", key " + getType(), |
| path, getKey()); |
| } |
| |
| if (result.equals(NO_INHERITANCE_MARKER)) { |
| throw new MissingResourceException("Encountered NO_INHERITANCE_MARKER", path, getKey()); |
| } |
| return result; |
| } |
| |
| public UResource.Value getValueWithFallback(String path) throws MissingResourceException { |
| ICUResourceBundle rb; |
| if (path.isEmpty()) { |
| rb = this; |
| } else { |
| rb = findResourceWithFallback(path, this, null); |
| if (rb == null) { |
| throw new MissingResourceException( |
| "Can't find resource for bundle " |
| + this.getClass().getName() + ", key " + getType(), |
| path, getKey()); |
| } |
| } |
| ReaderValue readerValue = new ReaderValue(); |
| ICUResourceBundleImpl impl = (ICUResourceBundleImpl)rb; |
| readerValue.reader = impl.wholeBundle.reader; |
| readerValue.res = impl.getResource(); |
| return readerValue; |
| } |
| |
| public void getAllItemsWithFallbackNoFail(String path, UResource.Sink sink) { |
| try { |
| getAllItemsWithFallback(path, sink); |
| } catch (MissingResourceException e) { |
| // Quietly ignore the exception. |
| } |
| } |
| |
| public void getAllItemsWithFallback(String path, UResource.Sink sink) |
| throws MissingResourceException { |
| // Collect existing and parsed key objects into an array of keys, |
| // rather than assembling and parsing paths. |
| int numPathKeys = countPathKeys(path); // How much deeper does the path go? |
| ICUResourceBundle rb; |
| if (numPathKeys == 0) { |
| rb = this; |
| } else { |
| // Get the keys for finding the target. |
| int depth = getResDepth(); // How deep are we in this bundle? |
| String[] pathKeys = new String[depth + numPathKeys]; |
| getResPathKeys(path, numPathKeys, pathKeys, depth); |
| rb = findResourceWithFallback(pathKeys, depth, this, null); |
| if (rb == null) { |
| throw new MissingResourceException( |
| "Can't find resource for bundle " |
| + this.getClass().getName() + ", key " + getType(), |
| path, getKey()); |
| } |
| } |
| UResource.Key key = new UResource.Key(); |
| ReaderValue readerValue = new ReaderValue(); |
| rb.getAllItemsWithFallback(key, readerValue, sink); |
| } |
| |
| private void getAllItemsWithFallback( |
| UResource.Key key, ReaderValue readerValue, UResource.Sink sink) { |
| // We recursively enumerate child-first, |
| // only storing parent items in the absence of child items. |
| // The sink needs to store a placeholder value for the no-fallback/no-inheritance marker |
| // to prevent a parent item from being stored. |
| // |
| // It would be possible to recursively enumerate parent-first, |
| // overriding parent items with child items. |
| // When the sink sees the no-fallback/no-inheritance marker, |
| // then it would remove the parent's item. |
| // We would deserialize parent values even though they are overridden in a child bundle. |
| ICUResourceBundleImpl impl = (ICUResourceBundleImpl)this; |
| readerValue.reader = impl.wholeBundle.reader; |
| readerValue.res = impl.getResource(); |
| key.setString(this.key != null ? this.key : ""); |
| sink.put(key, readerValue, parent == null); |
| if (parent != null) { |
| // We might try to query the sink whether |
| // any fallback from the parent bundle is still possible. |
| ICUResourceBundle parentBundle = (ICUResourceBundle)parent; |
| ICUResourceBundle rb; |
| int depth = getResDepth(); |
| if (depth == 0) { |
| rb = parentBundle; |
| } else { |
| // Re-fetch the path keys: They may differ from the original ones |
| // if we had followed an alias. |
| String[] pathKeys = new String[depth]; |
| getResPathKeys(pathKeys, depth); |
| rb = findResourceWithFallback(pathKeys, 0, parentBundle, null); |
| } |
| if (rb != null) { |
| rb.getAllItemsWithFallback(key, readerValue, sink); |
| } |
| } |
| } |
| |
| /** |
| * Return a set of the locale names supported by a collection of resource |
| * bundles. |
| * |
| * @param bundlePrefix the prefix of the resource bundles to use. |
| */ |
| public static Set<String> getAvailableLocaleNameSet(String bundlePrefix, ClassLoader loader) { |
| return getAvailEntry(bundlePrefix, loader).getLocaleNameSet(); |
| } |
| |
| /** |
| * Return a set of all the locale names supported by a collection of |
| * resource bundles. |
| */ |
| public static Set<String> getFullLocaleNameSet() { |
| return getFullLocaleNameSet(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER); |
| } |
| |
| /** |
| * Return a set of all the locale names supported by a collection of |
| * resource bundles. |
| * |
| * @param bundlePrefix the prefix of the resource bundles to use. |
| */ |
| public static Set<String> getFullLocaleNameSet(String bundlePrefix, ClassLoader loader) { |
| return getAvailEntry(bundlePrefix, loader).getFullLocaleNameSet(); |
| } |
| |
| /** |
| * Return a set of the locale names supported by a collection of resource |
| * bundles. |
| */ |
| public static Set<String> getAvailableLocaleNameSet() { |
| return getAvailableLocaleNameSet(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER); |
| } |
| |
| /** |
| * Get the set of Locales installed in the specified bundles, for the specified type. |
| * @return the list of available locales |
| */ |
| public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader, |
| ULocale.AvailableType type) { |
| return getAvailEntry(baseName, loader).getULocaleList(type); |
| } |
| |
| /** |
| * Get the set of ULocales installed the base bundle. |
| * @return the list of available locales |
| */ |
| public static final ULocale[] getAvailableULocales() { |
| return getAvailableULocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, ULocale.AvailableType.DEFAULT); |
| } |
| |
| /** |
| * Get the set of ULocales installed the base bundle, for the specified type. |
| * @return the list of available locales for the specified type |
| */ |
| public static final ULocale[] getAvailableULocales(ULocale.AvailableType type) { |
| return getAvailableULocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, type); |
| } |
| |
| /** |
| * Get the set of Locales installed in the specified bundles. |
| * @return the list of available locales |
| */ |
| public static final ULocale[] getAvailableULocales(String baseName, ClassLoader loader) { |
| return getAvailableULocales(baseName, loader, ULocale.AvailableType.DEFAULT); |
| } |
| |
| /** |
| * Get the set of Locales installed in the specified bundles, for the specified type. |
| * @return the list of available locales |
| */ |
| public static final Locale[] getAvailableLocales(String baseName, ClassLoader loader, |
| ULocale.AvailableType type) { |
| return getAvailEntry(baseName, loader).getLocaleList(type); |
| } |
| |
| /** |
| * Get the set of ULocales installed the base bundle. |
| * @return the list of available locales |
| */ |
| public static final Locale[] getAvailableLocales() { |
| return getAvailableLocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, ULocale.AvailableType.DEFAULT); |
| } |
| |
| /** |
| * Get the set of Locales installed the base bundle, for the specified type. |
| * @return the list of available locales |
| */ |
| public static final Locale[] getAvailableLocales(ULocale.AvailableType type) { |
| return getAvailableLocales(ICUData.ICU_BASE_NAME, ICU_DATA_CLASS_LOADER, type); |
| } |
| |
| /** |
| * Get the set of Locales installed in the specified bundles. |
| * @return the list of available locales |
| */ |
| public static final Locale[] getAvailableLocales(String baseName, ClassLoader loader) { |
| return getAvailableLocales(baseName, loader, ULocale.AvailableType.DEFAULT); |
| } |
| |
| /** |
| * Convert a list of ULocales to a list of Locales. ULocales with a script code will not be converted |
| * since they cannot be represented as a Locale. This means that the two lists will <b>not</b> match |
| * one-to-one, and that the returned list might be shorter than the input list. |
| * @param ulocales a list of ULocales to convert to a list of Locales. |
| * @return the list of converted ULocales |
| */ |
| public static final Locale[] getLocaleList(ULocale[] ulocales) { |
| ArrayList<Locale> list = new ArrayList<>(ulocales.length); |
| HashSet<Locale> uniqueSet = new HashSet<>(); |
| for (int i = 0; i < ulocales.length; i++) { |
| Locale loc = ulocales[i].toLocale(); |
| if (!uniqueSet.contains(loc)) { |
| list.add(loc); |
| uniqueSet.add(loc); |
| } |
| } |
| return list.toArray(new Locale[list.size()]); |
| } |
| |
| /** |
| * Returns the locale of this resource bundle. This method can be used after |
| * a call to getBundle() to determine whether the resource bundle returned |
| * really corresponds to the requested locale or is a fallback. |
| * |
| * @return the locale of this resource bundle |
| */ |
| @Override |
| public Locale getLocale() { |
| return getULocale().toLocale(); |
| } |
| |
| |
| // ========== privates ========== |
| private static final String ICU_RESOURCE_INDEX = "res_index"; |
| |
| private static final String DEFAULT_TAG = "default"; |
| |
| // The name of text file generated by ICU4J build script including all locale names |
| // (canonical, alias and root) |
| private static final String FULL_LOCALE_NAMES_LIST = "fullLocaleNames.lst"; |
| |
| // Flag for enabling/disabling debugging code |
| private static final boolean DEBUG = ICUDebug.enabled("localedata"); |
| |
| private static final class AvailableLocalesSink extends UResource.Sink { |
| |
| EnumMap<ULocale.AvailableType, ULocale[]> output; |
| |
| public AvailableLocalesSink(EnumMap<ULocale.AvailableType, ULocale[]> output) { |
| this.output = output; |
| } |
| |
| @Override |
| public void put(UResource.Key key, UResource.Value value, boolean noFallback) { |
| UResource.Table resIndexTable = value.getTable(); |
| for (int i = 0; resIndexTable.getKeyAndValue(i, key, value); ++i) { |
| ULocale.AvailableType type; |
| if (key.contentEquals("InstalledLocales")) { |
| type = ULocale.AvailableType.DEFAULT; |
| } else if (key.contentEquals("AliasLocales")) { |
| type = ULocale.AvailableType.ONLY_LEGACY_ALIASES; |
| } else { |
| // CLDRVersion, etc. |
| continue; |
| } |
| UResource.Table availableLocalesTable = value.getTable(); |
| ULocale[] locales = new ULocale[availableLocalesTable.getSize()]; |
| for (int j = 0; availableLocalesTable.getKeyAndValue(j, key, value); ++j) { |
| locales[j] = new ULocale(key.toString()); |
| } |
| output.put(type, locales); |
| } |
| } |
| } |
| |
| private static final EnumMap<ULocale.AvailableType, ULocale[]> createULocaleList( |
| String baseName, ClassLoader root) { |
| // the canned list is a subset of all the available .res files, the idea |
| // is we don't export them |
| // all. gotta be a better way to do this, since to add a locale you have |
| // to update this list, |
| // and it's embedded in our binary resources. |
| ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true); |
| |
| EnumMap<ULocale.AvailableType, ULocale[]> result = new EnumMap<>(ULocale.AvailableType.class); |
| AvailableLocalesSink sink = new AvailableLocalesSink(result); |
| rb.getAllItemsWithFallback("", sink); |
| return result; |
| } |
| |
| // Same as createULocaleList() but catches the MissingResourceException |
| // and returns the data in a different form. |
| private static final void addLocaleIDsFromIndexBundle(String baseName, |
| ClassLoader root, Set<String> locales) { |
| ICUResourceBundle bundle; |
| try { |
| bundle = (ICUResourceBundle) UResourceBundle.instantiateBundle(baseName, ICU_RESOURCE_INDEX, root, true); |
| bundle = (ICUResourceBundle) bundle.get(INSTALLED_LOCALES); |
| } catch (MissingResourceException e) { |
| if (DEBUG) { |
| System.out.println("couldn't find " + baseName + '/' + ICU_RESOURCE_INDEX + ".res"); |
| Thread.dumpStack(); |
| } |
| return; |
| } |
| UResourceBundleIterator iter = bundle.getIterator(); |
| iter.reset(); |
| while (iter.hasNext()) { |
| String locstr = iter.next(). getKey(); |
| locales.add(locstr); |
| } |
| } |
| |
| private static final void addBundleBaseNamesFromClassLoader( |
| final String bn, final ClassLoader root, final Set<String> names) { |
| java.security.AccessController |
| .doPrivileged(new java.security.PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| try { |
| // bn has a trailing slash: The WebSphere class loader would return null |
| // for a raw directory name without it. |
| Enumeration<URL> urls = root.getResources(bn); |
| if (urls == null) { |
| return null; |
| } |
| URLVisitor v = new URLVisitor() { |
| @Override |
| public void visit(String s) { |
| if (s.endsWith(".res")) { |
| String locstr = s.substring(0, s.length() - 4); |
| names.add(locstr); |
| } |
| } |
| }; |
| while (urls.hasMoreElements()) { |
| URL url = urls.nextElement(); |
| URLHandler handler = URLHandler.get(url); |
| if (handler != null) { |
| handler.guide(v, false); |
| } else { |
| if (DEBUG) System.out.println("handler for " + url + " is null"); |
| } |
| } |
| } catch (IOException e) { |
| if (DEBUG) System.out.println("ouch: " + e.getMessage()); |
| } |
| return null; |
| } |
| }); |
| } |
| |
| private static void addLocaleIDsFromListFile(String bn, ClassLoader root, Set<String> locales) { |
| try { |
| InputStream s = root.getResourceAsStream(bn + FULL_LOCALE_NAMES_LIST); |
| if (s != null) { |
| BufferedReader br = new BufferedReader(new InputStreamReader(s, "ASCII")); |
| try { |
| String line; |
| while ((line = br.readLine()) != null) { |
| if (line.length() != 0 && !line.startsWith("#")) { |
| locales.add(line); |
| } |
| } |
| } |
| finally { |
| br.close(); |
| } |
| } |
| } catch (IOException ignored) { |
| // swallow it |
| } |
| } |
| |
| private static Set<String> createFullLocaleNameSet(String baseName, ClassLoader loader) { |
| String bn = baseName.endsWith("/") ? baseName : baseName + "/"; |
| Set<String> set = new HashSet<>(); |
| String skipScan = ICUConfig.get("com.ibm.icu.impl.ICUResourceBundle.skipRuntimeLocaleResourceScan", "false"); |
| if (!skipScan.equalsIgnoreCase("true")) { |
| // scan available locale resources under the base url first |
| addBundleBaseNamesFromClassLoader(bn, loader, set); |
| if (baseName.startsWith(ICUData.ICU_BASE_NAME)) { |
| String folder; |
| if (baseName.length() == ICUData.ICU_BASE_NAME.length()) { |
| folder = ""; |
| } else if (baseName.charAt(ICUData.ICU_BASE_NAME.length()) == '/') { |
| folder = baseName.substring(ICUData.ICU_BASE_NAME.length() + 1); |
| } else { |
| folder = null; |
| } |
| if (folder != null) { |
| ICUBinary.addBaseNamesInFileFolder(folder, ".res", set); |
| } |
| } |
| set.remove(ICU_RESOURCE_INDEX); // "res_index" |
| // HACK: TODO: Figure out how we can distinguish locale data from other data items. |
| Iterator<String> iter = set.iterator(); |
| while (iter.hasNext()) { |
| String name = iter.next(); |
| if ((name.length() == 1 || name.length() > 3) && name.indexOf('_') < 0) { |
| // Does not look like a locale ID. |
| iter.remove(); |
| } |
| } |
| } |
| // look for prebuilt full locale names list next |
| if (set.isEmpty()) { |
| if (DEBUG) System.out.println("unable to enumerate data files in " + baseName); |
| addLocaleIDsFromListFile(bn, loader, set); |
| } |
| if (set.isEmpty()) { |
| // Use locale name set as the last resort fallback |
| addLocaleIDsFromIndexBundle(baseName, loader, set); |
| } |
| // We need to have the root locale in the set, but not as "root". |
| set.remove("root"); |
| set.add(ULocale.ROOT.toString()); // "" |
| return Collections.unmodifiableSet(set); |
| } |
| |
| private static Set<String> createLocaleNameSet(String baseName, ClassLoader loader) { |
| HashSet<String> set = new HashSet<>(); |
| addLocaleIDsFromIndexBundle(baseName, loader, set); |
| return Collections.unmodifiableSet(set); |
| } |
| |
| /** |
| * Holds the prefix, and lazily creates the Locale[] list or the locale name |
| * Set as needed. |
| */ |
| private static final class AvailEntry { |
| private String prefix; |
| private ClassLoader loader; |
| private volatile EnumMap<ULocale.AvailableType, ULocale[]> ulocales; |
| private volatile Locale[] locales; |
| private volatile Set<String> nameSet; |
| private volatile Set<String> fullNameSet; |
| |
| AvailEntry(String prefix, ClassLoader loader) { |
| this.prefix = prefix; |
| this.loader = loader; |
| } |
| |
| ULocale[] getULocaleList(ULocale.AvailableType type) { |
| // Direct data is available for DEFAULT and ONLY_LEGACY_ALIASES |
| assert type != ULocale.AvailableType.WITH_LEGACY_ALIASES; |
| if (ulocales == null) { |
| synchronized(this) { |
| if (ulocales == null) { |
| ulocales = createULocaleList(prefix, loader); |
| } |
| } |
| } |
| return ulocales.get(type); |
| } |
| Locale[] getLocaleList(ULocale.AvailableType type) { |
| if (locales == null) { |
| getULocaleList(type); |
| synchronized(this) { |
| if (locales == null) { |
| locales = ICUResourceBundle.getLocaleList(ulocales.get(type)); |
| } |
| } |
| } |
| return locales; |
| } |
| Set<String> getLocaleNameSet() { |
| if (nameSet == null) { |
| synchronized(this) { |
| if (nameSet == null) { |
| nameSet = createLocaleNameSet(prefix, loader); |
| } |
| } |
| } |
| return nameSet; |
| } |
| Set<String> getFullLocaleNameSet() { |
| // When there's no prebuilt index, we iterate through the jar files |
| // and read the contents to build it. If many threads try to read the |
| // same jar at the same time, java thrashes. Synchronize here |
| // so that we can avoid this problem. We don't synchronize on the |
| // other methods since they don't do this. |
| // |
| // This is the common entry point for access into the code that walks |
| // through the resources, and is cached. So it's a good place to lock |
| // access. Locking in the URLHandler doesn't give us a common object |
| // to lock. |
| if (fullNameSet == null) { |
| synchronized(this) { |
| if (fullNameSet == null) { |
| fullNameSet = createFullLocaleNameSet(prefix, loader); |
| } |
| } |
| } |
| return fullNameSet; |
| } |
| } |
| |
| |
| /* |
| * Cache used for AvailableEntry |
| */ |
| private static CacheBase<String, AvailEntry, ClassLoader> GET_AVAILABLE_CACHE = |
| new SoftCache<String, AvailEntry, ClassLoader>() { |
| @Override |
| protected AvailEntry createInstance(String key, ClassLoader loader) { |
| return new AvailEntry(key, loader); |
| } |
| }; |
| |
| /** |
| * Stores the locale information in a cache accessed by key (bundle prefix). |
| * The cached objects are AvailEntries. The cache is implemented by SoftCache |
| * so it can be GC'd. |
| */ |
| private static AvailEntry getAvailEntry(String key, ClassLoader loader) { |
| return GET_AVAILABLE_CACHE.getInstance(key, loader); |
| } |
| |
| private static final ICUResourceBundle findResourceWithFallback(String path, |
| UResourceBundle actualBundle, UResourceBundle requested) { |
| if (path.length() == 0) { |
| return null; |
| } |
| ICUResourceBundle base = (ICUResourceBundle) actualBundle; |
| // Collect existing and parsed key objects into an array of keys, |
| // rather than assembling and parsing paths. |
| int depth = base.getResDepth(); |
| int numPathKeys = countPathKeys(path); |
| assert numPathKeys > 0; |
| String[] keys = new String[depth + numPathKeys]; |
| getResPathKeys(path, numPathKeys, keys, depth); |
| return findResourceWithFallback(keys, depth, base, requested); |
| } |
| |
| private static final ICUResourceBundle findResourceWithFallback( |
| String[] keys, int depth, |
| ICUResourceBundle base, UResourceBundle requested) { |
| if (requested == null) { |
| requested = base; |
| } |
| |
| for (;;) { // Iterate over the parent bundles. |
| for (;;) { // Iterate over the keys on the requested path, within a bundle. |
| String subKey = keys[depth++]; |
| ICUResourceBundle sub = (ICUResourceBundle) base.handleGet(subKey, null, requested); |
| if (sub == null) { |
| --depth; |
| break; |
| } |
| if (depth == keys.length) { |
| // We found it. |
| return sub; |
| } |
| base = sub; |
| } |
| // Try the parent bundle of the last-found resource. |
| ICUResourceBundle nextBase = base.getParent(); |
| if (nextBase == null) { |
| return null; |
| } |
| // If we followed an alias, then we may have switched bundle (locale) and key path. |
| // Set the lower parts of the path according to the last-found resource. |
| // This relies on a resource found via alias to have its original location information, |
| // rather than the location of the alias. |
| int baseDepth = base.getResDepth(); |
| if (depth != baseDepth) { |
| String[] newKeys = new String[baseDepth + (keys.length - depth)]; |
| System.arraycopy(keys, depth, newKeys, baseDepth, keys.length - depth); |
| keys = newKeys; |
| } |
| base.getResPathKeys(keys, baseDepth); |
| base = nextBase; |
| depth = 0; // getParent() returned a top level table resource. |
| } |
| } |
| |
| /** |
| * Like findResourceWithFallback(...).getString() but with minimal creation of intermediate |
| * ICUResourceBundle objects. |
| */ |
| private static final String findStringWithFallback(String path, |
| UResourceBundle actualBundle, UResourceBundle requested) { |
| if (path.length() == 0) { |
| return null; |
| } |
| if (!(actualBundle instanceof ICUResourceBundleImpl.ResourceContainer)) { |
| return null; |
| } |
| if (requested == null) { |
| requested = actualBundle; |
| } |
| |
| ICUResourceBundle base = (ICUResourceBundle) actualBundle; |
| ICUResourceBundleReader reader = base.wholeBundle.reader; |
| int res = RES_BOGUS; |
| |
| // Collect existing and parsed key objects into an array of keys, |
| // rather than assembling and parsing paths. |
| int baseDepth = base.getResDepth(); |
| int depth = baseDepth; |
| int numPathKeys = countPathKeys(path); |
| assert numPathKeys > 0; |
| String[] keys = new String[depth + numPathKeys]; |
| getResPathKeys(path, numPathKeys, keys, depth); |
| |
| for (;;) { // Iterate over the parent bundles. |
| for (;;) { // Iterate over the keys on the requested path, within a bundle. |
| ICUResourceBundleReader.Container readerContainer; |
| if (res == RES_BOGUS) { |
| int type = base.getType(); |
| if (type == TABLE || type == ARRAY) { |
| readerContainer = ((ICUResourceBundleImpl.ResourceContainer)base).value; |
| } else { |
| break; |
| } |
| } else { |
| int type = ICUResourceBundleReader.RES_GET_TYPE(res); |
| if (ICUResourceBundleReader.URES_IS_TABLE(type)) { |
| readerContainer = reader.getTable(res); |
| } else if (ICUResourceBundleReader.URES_IS_ARRAY(type)) { |
| readerContainer = reader.getArray(res); |
| } else { |
| res = RES_BOGUS; |
| break; |
| } |
| } |
| String subKey = keys[depth++]; |
| res = readerContainer.getResource(reader, subKey); |
| if (res == RES_BOGUS) { |
| --depth; |
| break; |
| } |
| ICUResourceBundle sub; |
| if (ICUResourceBundleReader.RES_GET_TYPE(res) == ALIAS) { |
| base.getResPathKeys(keys, baseDepth); |
| sub = getAliasedResource(base, keys, depth, subKey, res, null, requested); |
| } else { |
| sub = null; |
| } |
| if (depth == keys.length) { |
| // We found it. |
| if (sub != null) { |
| return sub.getString(); // string from alias handling |
| } else { |
| String s = reader.getString(res); |
| if (s == null) { |
| throw new UResourceTypeMismatchException(""); |
| } |
| return s; |
| } |
| } |
| if (sub != null) { |
| base = sub; |
| reader = base.wholeBundle.reader; |
| res = RES_BOGUS; |
| // If we followed an alias, then we may have switched bundle (locale) and key path. |
| // Reserve space for the lower parts of the path according to the last-found resource. |
| // This relies on a resource found via alias to have its original location information, |
| // rather than the location of the alias. |
| baseDepth = base.getResDepth(); |
| if (depth != baseDepth) { |
| String[] newKeys = new String[baseDepth + (keys.length - depth)]; |
| System.arraycopy(keys, depth, newKeys, baseDepth, keys.length - depth); |
| keys = newKeys; |
| depth = baseDepth; |
| } |
| } |
| } |
| // Try the parent bundle of the last-found resource. |
| ICUResourceBundle nextBase = base.getParent(); |
| if (nextBase == null) { |
| return null; |
| } |
| // We probably have not yet set the lower parts of the key path. |
| base.getResPathKeys(keys, baseDepth); |
| base = nextBase; |
| reader = base.wholeBundle.reader; |
| depth = baseDepth = 0; // getParent() returned a top level table resource. |
| } |
| } |
| |
| private int getResDepth() { |
| return (container == null) ? 0 : container.getResDepth() + 1; |
| } |
| |
| /** |
| * Fills some of the keys array with the keys on the path to this resource object. |
| * Writes the top-level key into index 0 and increments from there. |
| * |
| * @param keys |
| * @param depth must be {@link #getResDepth()} |
| */ |
| private void getResPathKeys(String[] keys, int depth) { |
| ICUResourceBundle b = this; |
| while (depth > 0) { |
| keys[--depth] = b.key; |
| b = b.container; |
| assert (depth == 0) == (b.container == null); |
| } |
| } |
| |
| private static int countPathKeys(String path) { |
| if (path.isEmpty()) { |
| return 0; |
| } |
| int num = 1; |
| for (int i = 0; i < path.length(); ++i) { |
| if (path.charAt(i) == RES_PATH_SEP_CHAR) { |
| ++num; |
| } |
| } |
| return num; |
| } |
| |
| /** |
| * Fills some of the keys array (from start) with the num keys from the path string. |
| * |
| * @param path path string |
| * @param num must be {@link #countPathKeys(String)} |
| * @param keys |
| * @param start index where the first path key is stored |
| */ |
| private static void getResPathKeys(String path, int num, String[] keys, int start) { |
| if (num == 0) { |
| return; |
| } |
| if (num == 1) { |
| keys[start] = path; |
| return; |
| } |
| int i = 0; |
| for (;;) { |
| int j = path.indexOf(RES_PATH_SEP_CHAR, i); |
| assert j >= i; |
| keys[start++] = path.substring(i, j); |
| if (num == 2) { |
| assert path.indexOf(RES_PATH_SEP_CHAR, j + 1) < 0; |
| keys[start] = path.substring(j + 1); |
| break; |
| } else { |
| i = j + 1; |
| --num; |
| } |
| } |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| if (other instanceof ICUResourceBundle) { |
| ICUResourceBundle o = (ICUResourceBundle) other; |
| if (getBaseName().equals(o.getBaseName()) |
| && getLocaleID().equals(o.getLocaleID())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| assert false : "hashCode not designed"; |
| return 42; |
| } |
| |
| public enum OpenType { // C++ uresbund.cpp: enum UResOpenType |
| /** |
| * Open a resource bundle for the locale; |
| * if there is not even a base language bundle, then fall back to the default locale; |
| * if there is no bundle for that either, then load the root bundle. |
| * |
| * <p>This is the default bundle loading behavior. |
| */ |
| LOCALE_DEFAULT_ROOT, |
| // TODO: ICU ticket #11271 "consistent default locale across locale trees" |
| // Add an option to look at the main locale tree for whether to |
| // fall back to root directly (if the locale has main data) or |
| // fall back to the default locale first (if the locale does not even have main data). |
| /** |
| * Open a resource bundle for the locale; |
| * if there is not even a base language bundle, then load the root bundle; |
| * never fall back to the default locale. |
| * |
| * <p>This is used for algorithms that have good pan-Unicode default behavior, |
| * such as case mappings, collation, and segmentation (BreakIterator). |
| */ |
| LOCALE_ROOT, |
| /** |
| * Open a resource bundle for the locale; |
| * if there is not even a base language bundle, then fail; |
| * never fall back to the default locale nor to the root locale. |
| * |
| * <p>This is used when fallback to another language is not desired |
| * and the root locale is not generally useful. |
| * For example, {@link com.ibm.icu.util.LocaleData#setNoSubstitute(boolean)} |
| * or currency display names for {@link com.ibm.icu.text.LocaleDisplayNames}. |
| */ |
| LOCALE_ONLY, |
| /** |
| * Open a resource bundle for the exact bundle name as requested; |
| * no fallbacks, do not load parent bundles. |
| * |
| * <p>This is used for supplemental (non-locale) data. |
| */ |
| DIRECT |
| }; |
| |
| // This method is for super class's instantiateBundle method |
| public static ICUResourceBundle getBundleInstance(String baseName, String localeID, |
| ClassLoader root, boolean disableFallback) { |
| return getBundleInstance(baseName, localeID, root, |
| disableFallback ? OpenType.DIRECT : OpenType.LOCALE_DEFAULT_ROOT); |
| } |
| |
| public static ICUResourceBundle getBundleInstance( |
| String baseName, ULocale locale, OpenType openType) { |
| if (locale == null) { |
| locale = ULocale.getDefault(); |
| } |
| return getBundleInstance(baseName, locale.getBaseName(), |
| ICUResourceBundle.ICU_DATA_CLASS_LOADER, openType); |
| } |
| |
| public static ICUResourceBundle getBundleInstance(String baseName, String localeID, |
| ClassLoader root, OpenType openType) { |
| if (baseName == null) { |
| baseName = ICUData.ICU_BASE_NAME; |
| } |
| localeID = ULocale.getBaseName(localeID); |
| ICUResourceBundle b; |
| if (openType == OpenType.LOCALE_DEFAULT_ROOT) { |
| b = instantiateBundle(baseName, localeID, ULocale.getDefault().getBaseName(), |
| root, openType); |
| } else { |
| b = instantiateBundle(baseName, localeID, null, root, openType); |
| } |
| if(b==null){ |
| throw new MissingResourceException( |
| "Could not find the bundle "+ baseName+"/"+ localeID+".res","",""); |
| } |
| return b; |
| } |
| |
| private static boolean localeIDStartsWithLangSubtag(String localeID, String lang) { |
| return localeID.startsWith(lang) && |
| (localeID.length() == lang.length() || localeID.charAt(lang.length()) == '_'); |
| } |
| |
| private static ICUResourceBundle instantiateBundle( |
| final String baseName, final String localeID, final String defaultID, |
| final ClassLoader root, final OpenType openType) { |
| assert localeID.indexOf('@') < 0; |
| assert defaultID == null || defaultID.indexOf('@') < 0; |
| final String fullName = ICUResourceBundleReader.getFullName(baseName, localeID); |
| char openTypeChar = (char)('0' + openType.ordinal()); |
| String cacheKey = openType != OpenType.LOCALE_DEFAULT_ROOT ? |
| fullName + '#' + openTypeChar : |
| fullName + '#' + openTypeChar + '#' + defaultID; |
| return BUNDLE_CACHE.getInstance(cacheKey, new Loader() { |
| @Override |
| public ICUResourceBundle load() { |
| if(DEBUG) System.out.println("Creating "+fullName); |
| // here we assume that java type resource bundle organization |
| // is required then the base name contains '.' else |
| // the resource organization is of ICU type |
| // so clients can instantiate resources of the type |
| // com.mycompany.data.MyLocaleElements_en.res and |
| // com.mycompany.data.MyLocaleElements.res |
| // |
| final String rootLocale = (baseName.indexOf('.')==-1) ? "root" : ""; |
| String localeName = localeID.isEmpty() ? rootLocale : localeID; |
| ICUResourceBundle b = ICUResourceBundle.createBundle(baseName, localeName, root); |
| |
| if(DEBUG)System.out.println("The bundle created is: "+b+" and openType="+openType+" and bundle.getNoFallback="+(b!=null && b.getNoFallback())); |
| if (openType == OpenType.DIRECT || (b != null && b.getNoFallback())) { |
| // no fallback because the caller said so or because the bundle says so |
| // |
| // TODO for b!=null: In C++, ures_openDirect() builds the parent chain |
| // for its bundle unless its nofallback flag is set. |
| // Otherwise we get test failures. |
| // For example, item aliases are followed via ures_openDirect(), |
| // and fail if the target bundle needs fallbacks but the chain is not set. |
| // Figure out why Java does not build the parent chain |
| // for a bundle that does not have nofallback. |
| // Are the relevant test cases just disabled? |
| // Do item aliases not get followed via "direct" loading? |
| return b; |
| } |
| |
| // fallback to locale ID parent |
| if(b == null){ |
| int i = localeName.lastIndexOf('_'); |
| if (i != -1) { |
| // Chop off the last underscore and the subtag after that. |
| String temp = localeName.substring(0, i); |
| b = instantiateBundle(baseName, temp, defaultID, root, openType); |
| }else{ |
| // No underscore, only a base language subtag. |
| if(openType == OpenType.LOCALE_DEFAULT_ROOT && |
| !localeIDStartsWithLangSubtag(defaultID, localeName)) { |
| // Go to the default locale before root. |
| b = instantiateBundle(baseName, defaultID, defaultID, root, openType); |
| } else if(openType != OpenType.LOCALE_ONLY && !rootLocale.isEmpty()) { |
| // Ultimately go to root. |
| b = ICUResourceBundle.createBundle(baseName, rootLocale, root); |
| } |
| } |
| }else{ |
| UResourceBundle parent = null; |
| localeName = b.getLocaleID(); |
| int i = localeName.lastIndexOf('_'); |
| |
| // TODO: C++ uresbund.cpp also checks for %%ParentIsRoot. Why not Java? |
| String parentLocaleName = ((ICUResourceBundleImpl.ResourceTable)b).findString("%%Parent"); |
| if (parentLocaleName != null) { |
| parent = instantiateBundle(baseName, parentLocaleName, defaultID, root, openType); |
| } else if (i != -1) { |
| parent = instantiateBundle(baseName, localeName.substring(0, i), defaultID, root, openType); |
| } else if (!localeName.equals(rootLocale)){ |
| parent = instantiateBundle(baseName, rootLocale, defaultID, root, openType); |
| } |
| |
| if (!b.equals(parent)){ |
| b.setParent(parent); |
| } |
| } |
| return b; |
| }}); |
| } |
| |
| ICUResourceBundle get(String aKey, HashMap<String, String> aliasesVisited, UResourceBundle requested) { |
| ICUResourceBundle obj = (ICUResourceBundle)handleGet(aKey, aliasesVisited, requested); |
| if (obj == null) { |
| obj = getParent(); |
| if (obj != null) { |
| //call the get method to recursively fetch the resource |
| obj = obj.get(aKey, aliasesVisited, requested); |
| } |
| if (obj == null) { |
| String fullName = ICUResourceBundleReader.getFullName(getBaseName(), getLocaleID()); |
| throw new MissingResourceException( |
| "Can't find resource for bundle " + fullName + ", key " |
| + aKey, this.getClass().getName(), aKey); |
| } |
| } |
| return obj; |
| } |
| |
| /** Data member where the subclasses store the key. */ |
| protected String key; |
| |
| /** |
| * A resource word value that means "no resource". |
| * Note: 0xffffffff == -1 |
| * This has the same value as UResourceBundle.NONE, but they are semantically |
| * different and should be used appropriately according to context: |
| * NONE means "no type". |
| * (The type of RES_BOGUS is RES_RESERVED=15 which was defined in ICU4C ures.h.) |
| */ |
| public static final int RES_BOGUS = 0xffffffff; |
| //blic static final int RES_MAX_OFFSET = 0x0fffffff; |
| |
| /** |
| * Resource type constant for aliases; |
| * internally stores a string which identifies the actual resource |
| * storing the data (can be in a different resource bundle). |
| * Resolved internally before delivering the actual resource through the API. |
| */ |
| public static final int ALIAS = 3; |
| |
| /** Resource type constant for tables with 32-bit count, key offsets and values. */ |
| public static final int TABLE32 = 4; |
| |
| /** |
| * Resource type constant for tables with 16-bit count, key offsets and values. |
| * All values are STRING_V2 strings. |
| */ |
| public static final int TABLE16 = 5; |
| |
| /** Resource type constant for 16-bit Unicode strings in formatVersion 2. */ |
| public static final int STRING_V2 = 6; |
| |
| /** |
| * Resource type constant for arrays with 16-bit count and values. |
| * All values are STRING_V2 strings. |
| */ |
| public static final int ARRAY16 = 9; |
| |
| /* Resource type 15 is not defined but effectively used by RES_BOGUS=0xffffffff. */ |
| |
| /** |
| * Create a bundle using a reader. |
| * @param baseName The name for the bundle. |
| * @param localeID The locale identification. |
| * @param root The ClassLoader object root. |
| * @return the new bundle |
| */ |
| public static ICUResourceBundle createBundle(String baseName, String localeID, ClassLoader root) { |
| ICUResourceBundleReader reader = ICUResourceBundleReader.getReader(baseName, localeID, root); |
| if (reader == null) { |
| // could not open the .res file |
| return null; |
| } |
| return getBundle(reader, baseName, localeID, root); |
| } |
| |
| @Override |
| protected String getLocaleID() { |
| return wholeBundle.localeID; |
| } |
| |
| @Override |
| protected String getBaseName() { |
| return wholeBundle.baseName; |
| } |
| |
| @Override |
| public ULocale getULocale() { |
| return wholeBundle.ulocale; |
| } |
| |
| /** |
| * Returns true if this is the root bundle, or an item in the root bundle. |
| */ |
| public boolean isRoot() { |
| return wholeBundle.localeID.isEmpty() || wholeBundle.localeID.equals("root"); |
| } |
| |
| @Override |
| public ICUResourceBundle getParent() { |
| return (ICUResourceBundle) parent; |
| } |
| |
| @Override |
| protected void setParent(ResourceBundle parent) { |
| this.parent = parent; |
| } |
| |
| @Override |
| public String getKey() { |
| return key; |
| } |
| |
| /** |
| * Get the noFallback flag specified in the loaded bundle. |
| * @return The noFallback flag. |
| */ |
| private boolean getNoFallback() { |
| return wholeBundle.reader.getNoFallback(); |
| } |
| |
| private static ICUResourceBundle getBundle(ICUResourceBundleReader reader, |
| String baseName, String localeID, |
| ClassLoader loader) { |
| ICUResourceBundleImpl.ResourceTable rootTable; |
| int rootRes = reader.getRootResource(); |
| if(ICUResourceBundleReader.URES_IS_TABLE(ICUResourceBundleReader.RES_GET_TYPE(rootRes))) { |
| WholeBundle wb = new WholeBundle(baseName, localeID, loader, reader); |
| rootTable = new ICUResourceBundleImpl.ResourceTable(wb, rootRes); |
| } else { |
| throw new IllegalStateException("Invalid format error"); |
| } |
| String aliasString = rootTable.findString("%%ALIAS"); |
| if(aliasString != null) { |
| return (ICUResourceBundle)UResourceBundle.getBundleInstance(baseName, aliasString); |
| } else { |
| return rootTable; |
| } |
| } |
| /** |
| * Constructor for the root table of a bundle. |
| */ |
| protected ICUResourceBundle(WholeBundle wholeBundle) { |
| this.wholeBundle = wholeBundle; |
| } |
| // constructor for inner classes |
| protected ICUResourceBundle(ICUResourceBundle container, String key) { |
| this.key = key; |
| wholeBundle = container.wholeBundle; |
| this.container = container; |
| parent = container.parent; |
| } |
| |
| private static final char RES_PATH_SEP_CHAR = '/'; |
| private static final String RES_PATH_SEP_STR = "/"; |
| private static final String ICUDATA = "ICUDATA"; |
| private static final char HYPHEN = '-'; |
| private static final String LOCALE = "LOCALE"; |
| |
| /** |
| * Returns the resource object referred to from the alias _resource int's path string. |
| * Throws MissingResourceException if not found. |
| * |
| * If the alias path does not contain a key path: |
| * If keys != null then keys[:depth] is used. |
| * Otherwise the base key path plus the key parameter is used. |
| * |
| * @param base A direct or indirect container of the alias. |
| * @param keys The key path to the alias, or null. (const) |
| * @param depth The length of the key path, if keys != null. |
| * @param key The alias' own key within this current container, if keys == null. |
| * @param _resource The alias resource int. |
| * @param aliasesVisited Set of alias path strings already visited, for detecting loops. |
| * We cannot change the type (e.g., to Set<String>) because it is used |
| * in protected/@stable UResourceBundle methods. |
| * @param requested The original resource object from which the lookup started, |
| * which is the starting point for "/LOCALE/..." aliases. |
| * @return the aliased resource object |
| */ |
| protected static ICUResourceBundle getAliasedResource( |
| ICUResourceBundle base, String[] keys, int depth, |
| String key, int _resource, |
| HashMap<String, String> aliasesVisited, |
| UResourceBundle requested) { |
| WholeBundle wholeBundle = base.wholeBundle; |
| ClassLoader loaderToUse = wholeBundle.loader; |
| String locale; |
| String keyPath = null; |
| String bundleName; |
| String rpath = wholeBundle.reader.getAlias(_resource); |
| if (aliasesVisited == null) { |
| aliasesVisited = new HashMap<>(); |
| } |
| if (aliasesVisited.get(rpath) != null) { |
| throw new IllegalArgumentException( |
| "Circular references in the resource bundles"); |
| } |
| aliasesVisited.put(rpath, ""); |
| if (rpath.indexOf(RES_PATH_SEP_CHAR) == 0) { |
| int i = rpath.indexOf(RES_PATH_SEP_CHAR, 1); |
| int j = rpath.indexOf(RES_PATH_SEP_CHAR, i + 1); |
| bundleName = rpath.substring(1, i); |
| if (j < 0) { |
| locale = rpath.substring(i + 1); |
| } else { |
| locale = rpath.substring(i + 1, j); |
| keyPath = rpath.substring(j + 1, rpath.length()); |
| } |
| //there is a path included |
| if (bundleName.equals(ICUDATA)) { |
| bundleName = ICUData.ICU_BASE_NAME; |
| loaderToUse = ICU_DATA_CLASS_LOADER; |
| }else if(bundleName.indexOf(ICUDATA)>-1){ |
| int idx = bundleName.indexOf(HYPHEN); |
| if(idx>-1){ |
| bundleName = ICUData.ICU_BASE_NAME+RES_PATH_SEP_STR+bundleName.substring(idx+1,bundleName.length()); |
| loaderToUse = ICU_DATA_CLASS_LOADER; |
| } |
| } |
| } else { |
| //no path start with locale |
| int i = rpath.indexOf(RES_PATH_SEP_CHAR); |
| if (i != -1) { |
| locale = rpath.substring(0, i); |
| keyPath = rpath.substring(i + 1); |
| } else { |
| locale = rpath; |
| } |
| bundleName = wholeBundle.baseName; |
| } |
| ICUResourceBundle bundle = null; |
| ICUResourceBundle sub = null; |
| if(bundleName.equals(LOCALE)){ |
| bundleName = wholeBundle.baseName; |
| keyPath = rpath.substring(LOCALE.length() + 2/* prepending and appending / */, rpath.length()); |
| |
| // Get the top bundle of the requested bundle |
| bundle = (ICUResourceBundle)requested; |
| while (bundle.container != null) { |
| bundle = bundle.container; |
| } |
| sub = ICUResourceBundle.findResourceWithFallback(keyPath, bundle, null); |
| }else{ |
| bundle = getBundleInstance(bundleName, locale, loaderToUse, false); |
| |
| int numKeys; |
| if (keyPath != null) { |
| numKeys = countPathKeys(keyPath); |
| if (numKeys > 0) { |
| keys = new String[numKeys]; |
| getResPathKeys(keyPath, numKeys, keys, 0); |
| } |
| } else if (keys != null) { |
| numKeys = depth; |
| } else { |
| depth = base.getResDepth(); |
| numKeys = depth + 1; |
| keys = new String[numKeys]; |
| base.getResPathKeys(keys, depth); |
| keys[depth] = key; |
| } |
| if (numKeys > 0) { |
| sub = bundle; |
| for (int i = 0; sub != null && i < numKeys; ++i) { |
| sub = sub.get(keys[i], aliasesVisited, requested); |
| } |
| } |
| } |
| if (sub == null) { |
| throw new MissingResourceException(wholeBundle.localeID, wholeBundle.baseName, key); |
| } |
| // TODO: If we know that sub is not cached, |
| // then we should set its container and key to the alias' location, |
| // so that it behaves as if its value had been copied into the alias location. |
| // However, findResourceWithFallback() must reroute its bundle and key path |
| // to where the alias data comes from. |
| return sub; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final Set<String> getTopLevelKeySet() { |
| return wholeBundle.topLevelKeys; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final void setTopLevelKeySet(Set<String> keySet) { |
| wholeBundle.topLevelKeys = keySet; |
| } |
| |
| // This is the worker function for the public getKeys(). |
| // TODO: Now that UResourceBundle uses handleKeySet(), this function is obsolete. |
| // It is also not inherited from ResourceBundle, and it is not implemented |
| // by ResourceBundleWrapper despite its documentation requiring all subclasses to |
| // implement it. |
| // Consider deprecating UResourceBundle.handleGetKeys(), and consider making it always return null. |
| @Override |
| protected Enumeration<String> handleGetKeys() { |
| return Collections.enumeration(handleKeySet()); |
| } |
| |
| @Override |
| protected boolean isTopLevelResource() { |
| return container == null; |
| } |
| } |