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