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