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