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