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