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 98 public Enumeration<String> getKeys() { 99 return null; 100 } 101 102 @Override 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 269 public static ResourceBundle getBundle(String baseName, 270 Locale targetLocale, ResourceBundle.Control control) { 271 return getBundle(baseName, targetLocale, getLoader(), control); 272 } 273 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 472 public final String[] getStringArray(String key) { 473 return (String[]) getObject(key); 474 } 475 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 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 */ 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 */ 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 */ 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 604 private void setLocale(Locale locale) { 605 this.locale = locale; 606 } 607 608 public static void clearCache() { 609 cache.remove(ClassLoader.getSystemClassLoader()); 610 } 611 612 public static void clearCache(ClassLoader loader) { 613 if (loader == null) { 614 throw new NullPointerException("loader == null"); 615 } 616 cache.remove(loader); 617 } 618 619 public boolean containsKey(String key) { 620 if (key == null) { 621 throw new NullPointerException("key == null"); 622 } 623 return keySet().contains(key); 624 } 625 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 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 657 public NoFallbackControl(String format) { 658 listClass = new ArrayList<String>(); 659 listClass.add(format); 660 super.format = Collections.unmodifiableList(listClass); 661 } 662 663 public NoFallbackControl(List<String> list) { 664 super.format = list; 665 } 666 667 @Override 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 { 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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