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