• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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