OSDN Git Service

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