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