1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.util; 19 20 import dalvik.system.VMStack; 21 import java.io.File; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.InputStreamReader; 25 import java.net.URL; 26 import java.net.URLConnection; 27 import java.nio.charset.Charsets; 28 import static java.nio.charset.Charsets.UTF_8; 29 import libcore.io.IoUtils; 30 31 /** 32 * {@code ResourceBundle} is an abstract class which is the superclass of classes which 33 * provide {@code Locale}-specific resources. A bundle contains a number of named 34 * resources, where the names are {@code Strings}. A bundle may have a parent bundle, 35 * and when a resource is not found in a bundle, the parent bundle is searched for 36 * the resource. If the fallback mechanism reaches the base bundle and still 37 * can't find the resource it throws a {@code MissingResourceException}. 38 * 39 * <ul> 40 * <li>All bundles for the same group of resources share a common base bundle. 41 * This base bundle acts as the root and is the last fallback in case none of 42 * its children was able to respond to a request.</li> 43 * <li>The first level contains changes between different languages. Only the 44 * differences between a language and the language of the base bundle need to be 45 * handled by a language-specific {@code ResourceBundle}.</li> 46 * <li>The second level contains changes between different countries that use 47 * the same language. Only the differences between a country and the country of 48 * the language bundle need to be handled by a country-specific {@code ResourceBundle}. 49 * </li> 50 * <li>The third level contains changes that don't have a geographic reason 51 * (e.g. changes that where made at some point in time like {@code PREEURO} where the 52 * currency of come countries changed. The country bundle would return the 53 * current currency (Euro) and the {@code PREEURO} variant bundle would return the old 54 * currency (e.g. DM for Germany).</li> 55 * </ul> 56 * 57 * <strong>Examples</strong> 58 * <ul> 59 * <li>BaseName (base bundle) 60 * <li>BaseName_de (german language bundle) 61 * <li>BaseName_fr (french language bundle) 62 * <li>BaseName_de_DE (bundle with Germany specific resources in german) 63 * <li>BaseName_de_CH (bundle with Switzerland specific resources in german) 64 * <li>BaseName_fr_CH (bundle with Switzerland specific resources in french) 65 * <li>BaseName_de_DE_PREEURO (bundle with Germany specific resources in german of 66 * the time before the Euro) 67 * <li>BaseName_fr_FR_PREEURO (bundle with France specific resources in french of 68 * the time before the Euro) 69 * </ul> 70 * 71 * It's also possible to create variants for languages or countries. This can be 72 * done by just skipping the country or language abbreviation: 73 * BaseName_us__POSIX or BaseName__DE_PREEURO. But it's not allowed to 74 * circumvent both language and country: BaseName___VARIANT is illegal. 75 * 76 * @see Properties 77 * @see PropertyResourceBundle 78 * @see ListResourceBundle 79 * @since 1.1 80 */ 81 public abstract class ResourceBundle { 82 83 private static final String UNDER_SCORE = "_"; 84 85 private static final String EMPTY_STRING = ""; 86 87 /** 88 * The parent of this {@code ResourceBundle} that is used if this bundle doesn't 89 * include the requested resource. 90 */ 91 protected ResourceBundle parent; 92 93 private Locale locale; 94 95 private long lastLoadTime = 0; 96 97 static class MissingBundle extends ResourceBundle { 98 @Override getKeys()99 public Enumeration<String> getKeys() { 100 return null; 101 } 102 103 @Override handleGetObject(String name)104 public Object handleGetObject(String name) { 105 return null; 106 } 107 } 108 109 private static final ResourceBundle MISSING = new MissingBundle(); 110 111 private static final ResourceBundle MISSINGBASE = new MissingBundle(); 112 113 private static final WeakHashMap<Object, Hashtable<String, ResourceBundle>> cache 114 = new WeakHashMap<Object, Hashtable<String, ResourceBundle>>(); 115 116 private static Locale cacheLocale = Locale.getDefault(); 117 118 /** 119 * Constructs a new instance of this class. 120 */ ResourceBundle()121 public ResourceBundle() { 122 /* empty */ 123 } 124 125 /** 126 * Finds the named resource bundle for the default {@code Locale} and the caller's 127 * {@code ClassLoader}. 128 * 129 * @param bundleName 130 * the name of the {@code ResourceBundle}. 131 * @return the requested {@code ResourceBundle}. 132 * @throws MissingResourceException 133 * if the {@code ResourceBundle} cannot be found. 134 */ getBundle(String bundleName)135 public static ResourceBundle getBundle(String bundleName) throws MissingResourceException { 136 ClassLoader classLoader = VMStack.getCallingClassLoader(); 137 if (classLoader == null) { 138 classLoader = getLoader(); 139 } 140 return getBundle(bundleName, Locale.getDefault(), classLoader); 141 } 142 143 /** 144 * Finds the named {@code ResourceBundle} for the specified {@code Locale} and the caller 145 * {@code ClassLoader}. 146 * 147 * @param bundleName 148 * the name of the {@code ResourceBundle}. 149 * @param locale 150 * the {@code Locale}. 151 * @return the requested resource bundle. 152 * @throws MissingResourceException 153 * if the resource bundle cannot be found. 154 */ getBundle(String bundleName, Locale locale)155 public static ResourceBundle getBundle(String bundleName, Locale locale) { 156 ClassLoader classLoader = VMStack.getCallingClassLoader(); 157 if (classLoader == null) { 158 classLoader = getLoader(); 159 } 160 return getBundle(bundleName, locale, classLoader); 161 } 162 163 /** 164 * Finds the named resource bundle for the specified {@code Locale} and {@code ClassLoader}. 165 * 166 * The passed base name and {@code Locale} are used to create resource bundle names. 167 * The first name is created by concatenating the base name with the result 168 * of {@link Locale#toString()}. From this name all parent bundle names are 169 * derived. Then the same thing is done for the default {@code Locale}. This results 170 * in a list of possible bundle names. 171 * 172 * <strong>Example</strong> For the basename "BaseName", the {@code Locale} of the 173 * German part of Switzerland (de_CH) and the default {@code Locale} en_US the list 174 * would look something like this: 175 * 176 * <ol> 177 * <li>BaseName_de_CH</li> 178 * <li>BaseName_de</li> 179 * <li>Basename_en_US</li> 180 * <li>Basename_en</li> 181 * <li>BaseName</li> 182 * </ol> 183 * 184 * This list also shows the order in which the bundles will be searched for a requested 185 * resource in the German part of Switzerland (de_CH). 186 * 187 * As a first step, this method tries to instantiate 188 * a {@code ResourceBundle} with the names provided. 189 * If such a class can be instantiated and initialized, it is returned and 190 * all the parent bundles are instantiated too. If no such class can be 191 * found this method tries to load a {@code .properties} file with the names by 192 * replacing dots in the base name with a slash and by appending 193 * "{@code .properties}" at the end of the string. If such a resource can be found 194 * by calling {@link ClassLoader#getResource(String)} it is used to 195 * initialize a {@link PropertyResourceBundle}. If this succeeds, it will 196 * also load the parents of this {@code ResourceBundle}. 197 * 198 * For compatibility with older code, the bundle name isn't required to be 199 * a fully qualified class name. It's also possible to directly pass 200 * the path to a properties file (without a file extension). 201 * 202 * @param bundleName 203 * the name of the {@code ResourceBundle}. 204 * @param locale 205 * the {@code Locale}. 206 * @param loader 207 * the {@code ClassLoader} to use. 208 * @return the requested {@code ResourceBundle}. 209 * @throws MissingResourceException 210 * if the {@code ResourceBundle} cannot be found. 211 */ getBundle(String bundleName, Locale locale, ClassLoader loader)212 public static ResourceBundle getBundle(String bundleName, Locale locale, 213 ClassLoader loader) throws MissingResourceException { 214 if (loader == null) { 215 throw new NullPointerException("loader == null"); 216 } else if (bundleName == null) { 217 throw new NullPointerException("bundleName == null"); 218 } 219 Locale defaultLocale = Locale.getDefault(); 220 if (!cacheLocale.equals(defaultLocale)) { 221 cache.clear(); 222 cacheLocale = defaultLocale; 223 } 224 ResourceBundle bundle = null; 225 if (!locale.equals(defaultLocale)) { 226 bundle = handleGetBundle(false, bundleName, locale, loader); 227 } 228 if (bundle == null) { 229 bundle = handleGetBundle(true, bundleName, defaultLocale, loader); 230 if (bundle == null) { 231 throw missingResourceException(bundleName + '_' + locale, ""); 232 } 233 } 234 return bundle; 235 } 236 missingResourceException(String className, String key)237 private static MissingResourceException missingResourceException(String className, String key) { 238 String detail = "Can't find resource for bundle '" + className + "', key '" + key + "'"; 239 throw new MissingResourceException(detail, className, key); 240 } 241 242 /** 243 * Finds the named resource bundle for the specified base name and control. 244 * 245 * @param baseName 246 * the base name of a resource bundle 247 * @param control 248 * the control that control the access sequence 249 * @return the named resource bundle 250 * 251 * @since 1.6 252 */ getBundle(String baseName, ResourceBundle.Control control)253 public static ResourceBundle getBundle(String baseName, ResourceBundle.Control control) { 254 return getBundle(baseName, Locale.getDefault(), getLoader(), control); 255 } 256 257 /** 258 * Finds the named resource bundle for the specified base name and control. 259 * 260 * @param baseName 261 * the base name of a resource bundle 262 * @param targetLocale 263 * the target locale of the resource bundle 264 * @param control 265 * the control that control the access sequence 266 * @return the named resource bundle 267 * 268 * @since 1.6 269 */ getBundle(String baseName, Locale targetLocale, ResourceBundle.Control control)270 public static ResourceBundle getBundle(String baseName, 271 Locale targetLocale, ResourceBundle.Control control) { 272 return getBundle(baseName, targetLocale, getLoader(), control); 273 } 274 getLoader()275 private static ClassLoader getLoader() { 276 ClassLoader cl = ResourceBundle.class.getClassLoader(); 277 if (cl == null) { 278 cl = ClassLoader.getSystemClassLoader(); 279 } 280 return cl; 281 } 282 283 /** 284 * Finds the named resource bundle for the specified base name and control. 285 * 286 * @param baseName 287 * the base name of a resource bundle 288 * @param targetLocale 289 * the target locale of the resource bundle 290 * @param loader 291 * the class loader to load resource 292 * @param control 293 * the control that control the access sequence 294 * @return the named resource bundle 295 * 296 * @since 1.6 297 */ getBundle(String baseName, Locale targetLocale, ClassLoader loader, ResourceBundle.Control control)298 public static ResourceBundle getBundle(String baseName, 299 Locale targetLocale, ClassLoader loader, 300 ResourceBundle.Control control) { 301 boolean expired = false; 302 String bundleName = control.toBundleName(baseName, targetLocale); 303 Object cacheKey = loader != null ? loader : "null"; 304 Hashtable<String, ResourceBundle> loaderCache = getLoaderCache(cacheKey); 305 ResourceBundle result = loaderCache.get(bundleName); 306 if (result != null) { 307 long time = control.getTimeToLive(baseName, targetLocale); 308 if (time == 0 || time == Control.TTL_NO_EXPIRATION_CONTROL 309 || time + result.lastLoadTime < System.currentTimeMillis()) { 310 if (MISSING == result) { 311 throw new MissingResourceException(null, bundleName + '_' 312 + targetLocale, EMPTY_STRING); 313 } 314 return result; 315 } 316 expired = true; 317 } 318 // try to load 319 ResourceBundle ret = processGetBundle(baseName, targetLocale, loader, 320 control, expired, result); 321 322 if (ret != null) { 323 loaderCache.put(bundleName, ret); 324 ret.lastLoadTime = System.currentTimeMillis(); 325 return ret; 326 } 327 loaderCache.put(bundleName, MISSING); 328 throw new MissingResourceException(null, bundleName + '_' + targetLocale, EMPTY_STRING); 329 } 330 processGetBundle(String baseName, Locale targetLocale, ClassLoader loader, ResourceBundle.Control control, boolean expired, ResourceBundle result)331 private static ResourceBundle processGetBundle(String baseName, 332 Locale targetLocale, ClassLoader loader, 333 ResourceBundle.Control control, boolean expired, 334 ResourceBundle result) { 335 List<Locale> locales = control.getCandidateLocales(baseName, targetLocale); 336 if (locales == null) { 337 throw new IllegalArgumentException(); 338 } 339 List<String> formats = control.getFormats(baseName); 340 if (Control.FORMAT_CLASS == formats 341 || Control.FORMAT_PROPERTIES == formats 342 || Control.FORMAT_DEFAULT == formats) { 343 throw new IllegalArgumentException(); 344 } 345 ResourceBundle ret = null; 346 ResourceBundle currentBundle = null; 347 ResourceBundle bundle = null; 348 for (Locale locale : locales) { 349 for (String format : formats) { 350 try { 351 if (expired) { 352 bundle = control.newBundle(baseName, locale, format, 353 loader, control.needsReload(baseName, locale, 354 format, loader, result, System 355 .currentTimeMillis())); 356 357 } else { 358 try { 359 bundle = control.newBundle(baseName, locale, 360 format, loader, false); 361 } catch (IllegalArgumentException e) { 362 // do nothing 363 } 364 } 365 } catch (IllegalAccessException e) { 366 // do nothing 367 } catch (InstantiationException e) { 368 // do nothing 369 } catch (IOException e) { 370 // do nothing 371 } 372 if (bundle != null) { 373 if (currentBundle != null) { 374 currentBundle.setParent(bundle); 375 currentBundle = bundle; 376 } else { 377 if (ret == null) { 378 ret = bundle; 379 currentBundle = ret; 380 } 381 } 382 } 383 if (bundle != null) { 384 break; 385 } 386 } 387 } 388 389 if ((ret == null) 390 || (Locale.ROOT.equals(ret.getLocale()) && (!(locales.size() == 1 && locales 391 .contains(Locale.ROOT))))) { 392 Locale nextLocale = control.getFallbackLocale(baseName, targetLocale); 393 if (nextLocale != null) { 394 ret = processGetBundle(baseName, nextLocale, loader, control, 395 expired, result); 396 } 397 } 398 399 return ret; 400 } 401 402 /** 403 * Returns the names of the resources contained in this {@code ResourceBundle}. 404 * 405 * @return an {@code Enumeration} of the resource names. 406 */ getKeys()407 public abstract Enumeration<String> getKeys(); 408 409 /** 410 * Gets the {@code Locale} of this {@code ResourceBundle}. In case a bundle was not 411 * found for the requested {@code Locale}, this will return the actual {@code Locale} of 412 * this resource bundle that was found after doing a fallback. 413 * 414 * @return the {@code Locale} of this {@code ResourceBundle}. 415 */ getLocale()416 public Locale getLocale() { 417 return locale; 418 } 419 420 /** 421 * Returns the named resource from this {@code ResourceBundle}. If the resource 422 * cannot be found in this bundle, it falls back to the parent bundle (if 423 * it's not null) by calling the {@link #handleGetObject} method. If the resource still 424 * can't be found it throws a {@code MissingResourceException}. 425 * 426 * @param key 427 * the name of the resource. 428 * @return the resource object. 429 * @throws MissingResourceException 430 * if the resource is not found. 431 */ getObject(String key)432 public final Object getObject(String key) { 433 ResourceBundle last, theParent = this; 434 do { 435 Object result = theParent.handleGetObject(key); 436 if (result != null) { 437 return result; 438 } 439 last = theParent; 440 theParent = theParent.parent; 441 } while (theParent != null); 442 throw missingResourceException(last.getClass().getName(), key); 443 } 444 445 /** 446 * Returns the named string resource from this {@code ResourceBundle}. 447 * 448 * @param key 449 * the name of the resource. 450 * @return the resource string. 451 * @throws MissingResourceException 452 * if the resource is not found. 453 * @throws ClassCastException 454 * if the resource found is not a string. 455 * @see #getObject(String) 456 */ getString(String key)457 public final String getString(String key) { 458 return (String) getObject(key); 459 } 460 461 /** 462 * Returns the named resource from this {@code ResourceBundle}. 463 * 464 * @param key 465 * the name of the resource. 466 * @return the resource string array. 467 * @throws MissingResourceException 468 * if the resource is not found. 469 * @throws ClassCastException 470 * if the resource found is not an array of strings. 471 * @see #getObject(String) 472 */ getStringArray(String key)473 public final String[] getStringArray(String key) { 474 return (String[]) getObject(key); 475 } 476 handleGetBundle(boolean loadBase, String base, Locale locale, ClassLoader loader)477 private static ResourceBundle handleGetBundle(boolean loadBase, String base, Locale locale, 478 ClassLoader loader) { 479 String localeName = locale.toString(); 480 String bundleName = localeName.isEmpty() 481 ? base 482 : (base + "_" + localeName); 483 Object cacheKey = loader != null ? loader : "null"; 484 Hashtable<String, ResourceBundle> loaderCache = getLoaderCache(cacheKey); 485 ResourceBundle cached = loaderCache.get(bundleName); 486 if (cached != null) { 487 if (cached == MISSINGBASE) { 488 return null; 489 } else if (cached == MISSING) { 490 if (!loadBase) { 491 return null; 492 } 493 Locale newLocale = strip(locale); 494 if (newLocale == null) { 495 return null; 496 } 497 return handleGetBundle(loadBase, base, newLocale, loader); 498 } 499 return cached; 500 } 501 502 ResourceBundle bundle = null; 503 try { 504 Class<?> bundleClass = Class.forName(bundleName, true, loader); 505 if (ResourceBundle.class.isAssignableFrom(bundleClass)) { 506 bundle = (ResourceBundle) bundleClass.newInstance(); 507 } 508 } catch (LinkageError ignored) { 509 } catch (Exception ignored) { 510 } 511 512 if (bundle != null) { 513 bundle.setLocale(locale); 514 } else { 515 String fileName = bundleName.replace('.', '/') + ".properties"; 516 InputStream stream = loader != null 517 ? loader.getResourceAsStream(fileName) 518 : ClassLoader.getSystemResourceAsStream(fileName); 519 if (stream != null) { 520 try { 521 bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8)); 522 bundle.setLocale(locale); 523 } catch (IOException ignored) { 524 } finally { 525 IoUtils.closeQuietly(stream); 526 } 527 } 528 } 529 530 Locale strippedLocale = strip(locale); 531 if (bundle != null) { 532 if (strippedLocale != null) { 533 ResourceBundle parent = handleGetBundle(loadBase, base, strippedLocale, loader); 534 if (parent != null) { 535 bundle.setParent(parent); 536 } 537 } 538 loaderCache.put(bundleName, bundle); 539 return bundle; 540 } 541 542 if (strippedLocale != null && (loadBase || !strippedLocale.toString().isEmpty())) { 543 bundle = handleGetBundle(loadBase, base, strippedLocale, loader); 544 if (bundle != null) { 545 loaderCache.put(bundleName, bundle); 546 return bundle; 547 } 548 } 549 loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING); 550 return null; 551 } 552 getLoaderCache(Object cacheKey)553 private static Hashtable<String, ResourceBundle> getLoaderCache(Object cacheKey) { 554 synchronized (cache) { 555 Hashtable<String, ResourceBundle> loaderCache = cache.get(cacheKey); 556 if (loaderCache == null) { 557 loaderCache = new Hashtable<String, ResourceBundle>(); 558 cache.put(cacheKey, loaderCache); 559 } 560 return loaderCache; 561 } 562 } 563 564 /** 565 * Returns the named resource from this {@code ResourceBundle}, or null if the 566 * resource is not found. 567 * 568 * @param key 569 * the name of the resource. 570 * @return the resource object. 571 */ handleGetObject(String key)572 protected abstract Object handleGetObject(String key); 573 574 /** 575 * Sets the parent resource bundle of this {@code ResourceBundle}. The parent is 576 * searched for resources which are not found in this {@code ResourceBundle}. 577 * 578 * @param bundle 579 * the parent {@code ResourceBundle}. 580 */ setParent(ResourceBundle bundle)581 protected void setParent(ResourceBundle bundle) { 582 parent = bundle; 583 } 584 585 /** 586 * Returns a locale with the most-specific field removed, or null if this 587 * locale had an empty language, country and variant. 588 */ strip(Locale locale)589 private static Locale strip(Locale locale) { 590 String language = locale.getLanguage(); 591 String country = locale.getCountry(); 592 String variant = locale.getVariant(); 593 if (!variant.isEmpty()) { 594 variant = ""; 595 } else if (!country.isEmpty()) { 596 country = ""; 597 } else if (!language.isEmpty()) { 598 language = ""; 599 } else { 600 return null; 601 } 602 return new Locale(language, country, variant); 603 } 604 setLocale(Locale locale)605 private void setLocale(Locale locale) { 606 this.locale = locale; 607 } 608 clearCache()609 public static void clearCache() { 610 cache.remove(ClassLoader.getSystemClassLoader()); 611 } 612 clearCache(ClassLoader loader)613 public static void clearCache(ClassLoader loader) { 614 if (loader == null) { 615 throw new NullPointerException("loader == null"); 616 } 617 cache.remove(loader); 618 } 619 containsKey(String key)620 public boolean containsKey(String key) { 621 if (key == null) { 622 throw new NullPointerException("key == null"); 623 } 624 return keySet().contains(key); 625 } 626 keySet()627 public Set<String> keySet() { 628 Set<String> ret = new HashSet<String>(); 629 Enumeration<String> keys = getKeys(); 630 while (keys.hasMoreElements()) { 631 ret.add(keys.nextElement()); 632 } 633 return ret; 634 } 635 handleKeySet()636 protected Set<String> handleKeySet() { 637 Set<String> set = keySet(); 638 Set<String> ret = new HashSet<String>(); 639 for (String key : set) { 640 if (handleGetObject(key) != null) { 641 ret.add(key); 642 } 643 } 644 return ret; 645 } 646 647 private static class NoFallbackControl extends Control { 648 649 static final Control NOFALLBACK_FORMAT_PROPERTIES_CONTROL = new NoFallbackControl( 650 JAVAPROPERTIES); 651 652 static final Control NOFALLBACK_FORMAT_CLASS_CONTROL = new NoFallbackControl( 653 JAVACLASS); 654 655 static final Control NOFALLBACK_FORMAT_DEFAULT_CONTROL = new NoFallbackControl( 656 listDefault); 657 NoFallbackControl(String format)658 public NoFallbackControl(String format) { 659 listClass = new ArrayList<String>(); 660 listClass.add(format); 661 super.format = Collections.unmodifiableList(listClass); 662 } 663 NoFallbackControl(List<String> list)664 public NoFallbackControl(List<String> list) { 665 super.format = list; 666 } 667 668 @Override getFallbackLocale(String baseName, Locale locale)669 public Locale getFallbackLocale(String baseName, Locale locale) { 670 if (baseName == null) { 671 throw new NullPointerException("baseName == null"); 672 } else if (locale == null) { 673 throw new NullPointerException("locale == null"); 674 } 675 return null; 676 } 677 } 678 679 private static class SimpleControl extends Control { SimpleControl(String format)680 public SimpleControl(String format) { 681 listClass = new ArrayList<String>(); 682 listClass.add(format); 683 super.format = Collections.unmodifiableList(listClass); 684 } 685 } 686 687 /** 688 * ResourceBundle.Control is a static utility class defines ResourceBundle 689 * load access methods, its default access order is as the same as before. 690 * However users can implement their own control. 691 * 692 * @since 1.6 693 */ 694 public static class Control { 695 static List<String> listDefault = new ArrayList<String>(); 696 697 static List<String> listClass = new ArrayList<String>(); 698 699 static List<String> listProperties = new ArrayList<String>(); 700 701 static String JAVACLASS = "java.class"; 702 703 static String JAVAPROPERTIES = "java.properties"; 704 705 static { 706 listDefault.add(JAVACLASS); 707 listDefault.add(JAVAPROPERTIES); 708 listClass.add(JAVACLASS); 709 listProperties.add(JAVAPROPERTIES); 710 } 711 712 /** 713 * a list defines default format 714 */ 715 public static final List<String> FORMAT_DEFAULT = Collections 716 .unmodifiableList(listDefault); 717 718 /** 719 * a list defines java class format 720 */ 721 public static final List<String> FORMAT_CLASS = Collections 722 .unmodifiableList(listClass); 723 724 /** 725 * a list defines property format 726 */ 727 public static final List<String> FORMAT_PROPERTIES = Collections 728 .unmodifiableList(listProperties); 729 730 /** 731 * a constant that indicates cache will not be used. 732 */ 733 public static final long TTL_DONT_CACHE = -1L; 734 735 /** 736 * a constant that indicates cache will not be expired. 737 */ 738 public static final long TTL_NO_EXPIRATION_CONTROL = -2L; 739 740 private static final Control FORMAT_PROPERTIES_CONTROL = new SimpleControl( 741 JAVAPROPERTIES); 742 743 private static final Control FORMAT_CLASS_CONTROL = new SimpleControl( 744 JAVACLASS); 745 746 private static final Control FORMAT_DEFAULT_CONTROL = new Control(); 747 748 List<String> format; 749 750 /** 751 * default constructor 752 * 753 */ Control()754 protected Control() { 755 listClass = new ArrayList<String>(); 756 listClass.add(JAVACLASS); 757 listClass.add(JAVAPROPERTIES); 758 format = Collections.unmodifiableList(listClass); 759 } 760 761 /** 762 * Returns a control according to {@code formats}. 763 */ getControl(List<String> formats)764 public static Control getControl(List<String> formats) { 765 switch (formats.size()) { 766 case 1: 767 if (formats.contains(JAVACLASS)) { 768 return FORMAT_CLASS_CONTROL; 769 } 770 if (formats.contains(JAVAPROPERTIES)) { 771 return FORMAT_PROPERTIES_CONTROL; 772 } 773 break; 774 case 2: 775 if (formats.equals(FORMAT_DEFAULT)) { 776 return FORMAT_DEFAULT_CONTROL; 777 } 778 break; 779 } 780 throw new IllegalArgumentException(); 781 } 782 783 /** 784 * Returns a control according to {@code formats} whose fallback 785 * locale is null. 786 */ getNoFallbackControl(List<String> formats)787 public static Control getNoFallbackControl(List<String> formats) { 788 switch (formats.size()) { 789 case 1: 790 if (formats.contains(JAVACLASS)) { 791 return NoFallbackControl.NOFALLBACK_FORMAT_CLASS_CONTROL; 792 } 793 if (formats.contains(JAVAPROPERTIES)) { 794 return NoFallbackControl.NOFALLBACK_FORMAT_PROPERTIES_CONTROL; 795 } 796 break; 797 case 2: 798 if (formats.equals(FORMAT_DEFAULT)) { 799 return NoFallbackControl.NOFALLBACK_FORMAT_DEFAULT_CONTROL; 800 } 801 break; 802 } 803 throw new IllegalArgumentException(); 804 } 805 806 /** 807 * Returns a list of candidate locales according to {@code baseName} in 808 * {@code locale}. 809 */ getCandidateLocales(String baseName, Locale locale)810 public List<Locale> getCandidateLocales(String baseName, Locale locale) { 811 if (baseName == null) { 812 throw new NullPointerException("baseName == null"); 813 } else if (locale == null) { 814 throw new NullPointerException("locale == null"); 815 } 816 List<Locale> retList = new ArrayList<Locale>(); 817 String language = locale.getLanguage(); 818 String country = locale.getCountry(); 819 String variant = locale.getVariant(); 820 if (!EMPTY_STRING.equals(variant)) { 821 retList.add(new Locale(language, country, variant)); 822 } 823 if (!EMPTY_STRING.equals(country)) { 824 retList.add(new Locale(language, country)); 825 } 826 if (!EMPTY_STRING.equals(language)) { 827 retList.add(new Locale(language)); 828 } 829 retList.add(Locale.ROOT); 830 return retList; 831 } 832 833 /** 834 * Returns a list of strings of formats according to {@code baseName}. 835 */ getFormats(String baseName)836 public List<String> getFormats(String baseName) { 837 if (baseName == null) { 838 throw new NullPointerException("baseName == null"); 839 } 840 return format; 841 } 842 843 /** 844 * Returns the fallback locale for {@code baseName} in {@code locale}. 845 */ getFallbackLocale(String baseName, Locale locale)846 public Locale getFallbackLocale(String baseName, Locale locale) { 847 if (baseName == null) { 848 throw new NullPointerException("baseName == null"); 849 } else if (locale == null) { 850 throw new NullPointerException("locale == null"); 851 } 852 if (Locale.getDefault() != locale) { 853 return Locale.getDefault(); 854 } 855 return null; 856 } 857 858 /** 859 * Returns a new ResourceBundle. 860 * 861 * @param baseName 862 * the base name to use 863 * @param locale 864 * the given locale 865 * @param format 866 * the format, default is "java.class" or "java.properties" 867 * @param loader 868 * the classloader to use 869 * @param reload 870 * whether to reload the resource 871 * @return a new ResourceBundle according to the give parameters 872 * @throws IllegalAccessException 873 * if we can not access resources 874 * @throws InstantiationException 875 * if we can not instantiate a resource class 876 * @throws IOException 877 * if other I/O exception happens 878 */ newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)879 public ResourceBundle newBundle(String baseName, Locale locale, 880 String format, ClassLoader loader, boolean reload) 881 throws IllegalAccessException, InstantiationException, 882 IOException { 883 if (format == null) { 884 throw new NullPointerException("format == null"); 885 } else if (loader == null) { 886 throw new NullPointerException("loader == null"); 887 } 888 final String bundleName = toBundleName(baseName, locale); 889 final ClassLoader clsloader = loader; 890 ResourceBundle ret; 891 if (format.equals(JAVACLASS)) { 892 Class<?> cls = null; 893 try { 894 cls = clsloader.loadClass(bundleName); 895 } catch (Exception e) { 896 } catch (NoClassDefFoundError e) { 897 } 898 if (cls == null) { 899 return null; 900 } 901 try { 902 ResourceBundle bundle = (ResourceBundle) cls.newInstance(); 903 bundle.setLocale(locale); 904 return bundle; 905 } catch (NullPointerException e) { 906 return null; 907 } 908 } 909 if (format.equals(JAVAPROPERTIES)) { 910 InputStream streams = null; 911 final String resourceName = toResourceName(bundleName, "properties"); 912 if (reload) { 913 URL url = null; 914 try { 915 url = loader.getResource(resourceName); 916 } catch (NullPointerException e) { 917 // do nothing 918 } 919 if (url != null) { 920 URLConnection con = url.openConnection(); 921 con.setUseCaches(false); 922 streams = con.getInputStream(); 923 } 924 } else { 925 try { 926 streams = clsloader.getResourceAsStream(resourceName); 927 } catch (NullPointerException e) { 928 // do nothing 929 } 930 } 931 if (streams != null) { 932 try { 933 ret = new PropertyResourceBundle(new InputStreamReader(streams)); 934 ret.setLocale(locale); 935 streams.close(); 936 } catch (IOException e) { 937 return null; 938 } 939 return ret; 940 } 941 return null; 942 } 943 throw new IllegalArgumentException(); 944 } 945 946 /** 947 * Returns the time to live of the ResourceBundle {@code baseName} in {@code locale}, 948 * default is TTL_NO_EXPIRATION_CONTROL. 949 */ getTimeToLive(String baseName, Locale locale)950 public long getTimeToLive(String baseName, Locale locale) { 951 if (baseName == null) { 952 throw new NullPointerException("baseName == null"); 953 } else if (locale == null) { 954 throw new NullPointerException("locale == null"); 955 } 956 return TTL_NO_EXPIRATION_CONTROL; 957 } 958 959 /** 960 * Returns true if the ResourceBundle needs to reload. 961 * 962 * @param baseName 963 * the base name of the ResourceBundle 964 * @param locale 965 * the locale of the ResourceBundle 966 * @param format 967 * the format to load 968 * @param loader 969 * the ClassLoader to load resource 970 * @param bundle 971 * the ResourceBundle 972 * @param loadTime 973 * the expired time 974 * @return if the ResourceBundle needs to reload 975 */ needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime)976 public boolean needsReload(String baseName, Locale locale, 977 String format, ClassLoader loader, ResourceBundle bundle, 978 long loadTime) { 979 if (bundle == null) { 980 // FIXME what's the use of bundle? 981 throw new NullPointerException("bundle == null"); 982 } 983 String bundleName = toBundleName(baseName, locale); 984 String suffix = format; 985 if (format.equals(JAVACLASS)) { 986 suffix = "class"; 987 } 988 if (format.equals(JAVAPROPERTIES)) { 989 suffix = "properties"; 990 } 991 String urlname = toResourceName(bundleName, suffix); 992 URL url = loader.getResource(urlname); 993 if (url != null) { 994 String fileName = url.getFile(); 995 long lastModified = new File(fileName).lastModified(); 996 if (lastModified > loadTime) { 997 return true; 998 } 999 } 1000 return false; 1001 } 1002 1003 /** 1004 * a utility method to answer the name of a resource bundle according to 1005 * the given base name and locale 1006 * 1007 * @param baseName 1008 * the given base name 1009 * @param locale 1010 * the locale to use 1011 * @return the name of a resource bundle according to the given base 1012 * name and locale 1013 */ toBundleName(String baseName, Locale locale)1014 public String toBundleName(String baseName, Locale locale) { 1015 final String emptyString = EMPTY_STRING; 1016 final String preString = UNDER_SCORE; 1017 final String underline = UNDER_SCORE; 1018 if (baseName == null) { 1019 throw new NullPointerException("baseName == null"); 1020 } 1021 StringBuilder ret = new StringBuilder(); 1022 StringBuilder prefix = new StringBuilder(); 1023 ret.append(baseName); 1024 if (!locale.getLanguage().equals(emptyString)) { 1025 ret.append(underline); 1026 ret.append(locale.getLanguage()); 1027 } else { 1028 prefix.append(preString); 1029 } 1030 if (!locale.getCountry().equals(emptyString)) { 1031 ret.append((CharSequence) prefix); 1032 ret.append(underline); 1033 ret.append(locale.getCountry()); 1034 prefix = new StringBuilder(); 1035 } else { 1036 prefix.append(preString); 1037 } 1038 if (!locale.getVariant().equals(emptyString)) { 1039 ret.append((CharSequence) prefix); 1040 ret.append(underline); 1041 ret.append(locale.getVariant()); 1042 } 1043 return ret.toString(); 1044 } 1045 1046 /** 1047 * a utility method to answer the name of a resource according to the 1048 * given bundleName and suffix 1049 * 1050 * @param bundleName 1051 * the given bundle name 1052 * @param suffix 1053 * the suffix 1054 * @return the name of a resource according to the given bundleName and 1055 * suffix 1056 */ toResourceName(String bundleName, String suffix)1057 public final String toResourceName(String bundleName, String suffix) { 1058 if (suffix == null) { 1059 throw new NullPointerException("suffix == null"); 1060 } 1061 StringBuilder ret = new StringBuilder(bundleName.replace('.', '/')); 1062 ret.append('.'); 1063 ret.append(suffix); 1064 return ret.toString(); 1065 } 1066 } 1067 } 1068