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