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