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