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