• 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) {
215             throw new NullPointerException("loader == null");
216         } else if (bundleName == null) {
217             throw new NullPointerException("bundleName == null");
218         }
219         Locale defaultLocale = Locale.getDefault();
220         if (!cacheLocale.equals(defaultLocale)) {
221             cache.clear();
222             cacheLocale = defaultLocale;
223         }
224         ResourceBundle bundle = null;
225         if (!locale.equals(defaultLocale)) {
226             bundle = handleGetBundle(false, bundleName, locale, loader);
227         }
228         if (bundle == null) {
229             bundle = handleGetBundle(true, bundleName, defaultLocale, loader);
230             if (bundle == null) {
231                 throw missingResourceException(bundleName + '_' + locale, "");
232             }
233         }
234         return bundle;
235     }
236 
missingResourceException(String className, String key)237     private static MissingResourceException missingResourceException(String className, String key) {
238         String detail = "Can't find resource for bundle '" + className + "', key '" + key + "'";
239         throw new MissingResourceException(detail, className, key);
240     }
241 
242     /**
243      * Finds the named resource bundle for the specified base name and control.
244      *
245      * @param baseName
246      *            the base name of a resource bundle
247      * @param control
248      *            the control that control the access sequence
249      * @return the named resource bundle
250      *
251      * @since 1.6
252      */
getBundle(String baseName, ResourceBundle.Control control)253     public static ResourceBundle getBundle(String baseName, ResourceBundle.Control control) {
254         return getBundle(baseName, Locale.getDefault(), getLoader(), control);
255     }
256 
257     /**
258      * Finds the named resource bundle for the specified base name and control.
259      *
260      * @param baseName
261      *            the base name of a resource bundle
262      * @param targetLocale
263      *            the target locale of the resource bundle
264      * @param control
265      *            the control that control the access sequence
266      * @return the named resource bundle
267      *
268      * @since 1.6
269      */
getBundle(String baseName, Locale targetLocale, ResourceBundle.Control control)270     public static ResourceBundle getBundle(String baseName,
271             Locale targetLocale, ResourceBundle.Control control) {
272         return getBundle(baseName, targetLocale, getLoader(), control);
273     }
274 
getLoader()275     private static ClassLoader getLoader() {
276         ClassLoader cl = ResourceBundle.class.getClassLoader();
277         if (cl == null) {
278             cl = ClassLoader.getSystemClassLoader();
279         }
280         return cl;
281     }
282 
283     /**
284      * Finds the named resource bundle for the specified base name and control.
285      *
286      * @param baseName
287      *            the base name of a resource bundle
288      * @param targetLocale
289      *            the target locale of the resource bundle
290      * @param loader
291      *            the class loader to load resource
292      * @param control
293      *            the control that control the access sequence
294      * @return the named resource bundle
295      *
296      * @since 1.6
297      */
getBundle(String baseName, Locale targetLocale, ClassLoader loader, ResourceBundle.Control control)298     public static ResourceBundle getBundle(String baseName,
299             Locale targetLocale, ClassLoader loader,
300             ResourceBundle.Control control) {
301         boolean expired = false;
302         String bundleName = control.toBundleName(baseName, targetLocale);
303         Object cacheKey = loader != null ? loader : "null";
304         Hashtable<String, ResourceBundle> loaderCache = getLoaderCache(cacheKey);
305         ResourceBundle result = loaderCache.get(bundleName);
306         if (result != null) {
307             long time = control.getTimeToLive(baseName, targetLocale);
308             if (time == 0 || time == Control.TTL_NO_EXPIRATION_CONTROL
309                     || time + result.lastLoadTime < System.currentTimeMillis()) {
310                 if (MISSING == result) {
311                     throw new MissingResourceException(null, bundleName + '_'
312                             + targetLocale, EMPTY_STRING);
313                 }
314                 return result;
315             }
316             expired = true;
317         }
318         // try to load
319         ResourceBundle ret = processGetBundle(baseName, targetLocale, loader,
320                 control, expired, result);
321 
322         if (ret != null) {
323             loaderCache.put(bundleName, ret);
324             ret.lastLoadTime = System.currentTimeMillis();
325             return ret;
326         }
327         loaderCache.put(bundleName, MISSING);
328         throw new MissingResourceException(null, bundleName + '_' + targetLocale, EMPTY_STRING);
329     }
330 
processGetBundle(String baseName, Locale targetLocale, ClassLoader loader, ResourceBundle.Control control, boolean expired, ResourceBundle result)331     private static ResourceBundle processGetBundle(String baseName,
332             Locale targetLocale, ClassLoader loader,
333             ResourceBundle.Control control, boolean expired,
334             ResourceBundle result) {
335         List<Locale> locales = control.getCandidateLocales(baseName, targetLocale);
336         if (locales == null) {
337             throw new IllegalArgumentException();
338         }
339         List<String> formats = control.getFormats(baseName);
340         if (Control.FORMAT_CLASS == formats
341                 || Control.FORMAT_PROPERTIES == formats
342                 || Control.FORMAT_DEFAULT == formats) {
343             throw new IllegalArgumentException();
344         }
345         ResourceBundle ret = null;
346         ResourceBundle currentBundle = null;
347         ResourceBundle bundle = null;
348         for (Locale locale : locales) {
349             for (String format : formats) {
350                 try {
351                     if (expired) {
352                         bundle = control.newBundle(baseName, locale, format,
353                                 loader, control.needsReload(baseName, locale,
354                                         format, loader, result, System
355                                                 .currentTimeMillis()));
356 
357                     } else {
358                         try {
359                             bundle = control.newBundle(baseName, locale,
360                                     format, loader, false);
361                         } catch (IllegalArgumentException e) {
362                             // do nothing
363                         }
364                     }
365                 } catch (IllegalAccessException e) {
366                     // do nothing
367                 } catch (InstantiationException e) {
368                     // do nothing
369                 } catch (IOException e) {
370                     // do nothing
371                 }
372                 if (bundle != null) {
373                     if (currentBundle != null) {
374                         currentBundle.setParent(bundle);
375                         currentBundle = bundle;
376                     } else {
377                         if (ret == null) {
378                             ret = bundle;
379                             currentBundle = ret;
380                         }
381                     }
382                 }
383                 if (bundle != null) {
384                     break;
385                 }
386             }
387         }
388 
389         if ((ret == null)
390                 || (Locale.ROOT.equals(ret.getLocale()) && (!(locales.size() == 1 && locales
391                         .contains(Locale.ROOT))))) {
392             Locale nextLocale = control.getFallbackLocale(baseName, targetLocale);
393             if (nextLocale != null) {
394                 ret = processGetBundle(baseName, nextLocale, loader, control,
395                         expired, result);
396             }
397         }
398 
399         return ret;
400     }
401 
402     /**
403      * Returns the names of the resources contained in this {@code ResourceBundle}.
404      *
405      * @return an {@code Enumeration} of the resource names.
406      */
getKeys()407     public abstract Enumeration<String> getKeys();
408 
409     /**
410      * Gets the {@code Locale} of this {@code ResourceBundle}. In case a bundle was not
411      * found for the requested {@code Locale}, this will return the actual {@code Locale} of
412      * this resource bundle that was found after doing a fallback.
413      *
414      * @return the {@code Locale} of this {@code ResourceBundle}.
415      */
getLocale()416     public Locale getLocale() {
417         return locale;
418     }
419 
420     /**
421      * Returns the named resource from this {@code ResourceBundle}. If the resource
422      * cannot be found in this bundle, it falls back to the parent bundle (if
423      * it's not null) by calling the {@link #handleGetObject} method. If the resource still
424      * can't be found it throws a {@code MissingResourceException}.
425      *
426      * @param key
427      *            the name of the resource.
428      * @return the resource object.
429      * @throws MissingResourceException
430      *                if the resource is not found.
431      */
getObject(String key)432     public final Object getObject(String key) {
433         ResourceBundle last, theParent = this;
434         do {
435             Object result = theParent.handleGetObject(key);
436             if (result != null) {
437                 return result;
438             }
439             last = theParent;
440             theParent = theParent.parent;
441         } while (theParent != null);
442         throw missingResourceException(last.getClass().getName(), key);
443     }
444 
445     /**
446      * Returns the named string resource from this {@code ResourceBundle}.
447      *
448      * @param key
449      *            the name of the resource.
450      * @return the resource string.
451      * @throws MissingResourceException
452      *                if the resource is not found.
453      * @throws ClassCastException
454      *                if the resource found is not a string.
455      * @see #getObject(String)
456      */
getString(String key)457     public final String getString(String key) {
458         return (String) getObject(key);
459     }
460 
461     /**
462      * Returns the named resource from this {@code ResourceBundle}.
463      *
464      * @param key
465      *            the name of the resource.
466      * @return the resource string array.
467      * @throws MissingResourceException
468      *                if the resource is not found.
469      * @throws ClassCastException
470      *                if the resource found is not an array of strings.
471      * @see #getObject(String)
472      */
getStringArray(String key)473     public final String[] getStringArray(String key) {
474         return (String[]) getObject(key);
475     }
476 
handleGetBundle(boolean loadBase, String base, Locale locale, ClassLoader loader)477     private static ResourceBundle handleGetBundle(boolean loadBase, String base, Locale locale,
478             ClassLoader loader) {
479         String localeName = locale.toString();
480         String bundleName = localeName.isEmpty()
481                 ? base
482                 : (base + "_" + localeName);
483         Object cacheKey = loader != null ? loader : "null";
484         Hashtable<String, ResourceBundle> loaderCache = getLoaderCache(cacheKey);
485         ResourceBundle cached = loaderCache.get(bundleName);
486         if (cached != null) {
487             if (cached == MISSINGBASE) {
488                 return null;
489             } else if (cached == MISSING) {
490                 if (!loadBase) {
491                     return null;
492                 }
493                 Locale newLocale = strip(locale);
494                 if (newLocale == null) {
495                     return null;
496                 }
497                 return handleGetBundle(loadBase, base, newLocale, loader);
498             }
499             return cached;
500         }
501 
502         ResourceBundle bundle = null;
503         try {
504             Class<?> bundleClass = Class.forName(bundleName, true, loader);
505             if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
506                 bundle = (ResourceBundle) bundleClass.newInstance();
507             }
508         } catch (LinkageError ignored) {
509         } catch (Exception ignored) {
510         }
511 
512         if (bundle != null) {
513             bundle.setLocale(locale);
514         } else {
515             String fileName = bundleName.replace('.', '/') + ".properties";
516             InputStream stream = loader != null
517                     ? loader.getResourceAsStream(fileName)
518                     : ClassLoader.getSystemResourceAsStream(fileName);
519             if (stream != null) {
520                 try {
521                     bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8));
522                     bundle.setLocale(locale);
523                 } catch (IOException ignored) {
524                 } finally {
525                     IoUtils.closeQuietly(stream);
526                 }
527             }
528         }
529 
530         Locale strippedLocale = strip(locale);
531         if (bundle != null) {
532             if (strippedLocale != null) {
533                 ResourceBundle parent = handleGetBundle(loadBase, base, strippedLocale, loader);
534                 if (parent != null) {
535                     bundle.setParent(parent);
536                 }
537             }
538             loaderCache.put(bundleName, bundle);
539             return bundle;
540         }
541 
542         if (strippedLocale != null && (loadBase || !strippedLocale.toString().isEmpty())) {
543             bundle = handleGetBundle(loadBase, base, strippedLocale, loader);
544             if (bundle != null) {
545                 loaderCache.put(bundleName, bundle);
546                 return bundle;
547             }
548         }
549         loaderCache.put(bundleName, loadBase ? MISSINGBASE : MISSING);
550         return null;
551     }
552 
getLoaderCache(Object cacheKey)553     private static Hashtable<String, ResourceBundle> getLoaderCache(Object cacheKey) {
554         synchronized (cache) {
555             Hashtable<String, ResourceBundle> loaderCache = cache.get(cacheKey);
556             if (loaderCache == null) {
557                 loaderCache = new Hashtable<String, ResourceBundle>();
558                 cache.put(cacheKey, loaderCache);
559             }
560             return loaderCache;
561         }
562     }
563 
564     /**
565      * Returns the named resource from this {@code ResourceBundle}, or null if the
566      * resource is not found.
567      *
568      * @param key
569      *            the name of the resource.
570      * @return the resource object.
571      */
handleGetObject(String key)572     protected abstract Object handleGetObject(String key);
573 
574     /**
575      * Sets the parent resource bundle of this {@code ResourceBundle}. The parent is
576      * searched for resources which are not found in this {@code ResourceBundle}.
577      *
578      * @param bundle
579      *            the parent {@code ResourceBundle}.
580      */
setParent(ResourceBundle bundle)581     protected void setParent(ResourceBundle bundle) {
582         parent = bundle;
583     }
584 
585     /**
586      * Returns a locale with the most-specific field removed, or null if this
587      * locale had an empty language, country and variant.
588      */
strip(Locale locale)589     private static Locale strip(Locale locale) {
590         String language = locale.getLanguage();
591         String country = locale.getCountry();
592         String variant = locale.getVariant();
593         if (!variant.isEmpty()) {
594             variant = "";
595         } else if (!country.isEmpty()) {
596             country = "";
597         } else if (!language.isEmpty()) {
598             language = "";
599         } else {
600             return null;
601         }
602         return new Locale(language, country, variant);
603     }
604 
setLocale(Locale locale)605     private void setLocale(Locale locale) {
606         this.locale = locale;
607     }
608 
clearCache()609     public static void clearCache() {
610         cache.remove(ClassLoader.getSystemClassLoader());
611     }
612 
clearCache(ClassLoader loader)613     public static void clearCache(ClassLoader loader) {
614         if (loader == null) {
615             throw new NullPointerException("loader == null");
616         }
617         cache.remove(loader);
618     }
619 
containsKey(String key)620     public boolean containsKey(String key) {
621         if (key == null) {
622             throw new NullPointerException("key == null");
623         }
624         return keySet().contains(key);
625     }
626 
keySet()627     public Set<String> keySet() {
628         Set<String> ret = new HashSet<String>();
629         Enumeration<String> keys = getKeys();
630         while (keys.hasMoreElements()) {
631             ret.add(keys.nextElement());
632         }
633         return ret;
634     }
635 
handleKeySet()636     protected Set<String> handleKeySet() {
637         Set<String> set = keySet();
638         Set<String> ret = new HashSet<String>();
639         for (String key : set) {
640             if (handleGetObject(key) != null) {
641                 ret.add(key);
642             }
643         }
644         return ret;
645     }
646 
647     private static class NoFallbackControl extends Control {
648 
649         static final Control NOFALLBACK_FORMAT_PROPERTIES_CONTROL = new NoFallbackControl(
650                 JAVAPROPERTIES);
651 
652         static final Control NOFALLBACK_FORMAT_CLASS_CONTROL = new NoFallbackControl(
653                 JAVACLASS);
654 
655         static final Control NOFALLBACK_FORMAT_DEFAULT_CONTROL = new NoFallbackControl(
656                 listDefault);
657 
NoFallbackControl(String format)658         public NoFallbackControl(String format) {
659             listClass = new ArrayList<String>();
660             listClass.add(format);
661             super.format = Collections.unmodifiableList(listClass);
662         }
663 
NoFallbackControl(List<String> list)664         public NoFallbackControl(List<String> list) {
665             super.format = list;
666         }
667 
668         @Override
getFallbackLocale(String baseName, Locale locale)669         public Locale getFallbackLocale(String baseName, Locale locale) {
670             if (baseName == null) {
671                 throw new NullPointerException("baseName == null");
672             } else if (locale == null) {
673                 throw new NullPointerException("locale == null");
674             }
675             return null;
676         }
677     }
678 
679     private static class SimpleControl extends Control {
SimpleControl(String format)680         public SimpleControl(String format) {
681             listClass = new ArrayList<String>();
682             listClass.add(format);
683             super.format = Collections.unmodifiableList(listClass);
684         }
685     }
686 
687     /**
688      * ResourceBundle.Control is a static utility class defines ResourceBundle
689      * load access methods, its default access order is as the same as before.
690      * However users can implement their own control.
691      *
692      * @since 1.6
693      */
694     public static class Control {
695         static List<String> listDefault = new ArrayList<String>();
696 
697         static List<String> listClass = new ArrayList<String>();
698 
699         static List<String> listProperties = new ArrayList<String>();
700 
701         static String JAVACLASS = "java.class";
702 
703         static String JAVAPROPERTIES = "java.properties";
704 
705         static {
706             listDefault.add(JAVACLASS);
707             listDefault.add(JAVAPROPERTIES);
708             listClass.add(JAVACLASS);
709             listProperties.add(JAVAPROPERTIES);
710         }
711 
712         /**
713          * a list defines default format
714          */
715         public static final List<String> FORMAT_DEFAULT = Collections
716                 .unmodifiableList(listDefault);
717 
718         /**
719          * a list defines java class format
720          */
721         public static final List<String> FORMAT_CLASS = Collections
722                 .unmodifiableList(listClass);
723 
724         /**
725          * a list defines property format
726          */
727         public static final List<String> FORMAT_PROPERTIES = Collections
728                 .unmodifiableList(listProperties);
729 
730         /**
731          * a constant that indicates cache will not be used.
732          */
733         public static final long TTL_DONT_CACHE = -1L;
734 
735         /**
736          * a constant that indicates cache will not be expired.
737          */
738         public static final long TTL_NO_EXPIRATION_CONTROL = -2L;
739 
740         private static final Control FORMAT_PROPERTIES_CONTROL = new SimpleControl(
741                 JAVAPROPERTIES);
742 
743         private static final Control FORMAT_CLASS_CONTROL = new SimpleControl(
744                 JAVACLASS);
745 
746         private static final Control FORMAT_DEFAULT_CONTROL = new Control();
747 
748         List<String> format;
749 
750         /**
751          * default constructor
752          *
753          */
Control()754         protected Control() {
755             listClass = new ArrayList<String>();
756             listClass.add(JAVACLASS);
757             listClass.add(JAVAPROPERTIES);
758             format = Collections.unmodifiableList(listClass);
759         }
760 
761         /**
762          * Returns a control according to {@code formats}.
763          */
getControl(List<String> formats)764         public static Control getControl(List<String> formats) {
765             switch (formats.size()) {
766             case 1:
767                 if (formats.contains(JAVACLASS)) {
768                     return FORMAT_CLASS_CONTROL;
769                 }
770                 if (formats.contains(JAVAPROPERTIES)) {
771                     return FORMAT_PROPERTIES_CONTROL;
772                 }
773                 break;
774             case 2:
775                 if (formats.equals(FORMAT_DEFAULT)) {
776                     return FORMAT_DEFAULT_CONTROL;
777                 }
778                 break;
779             }
780             throw new IllegalArgumentException();
781         }
782 
783         /**
784          * Returns a control according to {@code formats} whose fallback
785          * locale is null.
786          */
getNoFallbackControl(List<String> formats)787         public static Control getNoFallbackControl(List<String> formats) {
788             switch (formats.size()) {
789             case 1:
790                 if (formats.contains(JAVACLASS)) {
791                     return NoFallbackControl.NOFALLBACK_FORMAT_CLASS_CONTROL;
792                 }
793                 if (formats.contains(JAVAPROPERTIES)) {
794                     return NoFallbackControl.NOFALLBACK_FORMAT_PROPERTIES_CONTROL;
795                 }
796                 break;
797             case 2:
798                 if (formats.equals(FORMAT_DEFAULT)) {
799                     return NoFallbackControl.NOFALLBACK_FORMAT_DEFAULT_CONTROL;
800                 }
801                 break;
802             }
803             throw new IllegalArgumentException();
804         }
805 
806         /**
807          * Returns a list of candidate locales according to {@code baseName} in
808          * {@code locale}.
809          */
getCandidateLocales(String baseName, Locale locale)810         public List<Locale> getCandidateLocales(String baseName, Locale locale) {
811             if (baseName == null) {
812                 throw new NullPointerException("baseName == null");
813             } else if (locale == null) {
814                 throw new NullPointerException("locale == null");
815             }
816             List<Locale> retList = new ArrayList<Locale>();
817             String language = locale.getLanguage();
818             String country = locale.getCountry();
819             String variant = locale.getVariant();
820             if (!EMPTY_STRING.equals(variant)) {
821                 retList.add(new Locale(language, country, variant));
822             }
823             if (!EMPTY_STRING.equals(country)) {
824                 retList.add(new Locale(language, country));
825             }
826             if (!EMPTY_STRING.equals(language)) {
827                 retList.add(new Locale(language));
828             }
829             retList.add(Locale.ROOT);
830             return retList;
831         }
832 
833         /**
834          * Returns a list of strings of formats according to {@code baseName}.
835          */
getFormats(String baseName)836         public List<String> getFormats(String baseName) {
837             if (baseName == null) {
838                 throw new NullPointerException("baseName == null");
839             }
840             return format;
841         }
842 
843         /**
844          * Returns the fallback locale for {@code baseName} in {@code locale}.
845          */
getFallbackLocale(String baseName, Locale locale)846         public Locale getFallbackLocale(String baseName, Locale locale) {
847             if (baseName == null) {
848                 throw new NullPointerException("baseName == null");
849             } else if (locale == null) {
850                 throw new NullPointerException("locale == null");
851             }
852             if (Locale.getDefault() != locale) {
853                 return Locale.getDefault();
854             }
855             return null;
856         }
857 
858         /**
859          * Returns a new ResourceBundle.
860          *
861          * @param baseName
862          *            the base name to use
863          * @param locale
864          *            the given locale
865          * @param format
866          *            the format, default is "java.class" or "java.properties"
867          * @param loader
868          *            the classloader to use
869          * @param reload
870          *            whether to reload the resource
871          * @return a new ResourceBundle according to the give parameters
872          * @throws IllegalAccessException
873          *             if we can not access resources
874          * @throws InstantiationException
875          *             if we can not instantiate a resource class
876          * @throws IOException
877          *             if other I/O exception happens
878          */
newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)879         public ResourceBundle newBundle(String baseName, Locale locale,
880                 String format, ClassLoader loader, boolean reload)
881                 throws IllegalAccessException, InstantiationException,
882                 IOException {
883             if (format == null) {
884                 throw new NullPointerException("format == null");
885             } else if (loader == null) {
886                 throw new NullPointerException("loader == null");
887             }
888             final String bundleName = toBundleName(baseName, locale);
889             final ClassLoader clsloader = loader;
890             ResourceBundle ret;
891             if (format.equals(JAVACLASS)) {
892                 Class<?> cls = null;
893                 try {
894                     cls = clsloader.loadClass(bundleName);
895                 } catch (Exception e) {
896                 } catch (NoClassDefFoundError e) {
897                 }
898                 if (cls == null) {
899                     return null;
900                 }
901                 try {
902                     ResourceBundle bundle = (ResourceBundle) cls.newInstance();
903                     bundle.setLocale(locale);
904                     return bundle;
905                 } catch (NullPointerException e) {
906                     return null;
907                 }
908             }
909             if (format.equals(JAVAPROPERTIES)) {
910                 InputStream streams = null;
911                 final String resourceName = toResourceName(bundleName, "properties");
912                 if (reload) {
913                     URL url = null;
914                     try {
915                         url = loader.getResource(resourceName);
916                     } catch (NullPointerException e) {
917                         // do nothing
918                     }
919                     if (url != null) {
920                         URLConnection con = url.openConnection();
921                         con.setUseCaches(false);
922                         streams = con.getInputStream();
923                     }
924                 } else {
925                     try {
926                         streams = clsloader.getResourceAsStream(resourceName);
927                     } catch (NullPointerException e) {
928                         // do nothing
929                     }
930                 }
931                 if (streams != null) {
932                     try {
933                         ret = new PropertyResourceBundle(new InputStreamReader(streams));
934                         ret.setLocale(locale);
935                         streams.close();
936                     } catch (IOException e) {
937                         return null;
938                     }
939                     return ret;
940                 }
941                 return null;
942             }
943             throw new IllegalArgumentException();
944         }
945 
946         /**
947          * Returns the time to live of the ResourceBundle {@code baseName} in {@code locale},
948          * default is TTL_NO_EXPIRATION_CONTROL.
949          */
getTimeToLive(String baseName, Locale locale)950         public long getTimeToLive(String baseName, Locale locale) {
951             if (baseName == null) {
952                 throw new NullPointerException("baseName == null");
953             } else if (locale == null) {
954                 throw new NullPointerException("locale == null");
955             }
956             return TTL_NO_EXPIRATION_CONTROL;
957         }
958 
959         /**
960          * Returns true if the ResourceBundle needs to reload.
961          *
962          * @param baseName
963          *            the base name of the ResourceBundle
964          * @param locale
965          *            the locale of the ResourceBundle
966          * @param format
967          *            the format to load
968          * @param loader
969          *            the ClassLoader to load resource
970          * @param bundle
971          *            the ResourceBundle
972          * @param loadTime
973          *            the expired time
974          * @return if the ResourceBundle needs to reload
975          */
needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime)976         public boolean needsReload(String baseName, Locale locale,
977                 String format, ClassLoader loader, ResourceBundle bundle,
978                 long loadTime) {
979             if (bundle == null) {
980                 // FIXME what's the use of bundle?
981                 throw new NullPointerException("bundle == null");
982             }
983             String bundleName = toBundleName(baseName, locale);
984             String suffix = format;
985             if (format.equals(JAVACLASS)) {
986                 suffix = "class";
987             }
988             if (format.equals(JAVAPROPERTIES)) {
989                 suffix = "properties";
990             }
991             String urlname = toResourceName(bundleName, suffix);
992             URL url = loader.getResource(urlname);
993             if (url != null) {
994                 String fileName = url.getFile();
995                 long lastModified = new File(fileName).lastModified();
996                 if (lastModified > loadTime) {
997                     return true;
998                 }
999             }
1000             return false;
1001         }
1002 
1003         /**
1004          * a utility method to answer the name of a resource bundle according to
1005          * the given base name and locale
1006          *
1007          * @param baseName
1008          *            the given base name
1009          * @param locale
1010          *            the locale to use
1011          * @return the name of a resource bundle according to the given base
1012          *         name and locale
1013          */
toBundleName(String baseName, Locale locale)1014         public String toBundleName(String baseName, Locale locale) {
1015             final String emptyString = EMPTY_STRING;
1016             final String preString = UNDER_SCORE;
1017             final String underline = UNDER_SCORE;
1018             if (baseName == null) {
1019                 throw new NullPointerException("baseName == null");
1020             }
1021             StringBuilder ret = new StringBuilder();
1022             StringBuilder prefix = new StringBuilder();
1023             ret.append(baseName);
1024             if (!locale.getLanguage().equals(emptyString)) {
1025                 ret.append(underline);
1026                 ret.append(locale.getLanguage());
1027             } else {
1028                 prefix.append(preString);
1029             }
1030             if (!locale.getCountry().equals(emptyString)) {
1031                 ret.append((CharSequence) prefix);
1032                 ret.append(underline);
1033                 ret.append(locale.getCountry());
1034                 prefix = new StringBuilder();
1035             } else {
1036                 prefix.append(preString);
1037             }
1038             if (!locale.getVariant().equals(emptyString)) {
1039                 ret.append((CharSequence) prefix);
1040                 ret.append(underline);
1041                 ret.append(locale.getVariant());
1042             }
1043             return ret.toString();
1044         }
1045 
1046         /**
1047          * a utility method to answer the name of a resource according to the
1048          * given bundleName and suffix
1049          *
1050          * @param bundleName
1051          *            the given bundle name
1052          * @param suffix
1053          *            the suffix
1054          * @return the name of a resource according to the given bundleName and
1055          *         suffix
1056          */
toResourceName(String bundleName, String suffix)1057         public final String toResourceName(String bundleName, String suffix) {
1058             if (suffix == null) {
1059                 throw new NullPointerException("suffix == null");
1060             }
1061             StringBuilder ret = new StringBuilder(bundleName.replace('.', '/'));
1062             ret.append('.');
1063             ret.append(suffix);
1064             return ret.toString();
1065         }
1066     }
1067 }
1068