2
* Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
27
* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
28
* (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
30
* The original version of this source code and documentation
31
* is copyrighted and owned by Taligent, Inc., a wholly-owned
32
* subsidiary of IBM. These materials are provided under terms
33
* of a License Agreement between Taligent and Sun. This technology
34
* is protected by multiple US and International patents.
36
* This notice and attribution to Taligent may not be removed.
37
* Taligent is a registered trademark of Taligent, Inc.
43
import java.io.IOException;
44
import java.io.InputStream;
45
import java.lang.ref.ReferenceQueue;
46
import java.lang.ref.SoftReference;
47
import java.lang.ref.WeakReference;
48
import java.net.JarURLConnection;
50
import java.net.URLConnection;
51
import java.security.AccessController;
52
import java.security.PrivilegedAction;
53
import java.security.PrivilegedActionException;
54
import java.security.PrivilegedExceptionAction;
55
import java.util.concurrent.ConcurrentHashMap;
56
import java.util.concurrent.ConcurrentMap;
57
import java.util.jar.JarEntry;
58
import sun.reflect.Reflection;
59
import sun.util.locale.BaseLocale;
60
import sun.util.locale.LocaleObjectCache;
65
* Resource bundles contain locale-specific objects. When your program needs a
66
* locale-specific resource, a <code>String</code> for example, your program can
67
* load it from the resource bundle that is appropriate for the current user's
68
* locale. In this way, you can write program code that is largely independent
69
* of the user's locale isolating most, if not all, of the locale-specific
70
* information in resource bundles.
73
* This allows you to write programs that can:
75
* <LI> be easily localized, or translated, into different languages
76
* <LI> handle multiple locales at once
77
* <LI> be easily modified later to support even more locales
81
* Resource bundles belong to families whose members share a common base
82
* name, but whose names also have additional components that identify
83
* their locales. For example, the base name of a family of resource
84
* bundles might be "MyResources". The family should have a default
85
* resource bundle which simply has the same name as its family -
86
* "MyResources" - and will be used as the bundle of last resort if a
87
* specific locale is not supported. The family can then provide as
88
* many locale-specific members as needed, for example a German one
89
* named "MyResources_de".
92
* Each resource bundle in a family contains the same items, but the items have
93
* been translated for the locale represented by that resource bundle.
94
* For example, both "MyResources" and "MyResources_de" may have a
95
* <code>String</code> that's used on a button for canceling operations.
96
* In "MyResources" the <code>String</code> may contain "Cancel" and in
97
* "MyResources_de" it may contain "Abbrechen".
100
* If there are different resources for different countries, you
101
* can make specializations: for example, "MyResources_de_CH" contains objects for
102
* the German language (de) in Switzerland (CH). If you want to only
103
* modify some of the resources
104
* in the specialization, you can do so.
107
* When your program needs a locale-specific object, it loads
108
* the <code>ResourceBundle</code> class using the
109
* {@link #getBundle(java.lang.String, java.util.Locale) getBundle}
113
* ResourceBundle myResources =
114
* ResourceBundle.getBundle("MyResources", currentLocale);
119
* Resource bundles contain key/value pairs. The keys uniquely
120
* identify a locale-specific object in the bundle. Here's an
121
* example of a <code>ListResourceBundle</code> that contains
122
* two key/value pairs:
125
* public class MyResources extends ListResourceBundle {
126
* protected Object[][] getContents() {
127
* return new Object[][] {
128
* // LOCALIZE THE SECOND STRING OF EACH ARRAY (e.g., "OK")
130
* {"CancelKey", "Cancel"},
131
* // END OF MATERIAL TO LOCALIZE
137
* Keys are always <code>String</code>s.
138
* In this example, the keys are "OkKey" and "CancelKey".
139
* In the above example, the values
140
* are also <code>String</code>s--"OK" and "Cancel"--but
141
* they don't have to be. The values can be any type of object.
144
* You retrieve an object from resource bundle using the appropriate
145
* getter method. Because "OkKey" and "CancelKey"
146
* are both strings, you would use <code>getString</code> to retrieve them:
149
* button1 = new Button(myResources.getString("OkKey"));
150
* button2 = new Button(myResources.getString("CancelKey"));
153
* The getter methods all require the key as an argument and return
154
* the object if found. If the object is not found, the getter method
155
* throws a <code>MissingResourceException</code>.
158
* Besides <code>getString</code>, <code>ResourceBundle</code> also provides
159
* a method for getting string arrays, <code>getStringArray</code>,
160
* as well as a generic <code>getObject</code> method for any other
161
* type of object. When using <code>getObject</code>, you'll
162
* have to cast the result to the appropriate type. For example:
165
* int[] myIntegers = (int[]) myResources.getObject("intList");
170
* The Java Platform provides two subclasses of <code>ResourceBundle</code>,
171
* <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>,
172
* that provide a fairly simple way to create resources.
173
* As you saw briefly in a previous example, <code>ListResourceBundle</code>
174
* manages its resource as a list of key/value pairs.
175
* <code>PropertyResourceBundle</code> uses a properties file to manage
179
* If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code>
180
* do not suit your needs, you can write your own <code>ResourceBundle</code>
181
* subclass. Your subclasses must override two methods: <code>handleGetObject</code>
182
* and <code>getKeys()</code>.
184
* <h4>ResourceBundle.Control</h4>
186
* The {@link ResourceBundle.Control} class provides information necessary
187
* to perform the bundle loading process by the <code>getBundle</code>
188
* factory methods that take a <code>ResourceBundle.Control</code>
189
* instance. You can implement your own subclass in order to enable
190
* non-standard resource bundle formats, change the search strategy, or
191
* define caching parameters. Refer to the descriptions of the class and the
192
* {@link #getBundle(String, Locale, ClassLoader, Control) getBundle}
193
* factory method for details.
195
* <h4>Cache Management</h4>
197
* Resource bundle instances created by the <code>getBundle</code> factory
198
* methods are cached by default, and the factory methods return the same
199
* resource bundle instance multiple times if it has been
200
* cached. <code>getBundle</code> clients may clear the cache, manage the
201
* lifetime of cached resource bundle instances using time-to-live values,
202
* or specify not to cache resource bundle instances. Refer to the
203
* descriptions of the {@linkplain #getBundle(String, Locale, ClassLoader,
204
* Control) <code>getBundle</code> factory method}, {@link
205
* #clearCache(ClassLoader) clearCache}, {@link
206
* Control#getTimeToLive(String, Locale)
207
* ResourceBundle.Control.getTimeToLive}, and {@link
208
* Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle,
209
* long) ResourceBundle.Control.needsReload} for details.
213
* The following is a very simple example of a <code>ResourceBundle</code>
214
* subclass, <code>MyResources</code>, that manages two resources (for a larger number of
215
* resources you would probably use a <code>Map</code>).
216
* Notice that you don't need to supply a value if
217
* a "parent-level" <code>ResourceBundle</code> handles the same
218
* key with the same value (as for the okKey below).
221
* // default (English language, United States)
222
* public class MyResources extends ResourceBundle {
223
* public Object handleGetObject(String key) {
224
* if (key.equals("okKey")) return "Ok";
225
* if (key.equals("cancelKey")) return "Cancel";
229
* public Enumeration<String> getKeys() {
230
* return Collections.enumeration(keySet());
233
* // Overrides handleKeySet() so that the getKeys() implementation
234
* // can rely on the keySet() value.
235
* protected Set<String> handleKeySet() {
236
* return new HashSet<String>(Arrays.asList("okKey", "cancelKey"));
241
* public class MyResources_de extends MyResources {
242
* public Object handleGetObject(String key) {
243
* // don't need okKey, since parent level handles it.
244
* if (key.equals("cancelKey")) return "Abbrechen";
248
* protected Set<String> handleKeySet() {
249
* return new HashSet<String>(Arrays.asList("cancelKey"));
254
* You do not have to restrict yourself to using a single family of
255
* <code>ResourceBundle</code>s. For example, you could have a set of bundles for
256
* exception messages, <code>ExceptionResources</code>
257
* (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...),
258
* and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>,
259
* <code>WidgetResources_de</code>, ...); breaking up the resources however you like.
261
* @see ListResourceBundle
262
* @see PropertyResourceBundle
263
* @see MissingResourceException
266
public abstract class ResourceBundle {
268
/** initial size of the bundle cache */
269
private static final int INITIAL_CACHE_SIZE = 32;
271
/** constant indicating that no resource bundle exists */
272
private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
273
public Enumeration<String> getKeys() { return null; }
274
protected Object handleGetObject(String key) { return null; }
275
public String toString() { return "NONEXISTENT_BUNDLE"; }
280
* The cache is a map from cache keys (with bundle base name, locale, and
281
* class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a
284
* The cache is a ConcurrentMap, allowing the cache to be searched
285
* concurrently by multiple threads. This will also allow the cache keys
286
* to be reclaimed along with the ClassLoaders they reference.
288
* This variable would be better named "cache", but we keep the old
289
* name for compatibility with some workarounds for bug 4212439.
291
private static final ConcurrentMap<CacheKey, BundleReference> cacheList
292
= new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
295
* Queue for reference objects referring to class loaders or bundles.
297
private static final ReferenceQueue referenceQueue = new ReferenceQueue();
300
* The parent bundle of this bundle.
301
* The parent bundle is searched by {@link #getObject getObject}
302
* when this bundle does not contain a particular resource.
304
protected ResourceBundle parent = null;
307
* The locale for this bundle.
309
private Locale locale = null;
312
* The base bundle name for this bundle.
317
* The flag indicating this bundle has expired in the cache.
319
private volatile boolean expired;
322
* The back link to the cache key. null if this bundle isn't in
323
* the cache (yet) or has expired.
325
private volatile CacheKey cacheKey;
328
* A Set of the keys contained only in this ResourceBundle.
330
private volatile Set<String> keySet;
333
* Sole constructor. (For invocation by subclass constructors, typically
336
public ResourceBundle() {
340
* Gets a string for the given key from this resource bundle or one of its parents.
341
* Calling this method is equivalent to calling
343
* <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
346
* @param key the key for the desired string
347
* @exception NullPointerException if <code>key</code> is <code>null</code>
348
* @exception MissingResourceException if no object for the given key can be found
349
* @exception ClassCastException if the object found for the given key is not a string
350
* @return the string for the given key
352
public final String getString(String key) {
353
return (String) getObject(key);
357
* Gets a string array for the given key from this resource bundle or one of its parents.
358
* Calling this method is equivalent to calling
360
* <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
363
* @param key the key for the desired string array
364
* @exception NullPointerException if <code>key</code> is <code>null</code>
365
* @exception MissingResourceException if no object for the given key can be found
366
* @exception ClassCastException if the object found for the given key is not a string array
367
* @return the string array for the given key
369
public final String[] getStringArray(String key) {
370
return (String[]) getObject(key);
374
* Gets an object for the given key from this resource bundle or one of its parents.
375
* This method first tries to obtain the object from this resource bundle using
376
* {@link #handleGetObject(java.lang.String) handleGetObject}.
377
* If not successful, and the parent resource bundle is not null,
378
* it calls the parent's <code>getObject</code> method.
379
* If still not successful, it throws a MissingResourceException.
381
* @param key the key for the desired object
382
* @exception NullPointerException if <code>key</code> is <code>null</code>
383
* @exception MissingResourceException if no object for the given key can be found
384
* @return the object for the given key
386
public final Object getObject(String key) {
387
Object obj = handleGetObject(key);
389
if (parent != null) {
390
obj = parent.getObject(key);
393
throw new MissingResourceException("Can't find resource for bundle "
394
+this.getClass().getName()
396
this.getClass().getName(),
403
* Returns the locale of this resource bundle. This method can be used after a
404
* call to getBundle() to determine whether the resource bundle returned really
405
* corresponds to the requested locale or is a fallback.
407
* @return the locale of this resource bundle
409
public Locale getLocale() {
414
* Automatic determination of the ClassLoader to be used to load
415
* resources on behalf of the client. N.B. The client is getLoader's
418
private static ClassLoader getLoader(Class c) {
419
ClassLoader cl = (c == null) ? null : c.getClassLoader();
421
// When the caller's loader is the boot class loader, cl is null
422
// here. In that case, ClassLoader.getSystemClassLoader() may
423
// return the same class loader that the application is
424
// using. We therefore use a wrapper ClassLoader to create a
425
// separate scope for bundles loaded on behalf of the Java
426
// runtime so that these bundles cannot be returned from the
427
// cache to the application (5048280).
428
cl = RBClassLoader.INSTANCE;
434
* A wrapper of ClassLoader.getSystemClassLoader().
436
private static class RBClassLoader extends ClassLoader {
437
private static final RBClassLoader INSTANCE = AccessController.doPrivileged(
438
new PrivilegedAction<RBClassLoader>() {
439
public RBClassLoader run() {
440
return new RBClassLoader();
443
private static final ClassLoader loader = ClassLoader.getSystemClassLoader();
445
private RBClassLoader() {
447
public Class<?> loadClass(String name) throws ClassNotFoundException {
448
if (loader != null) {
449
return loader.loadClass(name);
451
return Class.forName(name);
453
public URL getResource(String name) {
454
if (loader != null) {
455
return loader.getResource(name);
457
return ClassLoader.getSystemResource(name);
459
public InputStream getResourceAsStream(String name) {
460
if (loader != null) {
461
return loader.getResourceAsStream(name);
463
return ClassLoader.getSystemResourceAsStream(name);
468
* Sets the parent bundle of this bundle.
469
* The parent bundle is searched by {@link #getObject getObject}
470
* when this bundle does not contain a particular resource.
472
* @param parent this bundle's parent bundle.
474
protected void setParent(ResourceBundle parent) {
475
assert parent != NONEXISTENT_BUNDLE;
476
this.parent = parent;
480
* Key used for cached resource bundles. The key checks the base
481
* name, the locale, and the class loader to determine if the
482
* resource is a match to the requested one. The loader may be
483
* null, but the base name and the locale must have a non-null
486
private static final class CacheKey implements Cloneable {
487
// These three are the actual keys for lookup in Map.
489
private Locale locale;
490
private LoaderReference loaderRef;
492
// bundle format which is necessary for calling
493
// Control.needsReload().
494
private String format;
496
// These time values are in CacheKey so that NONEXISTENT_BUNDLE
497
// doesn't need to be cloned for caching.
499
// The time when the bundle has been loaded
500
private volatile long loadTime;
502
// The time when the bundle expires in the cache, or either
503
// Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL.
504
private volatile long expirationTime;
506
// Placeholder for an error report by a Throwable
507
private Throwable cause;
509
// Hash code value cache to avoid recalculating the hash code
511
private int hashCodeCache;
513
CacheKey(String baseName, Locale locale, ClassLoader loader) {
514
this.name = baseName;
515
this.locale = locale;
516
if (loader == null) {
517
this.loaderRef = null;
519
loaderRef = new LoaderReference(loader, referenceQueue, this);
528
CacheKey setName(String baseName) {
529
if (!this.name.equals(baseName)) {
530
this.name = baseName;
540
CacheKey setLocale(Locale locale) {
541
if (!this.locale.equals(locale)) {
542
this.locale = locale;
548
ClassLoader getLoader() {
549
return (loaderRef != null) ? loaderRef.get() : null;
552
public boolean equals(Object other) {
557
final CacheKey otherEntry = (CacheKey)other;
558
//quick check to see if they are not equal
559
if (hashCodeCache != otherEntry.hashCodeCache) {
562
//are the names the same?
563
if (!name.equals(otherEntry.name)) {
566
// are the locales the same?
567
if (!locale.equals(otherEntry.locale)) {
570
//are refs (both non-null) or (both null)?
571
if (loaderRef == null) {
572
return otherEntry.loaderRef == null;
574
ClassLoader loader = loaderRef.get();
575
return (otherEntry.loaderRef != null)
576
// with a null reference we can no longer find
577
// out which class loader was referenced; so
578
// treat it as unequal
580
&& (loader == otherEntry.loaderRef.get());
581
} catch (NullPointerException e) {
582
} catch (ClassCastException e) {
587
public int hashCode() {
588
return hashCodeCache;
591
private void calculateHashCode() {
592
hashCodeCache = name.hashCode() << 3;
593
hashCodeCache ^= locale.hashCode();
594
ClassLoader loader = getLoader();
595
if (loader != null) {
596
hashCodeCache ^= loader.hashCode();
600
public Object clone() {
602
CacheKey clone = (CacheKey) super.clone();
603
if (loaderRef != null) {
604
clone.loaderRef = new LoaderReference(loaderRef.get(),
605
referenceQueue, clone);
607
// Clear the reference to a Throwable
610
} catch (CloneNotSupportedException e) {
611
//this should never happen
612
throw new InternalError();
620
void setFormat(String format) {
621
this.format = format;
624
private void setCause(Throwable cause) {
625
if (this.cause == null) {
628
// Override the cause if the previous one is
629
// ClassNotFoundException.
630
if (this.cause instanceof ClassNotFoundException) {
636
private Throwable getCause() {
640
public String toString() {
641
String l = locale.toString();
642
if (l.length() == 0) {
643
if (locale.getVariant().length() != 0) {
644
l = "__" + locale.getVariant();
649
return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
650
+ "(format=" + format + ")]";
655
* The common interface to get a CacheKey in LoaderReference and
658
private static interface CacheKeyReference {
659
public CacheKey getCacheKey();
663
* References to class loaders are weak references, so that they can be
664
* garbage collected when nobody else is using them. The ResourceBundle
665
* class has no reason to keep class loaders alive.
667
private static final class LoaderReference extends WeakReference<ClassLoader>
668
implements CacheKeyReference {
669
private CacheKey cacheKey;
671
LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) {
676
public CacheKey getCacheKey() {
682
* References to bundles are soft references so that they can be garbage
683
* collected when they have no hard references.
685
private static final class BundleReference extends SoftReference<ResourceBundle>
686
implements CacheKeyReference {
687
private CacheKey cacheKey;
689
BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) {
694
public CacheKey getCacheKey() {
700
* Gets a resource bundle using the specified base name, the default locale,
701
* and the caller's class loader. Calling this method is equivalent to calling
703
* <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
705
* except that <code>getClassLoader()</code> is run with the security
706
* privileges of <code>ResourceBundle</code>.
707
* See {@link #getBundle(String, Locale, ClassLoader) getBundle}
708
* for a complete description of the search and instantiation strategy.
710
* @param baseName the base name of the resource bundle, a fully qualified class name
711
* @exception java.lang.NullPointerException
712
* if <code>baseName</code> is <code>null</code>
713
* @exception MissingResourceException
714
* if no resource bundle for the specified base name can be found
715
* @return a resource bundle for the given base name and the default locale
717
@ikvm.internal.HasCallerID
718
public static final ResourceBundle getBundle(String baseName)
720
return getBundleImpl(baseName, Locale.getDefault(),
721
/* must determine loader here, else we break stack invariant */
722
getLoader(Reflection.getCallerClass(2)),
727
* Returns a resource bundle using the specified base name, the
728
* default locale and the specified control. Calling this method
729
* is equivalent to calling
731
* getBundle(baseName, Locale.getDefault(),
732
* this.getClass().getClassLoader(), control),
734
* except that <code>getClassLoader()</code> is run with the security
735
* privileges of <code>ResourceBundle</code>. See {@link
736
* #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
737
* complete description of the resource bundle loading process with a
738
* <code>ResourceBundle.Control</code>.
741
* the base name of the resource bundle, a fully qualified class
744
* the control which gives information for the resource bundle
746
* @return a resource bundle for the given base name and the default
748
* @exception NullPointerException
749
* if <code>baseName</code> or <code>control</code> is
751
* @exception MissingResourceException
752
* if no resource bundle for the specified base name can be found
753
* @exception IllegalArgumentException
754
* if the given <code>control</code> doesn't perform properly
755
* (e.g., <code>control.getCandidateLocales</code> returns null.)
756
* Note that validation of <code>control</code> is performed as
760
@ikvm.internal.HasCallerID
761
public static final ResourceBundle getBundle(String baseName,
763
return getBundleImpl(baseName, Locale.getDefault(),
764
/* must determine loader here, else we break stack invariant */
765
getLoader(Reflection.getCallerClass(2)),
770
* Gets a resource bundle using the specified base name and locale,
771
* and the caller's class loader. Calling this method is equivalent to calling
773
* <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
775
* except that <code>getClassLoader()</code> is run with the security
776
* privileges of <code>ResourceBundle</code>.
777
* See {@link #getBundle(String, Locale, ClassLoader) getBundle}
778
* for a complete description of the search and instantiation strategy.
781
* the base name of the resource bundle, a fully qualified class name
783
* the locale for which a resource bundle is desired
784
* @exception NullPointerException
785
* if <code>baseName</code> or <code>locale</code> is <code>null</code>
786
* @exception MissingResourceException
787
* if no resource bundle for the specified base name can be found
788
* @return a resource bundle for the given base name and locale
790
@ikvm.internal.HasCallerID
791
public static final ResourceBundle getBundle(String baseName,
794
return getBundleImpl(baseName, locale,
795
/* must determine loader here, else we break stack invariant */
796
getLoader(Reflection.getCallerClass(2)),
801
* Returns a resource bundle using the specified base name, target
802
* locale and control, and the caller's class loader. Calling this
803
* method is equivalent to calling
805
* getBundle(baseName, targetLocale, this.getClass().getClassLoader(),
808
* except that <code>getClassLoader()</code> is run with the security
809
* privileges of <code>ResourceBundle</code>. See {@link
810
* #getBundle(String, Locale, ClassLoader, Control) getBundle} for the
811
* complete description of the resource bundle loading process with a
812
* <code>ResourceBundle.Control</code>.
815
* the base name of the resource bundle, a fully qualified
817
* @param targetLocale
818
* the locale for which a resource bundle is desired
820
* the control which gives information for the resource
821
* bundle loading process
822
* @return a resource bundle for the given base name and a
823
* <code>Locale</code> in <code>locales</code>
824
* @exception NullPointerException
825
* if <code>baseName</code>, <code>locales</code> or
826
* <code>control</code> is <code>null</code>
827
* @exception MissingResourceException
828
* if no resource bundle for the specified base name in any
829
* of the <code>locales</code> can be found.
830
* @exception IllegalArgumentException
831
* if the given <code>control</code> doesn't perform properly
832
* (e.g., <code>control.getCandidateLocales</code> returns null.)
833
* Note that validation of <code>control</code> is performed as
837
@ikvm.internal.HasCallerID
838
public static final ResourceBundle getBundle(String baseName, Locale targetLocale,
840
return getBundleImpl(baseName, targetLocale,
841
/* must determine loader here, else we break stack invariant */
842
getLoader(Reflection.getCallerClass(2)),
847
* Gets a resource bundle using the specified base name, locale, and class
850
* <p><a name="default_behavior"/>This method behaves the same as calling
851
* {@link #getBundle(String, Locale, ClassLoader, Control)} passing a
852
* default instance of {@link Control}. The following describes this behavior.
854
* <p><code>getBundle</code> uses the base name, the specified locale, and
855
* the default locale (obtained from {@link java.util.Locale#getDefault()
856
* Locale.getDefault}) to generate a sequence of <a
857
* name="candidates"><em>candidate bundle names</em></a>. If the specified
858
* locale's language, script, country, and variant are all empty strings,
859
* then the base name is the only candidate bundle name. Otherwise, a list
860
* of candidate locales is generated from the attribute values of the
861
* specified locale (language, script, country and variant) and appended to
862
* the base name. Typically, this will look like the following:
865
* baseName + "_" + language + "_" + script + "_" + country + "_" + variant
866
* baseName + "_" + language + "_" + script + "_" + country
867
* baseName + "_" + language + "_" + script
868
* baseName + "_" + language + "_" + country + "_" + variant
869
* baseName + "_" + language + "_" + country
870
* baseName + "_" + language
873
* <p>Candidate bundle names where the final component is an empty string
874
* are omitted, along with the underscore. For example, if country is an
875
* empty string, the second and the fifth candidate bundle names above
876
* would be omitted. Also, if script is an empty string, the candidate names
877
* including script are omitted. For example, a locale with language "de"
878
* and variant "JAVA" will produce candidate names with base name
879
* "MyResource" below.
882
* MyResource_de__JAVA
886
* In the case that the variant contains one or more underscores ('_'), a
887
* sequence of bundle names generated by truncating the last underscore and
888
* the part following it is inserted after a candidate bundle name with the
889
* original variant. For example, for a locale with language "en", script
890
* "Latn, country "US" and variant "WINDOWS_VISTA", and bundle base name
891
* "MyResource", the list of candidate bundle names below is generated:
894
* MyResource_en_Latn_US_WINDOWS_VISTA
895
* MyResource_en_Latn_US_WINDOWS
896
* MyResource_en_Latn_US
898
* MyResource_en_US_WINDOWS_VISTA
899
* MyResource_en_US_WINDOWS
904
* <blockquote><b>Note:</b> For some <code>Locale</code>s, the list of
905
* candidate bundle names contains extra names, or the order of bundle names
906
* is slightly modified. See the description of the default implementation
907
* of {@link Control#getCandidateLocales(String, Locale)
908
* getCandidateLocales} for details.</blockquote>
910
* <p><code>getBundle</code> then iterates over the candidate bundle names
911
* to find the first one for which it can <em>instantiate</em> an actual
912
* resource bundle. It uses the default controls' {@link Control#getFormats
913
* getFormats} method, which generates two bundle names for each generated
914
* name, the first a class name and the second a properties file name. For
915
* each candidate bundle name, it attempts to create a resource bundle:
917
* <ul><li>First, it attempts to load a class using the generated class name.
918
* If such a class can be found and loaded using the specified class
919
* loader, is assignment compatible with ResourceBundle, is accessible from
920
* ResourceBundle, and can be instantiated, <code>getBundle</code> creates a
921
* new instance of this class and uses it as the <em>result resource
924
* <li>Otherwise, <code>getBundle</code> attempts to locate a property
925
* resource file using the generated properties file name. It generates a
926
* path name from the candidate bundle name by replacing all "." characters
927
* with "/" and appending the string ".properties". It attempts to find a
928
* "resource" with this name using {@link
929
* java.lang.ClassLoader#getResource(java.lang.String)
930
* ClassLoader.getResource}. (Note that a "resource" in the sense of
931
* <code>getResource</code> has nothing to do with the contents of a
932
* resource bundle, it is just a container of data, such as a file.) If it
933
* finds a "resource", it attempts to create a new {@link
934
* PropertyResourceBundle} instance from its contents. If successful, this
935
* instance becomes the <em>result resource bundle</em>. </ul>
937
* <p>This continues until a result resource bundle is instantiated or the
938
* list of candidate bundle names is exhausted. If no matching resource
939
* bundle is found, the default control's {@link Control#getFallbackLocale
940
* getFallbackLocale} method is called, which returns the current default
941
* locale. A new sequence of candidate locale names is generated using this
942
* locale and and searched again, as above.
944
* <p>If still no result bundle is found, the base name alone is looked up. If
945
* this still fails, a <code>MissingResourceException</code> is thrown.
947
* <p><a name="parent_chain"/> Once a result resource bundle has been found,
948
* its <em>parent chain</em> is instantiated. If the result bundle already
949
* has a parent (perhaps because it was returned from a cache) the chain is
952
* <p>Otherwise, <code>getBundle</code> examines the remainder of the
953
* candidate locale list that was used during the pass that generated the
954
* result resource bundle. (As before, candidate bundle names where the
955
* final component is an empty string are omitted.) When it comes to the
956
* end of the candidate list, it tries the plain bundle name. With each of the
957
* candidate bundle names it attempts to instantiate a resource bundle (first
958
* looking for a class and then a properties file, as described above).
960
* <p>Whenever it succeeds, it calls the previously instantiated resource
961
* bundle's {@link #setParent(java.util.ResourceBundle) setParent} method
962
* with the new resource bundle. This continues until the list of names
963
* is exhausted or the current bundle already has a non-null parent.
965
* <p>Once the parent chain is complete, the bundle is returned.
967
* <p><b>Note:</b> <code>getBundle</code> caches instantiated resource
968
* bundles and might return the same resource bundle instance multiple times.
970
* <p><b>Note:</b>The <code>baseName</code> argument should be a fully
971
* qualified class name. However, for compatibility with earlier versions,
972
* Sun's Java SE Runtime Environments do not verify this, and so it is
973
* possible to access <code>PropertyResourceBundle</code>s by specifying a
974
* path name (using "/") instead of a fully qualified class name (using
977
* <p><a name="default_behavior_example"/>
978
* <strong>Example:</strong>
980
* The following class and property files are provided:
983
* MyResources.properties
984
* MyResources_fr.properties
985
* MyResources_fr_CH.class
986
* MyResources_fr_CH.properties
987
* MyResources_en.properties
988
* MyResources_es_ES.class
991
* The contents of all files are valid (that is, public non-abstract
992
* subclasses of <code>ResourceBundle</code> for the ".class" files,
993
* syntactically correct ".properties" files). The default locale is
994
* <code>Locale("en", "GB")</code>.
996
* <p>Calling <code>getBundle</code> with the locale arguments below will
997
* instantiate resource bundles as follows:
1000
* <tr><td>Locale("fr", "CH")</td><td>MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class</td></tr>
1001
* <tr><td>Locale("fr", "FR")</td><td>MyResources_fr.properties, parent MyResources.class</td></tr>
1002
* <tr><td>Locale("de", "DE")</td><td>MyResources_en.properties, parent MyResources.class</td></tr>
1003
* <tr><td>Locale("en", "US")</td><td>MyResources_en.properties, parent MyResources.class</td></tr>
1004
* <tr><td>Locale("es", "ES")</td><td>MyResources_es_ES.class, parent MyResources.class</td></tr>
1007
* <p>The file MyResources_fr_CH.properties is never used because it is
1008
* hidden by the MyResources_fr_CH.class. Likewise, MyResources.properties
1009
* is also hidden by MyResources.class.
1011
* @param baseName the base name of the resource bundle, a fully qualified class name
1012
* @param locale the locale for which a resource bundle is desired
1013
* @param loader the class loader from which to load the resource bundle
1014
* @return a resource bundle for the given base name and locale
1015
* @exception java.lang.NullPointerException
1016
* if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code>
1017
* @exception MissingResourceException
1018
* if no resource bundle for the specified base name can be found
1021
public static ResourceBundle getBundle(String baseName, Locale locale,
1024
if (loader == null) {
1025
throw new NullPointerException();
1027
return getBundleImpl(baseName, locale, loader, Control.INSTANCE);
1031
* Returns a resource bundle using the specified base name, target
1032
* locale, class loader and control. Unlike the {@linkplain
1033
* #getBundle(String, Locale, ClassLoader) <code>getBundle</code>
1034
* factory methods with no <code>control</code> argument}, the given
1035
* <code>control</code> specifies how to locate and instantiate resource
1036
* bundles. Conceptually, the bundle loading process with the given
1037
* <code>control</code> is performed in the following steps.
1041
* <li>This factory method looks up the resource bundle in the cache for
1042
* the specified <code>baseName</code>, <code>targetLocale</code> and
1043
* <code>loader</code>. If the requested resource bundle instance is
1044
* found in the cache and the time-to-live periods of the instance and
1045
* all of its parent instances have not expired, the instance is returned
1046
* to the caller. Otherwise, this factory method proceeds with the
1047
* loading process below.</li>
1049
* <li>The {@link ResourceBundle.Control#getFormats(String)
1050
* control.getFormats} method is called to get resource bundle formats
1051
* to produce bundle or resource names. The strings
1052
* <code>"java.class"</code> and <code>"java.properties"</code>
1053
* designate class-based and {@linkplain PropertyResourceBundle
1054
* property}-based resource bundles, respectively. Other strings
1055
* starting with <code>"java."</code> are reserved for future extensions
1056
* and must not be used for application-defined formats. Other strings
1057
* designate application-defined formats.</li>
1059
* <li>The {@link ResourceBundle.Control#getCandidateLocales(String,
1060
* Locale) control.getCandidateLocales} method is called with the target
1061
* locale to get a list of <em>candidate <code>Locale</code>s</em> for
1062
* which resource bundles are searched.</li>
1064
* <li>The {@link ResourceBundle.Control#newBundle(String, Locale,
1065
* String, ClassLoader, boolean) control.newBundle} method is called to
1066
* instantiate a <code>ResourceBundle</code> for the base bundle name, a
1067
* candidate locale, and a format. (Refer to the note on the cache
1068
* lookup below.) This step is iterated over all combinations of the
1069
* candidate locales and formats until the <code>newBundle</code> method
1070
* returns a <code>ResourceBundle</code> instance or the iteration has
1071
* used up all the combinations. For example, if the candidate locales
1072
* are <code>Locale("de", "DE")</code>, <code>Locale("de")</code> and
1073
* <code>Locale("")</code> and the formats are <code>"java.class"</code>
1074
* and <code>"java.properties"</code>, then the following is the
1075
* sequence of locale-format combinations to be used to call
1076
* <code>control.newBundle</code>.
1078
* <table style="width: 50%; text-align: left; margin-left: 40px;"
1079
* border="0" cellpadding="2" cellspacing="2">
1083
* style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">Locale<br>
1086
* style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">format<br>
1090
* <td style="vertical-align: top; width: 50%;">Locale("de", "DE")<br>
1092
* <td style="vertical-align: top; width: 50%;">java.class<br>
1096
* <td style="vertical-align: top; width: 50%;">Locale("de", "DE")</td>
1097
* <td style="vertical-align: top; width: 50%;">java.properties<br>
1101
* <td style="vertical-align: top; width: 50%;">Locale("de")</td>
1102
* <td style="vertical-align: top; width: 50%;">java.class</td>
1105
* <td style="vertical-align: top; width: 50%;">Locale("de")</td>
1106
* <td style="vertical-align: top; width: 50%;">java.properties</td>
1109
* <td style="vertical-align: top; width: 50%;">Locale("")<br>
1111
* <td style="vertical-align: top; width: 50%;">java.class</td>
1114
* <td style="vertical-align: top; width: 50%;">Locale("")</td>
1115
* <td style="vertical-align: top; width: 50%;">java.properties</td>
1121
* <li>If the previous step has found no resource bundle, proceed to
1122
* Step 6. If a bundle has been found that is a base bundle (a bundle
1123
* for <code>Locale("")</code>), and the candidate locale list only contained
1124
* <code>Locale("")</code>, return the bundle to the caller. If a bundle
1125
* has been found that is a base bundle, but the candidate locale list
1126
* contained locales other than Locale(""), put the bundle on hold and
1127
* proceed to Step 6. If a bundle has been found that is not a base
1128
* bundle, proceed to Step 7.</li>
1130
* <li>The {@link ResourceBundle.Control#getFallbackLocale(String,
1131
* Locale) control.getFallbackLocale} method is called to get a fallback
1132
* locale (alternative to the current target locale) to try further
1133
* finding a resource bundle. If the method returns a non-null locale,
1134
* it becomes the next target locale and the loading process starts over
1135
* from Step 3. Otherwise, if a base bundle was found and put on hold in
1136
* a previous Step 5, it is returned to the caller now. Otherwise, a
1137
* MissingResourceException is thrown.</li>
1139
* <li>At this point, we have found a resource bundle that's not the
1140
* base bundle. If this bundle set its parent during its instantiation,
1141
* it is returned to the caller. Otherwise, its <a
1142
* href="./ResourceBundle.html#parent_chain">parent chain</a> is
1143
* instantiated based on the list of candidate locales from which it was
1144
* found. Finally, the bundle is returned to the caller.</li>
1147
* <p>During the resource bundle loading process above, this factory
1148
* method looks up the cache before calling the {@link
1149
* Control#newBundle(String, Locale, String, ClassLoader, boolean)
1150
* control.newBundle} method. If the time-to-live period of the
1151
* resource bundle found in the cache has expired, the factory method
1152
* calls the {@link ResourceBundle.Control#needsReload(String, Locale,
1153
* String, ClassLoader, ResourceBundle, long) control.needsReload}
1154
* method to determine whether the resource bundle needs to be reloaded.
1155
* If reloading is required, the factory method calls
1156
* <code>control.newBundle</code> to reload the resource bundle. If
1157
* <code>control.newBundle</code> returns <code>null</code>, the factory
1158
* method puts a dummy resource bundle in the cache as a mark of
1159
* nonexistent resource bundles in order to avoid lookup overhead for
1160
* subsequent requests. Such dummy resource bundles are under the same
1161
* expiration control as specified by <code>control</code>.
1163
* <p>All resource bundles loaded are cached by default. Refer to
1164
* {@link Control#getTimeToLive(String,Locale)
1165
* control.getTimeToLive} for details.
1167
* <p>The following is an example of the bundle loading process with the
1168
* default <code>ResourceBundle.Control</code> implementation.
1172
* <li>Base bundle name: <code>foo.bar.Messages</code>
1173
* <li>Requested <code>Locale</code>: {@link Locale#ITALY}</li>
1174
* <li>Default <code>Locale</code>: {@link Locale#FRENCH}</li>
1175
* <li>Available resource bundles:
1176
* <code>foo/bar/Messages_fr.properties</code> and
1177
* <code>foo/bar/Messages.properties</code></li>
1180
* <p>First, <code>getBundle</code> tries loading a resource bundle in
1181
* the following sequence.
1184
* <li>class <code>foo.bar.Messages_it_IT</code>
1185
* <li>file <code>foo/bar/Messages_it_IT.properties</code>
1186
* <li>class <code>foo.bar.Messages_it</code></li>
1187
* <li>file <code>foo/bar/Messages_it.properties</code></li>
1188
* <li>class <code>foo.bar.Messages</code></li>
1189
* <li>file <code>foo/bar/Messages.properties</code></li>
1192
* <p>At this point, <code>getBundle</code> finds
1193
* <code>foo/bar/Messages.properties</code>, which is put on hold
1194
* because it's the base bundle. <code>getBundle</code> calls {@link
1195
* Control#getFallbackLocale(String, Locale)
1196
* control.getFallbackLocale("foo.bar.Messages", Locale.ITALY)} which
1197
* returns <code>Locale.FRENCH</code>. Next, <code>getBundle</code>
1198
* tries loading a bundle in the following sequence.
1201
* <li>class <code>foo.bar.Messages_fr</code></li>
1202
* <li>file <code>foo/bar/Messages_fr.properties</code></li>
1203
* <li>class <code>foo.bar.Messages</code></li>
1204
* <li>file <code>foo/bar/Messages.properties</code></li>
1207
* <p><code>getBundle</code> finds
1208
* <code>foo/bar/Messages_fr.properties</code> and creates a
1209
* <code>ResourceBundle</code> instance. Then, <code>getBundle</code>
1210
* sets up its parent chain from the list of the candiate locales. Only
1211
* <code>foo/bar/Messages.properties</code> is found in the list and
1212
* <code>getBundle</code> creates a <code>ResourceBundle</code> instance
1213
* that becomes the parent of the instance for
1214
* <code>foo/bar/Messages_fr.properties</code>.
1217
* the base name of the resource bundle, a fully qualified
1219
* @param targetLocale
1220
* the locale for which a resource bundle is desired
1222
* the class loader from which to load the resource bundle
1224
* the control which gives information for the resource
1225
* bundle loading process
1226
* @return a resource bundle for the given base name and locale
1227
* @exception NullPointerException
1228
* if <code>baseName</code>, <code>targetLocale</code>,
1229
* <code>loader</code>, or <code>control</code> is
1231
* @exception MissingResourceException
1232
* if no resource bundle for the specified base name can be found
1233
* @exception IllegalArgumentException
1234
* if the given <code>control</code> doesn't perform properly
1235
* (e.g., <code>control.getCandidateLocales</code> returns null.)
1236
* Note that validation of <code>control</code> is performed as
1240
public static ResourceBundle getBundle(String baseName, Locale targetLocale,
1241
ClassLoader loader, Control control) {
1242
if (loader == null || control == null) {
1243
throw new NullPointerException();
1245
return getBundleImpl(baseName, targetLocale, loader, control);
1248
private static ResourceBundle getBundleImpl(String baseName, Locale locale,
1249
ClassLoader loader, Control control) {
1250
if (locale == null || control == null) {
1251
throw new NullPointerException();
1254
// We create a CacheKey here for use by this call. The base
1255
// name and loader will never change during the bundle loading
1256
// process. We have to make sure that the locale is set before
1257
// using it as a cache key.
1258
CacheKey cacheKey = new CacheKey(baseName, locale, loader);
1259
ResourceBundle bundle = null;
1261
// Quick lookup of the cache.
1262
BundleReference bundleRef = cacheList.get(cacheKey);
1263
if (bundleRef != null) {
1264
bundle = bundleRef.get();
1268
// If this bundle and all of its parents are valid (not expired),
1269
// then return this bundle. If any of the bundles is expired, we
1270
// don't call control.needsReload here but instead drop into the
1271
// complete loading process below.
1272
if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
1276
// No valid bundle was found in the cache, so we need to load the
1277
// resource bundle and its parents.
1279
boolean isKnownControl = (control == Control.INSTANCE) ||
1280
(control instanceof SingleFormatControl);
1281
List<String> formats = control.getFormats(baseName);
1282
if (!isKnownControl && !checkList(formats)) {
1283
throw new IllegalArgumentException("Invalid Control: getFormats");
1286
ResourceBundle baseBundle = null;
1287
for (Locale targetLocale = locale;
1288
targetLocale != null;
1289
targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
1290
List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
1291
if (!isKnownControl && !checkList(candidateLocales)) {
1292
throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
1295
bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);
1297
// If the loaded bundle is the base bundle and exactly for the
1298
// requested locale or the only candidate locale, then take the
1299
// bundle as the resulting one. If the loaded bundle is the base
1300
// bundle, it's put on hold until we finish processing all
1301
// fallback locales.
1302
if (isValidBundle(bundle)) {
1303
boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
1304
if (!isBaseBundle || bundle.locale.equals(locale)
1305
|| (candidateLocales.size() == 1
1306
&& bundle.locale.equals(candidateLocales.get(0)))) {
1310
// If the base bundle has been loaded, keep the reference in
1311
// baseBundle so that we can avoid any redundant loading in case
1312
// the control specify not to cache bundles.
1313
if (isBaseBundle && baseBundle == null) {
1314
baseBundle = bundle;
1319
if (bundle == null) {
1320
if (baseBundle == null) {
1321
throwMissingResourceException(baseName, locale, cacheKey.getCause());
1323
bundle = baseBundle;
1330
* Checks if the given <code>List</code> is not null, not empty,
1331
* not having null in its elements.
1333
private static final boolean checkList(List a) {
1334
boolean valid = (a != null && a.size() != 0);
1336
int size = a.size();
1337
for (int i = 0; valid && i < size; i++) {
1338
valid = (a.get(i) != null);
1344
private static final ResourceBundle findBundle(CacheKey cacheKey,
1345
List<Locale> candidateLocales,
1346
List<String> formats,
1349
ResourceBundle baseBundle) {
1350
Locale targetLocale = candidateLocales.get(index);
1351
ResourceBundle parent = null;
1352
if (index != candidateLocales.size() - 1) {
1353
parent = findBundle(cacheKey, candidateLocales, formats, index + 1,
1354
control, baseBundle);
1355
} else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
1359
// Before we do the real loading work, see whether we need to
1360
// do some housekeeping: If references to class loaders or
1361
// resource bundles have been nulled out, remove all related
1362
// information from the cache.
1364
while ((ref = referenceQueue.poll()) != null) {
1365
cacheList.remove(((CacheKeyReference)ref).getCacheKey());
1368
// flag indicating the resource bundle has expired in the cache
1369
boolean expiredBundle = false;
1371
// First, look up the cache to see if it's in the cache, without
1372
// attempting to load bundle.
1373
cacheKey.setLocale(targetLocale);
1374
ResourceBundle bundle = findBundleInCache(cacheKey, control);
1375
if (isValidBundle(bundle)) {
1376
expiredBundle = bundle.expired;
1377
if (!expiredBundle) {
1378
// If its parent is the one asked for by the candidate
1379
// locales (the runtime lookup path), we can take the cached
1380
// one. (If it's not identical, then we'd have to check the
1381
// parent's parents to be consistent with what's been
1383
if (bundle.parent == parent) {
1386
// Otherwise, remove the cached one since we can't keep
1387
// the same bundles having different parents.
1388
BundleReference bundleRef = cacheList.get(cacheKey);
1389
if (bundleRef != null && bundleRef.get() == bundle) {
1390
cacheList.remove(cacheKey, bundleRef);
1395
if (bundle != NONEXISTENT_BUNDLE) {
1396
CacheKey constKey = (CacheKey) cacheKey.clone();
1399
bundle = loadBundle(cacheKey, formats, control, expiredBundle);
1400
if (bundle != null) {
1401
if (bundle.parent == null) {
1402
bundle.setParent(parent);
1404
bundle.locale = targetLocale;
1405
bundle = putBundleInCache(cacheKey, bundle, control);
1409
// Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
1410
// instance for the locale.
1411
putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control);
1413
if (constKey.getCause() instanceof InterruptedException) {
1414
Thread.currentThread().interrupt();
1421
private static final ResourceBundle loadBundle(CacheKey cacheKey,
1422
List<String> formats,
1426
// Here we actually load the bundle in the order of formats
1427
// specified by the getFormats() value.
1428
Locale targetLocale = cacheKey.getLocale();
1430
ResourceBundle bundle = null;
1431
int size = formats.size();
1432
for (int i = 0; i < size; i++) {
1433
String format = formats.get(i);
1435
bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
1436
cacheKey.getLoader(), reload);
1437
} catch (LinkageError error) {
1438
// We need to handle the LinkageError case due to
1439
// inconsistent case-sensitivity in ClassLoader.
1440
// See 6572242 for details.
1441
cacheKey.setCause(error);
1442
} catch (Exception cause) {
1443
cacheKey.setCause(cause);
1445
if (bundle != null) {
1446
// Set the format in the cache key so that it can be
1447
// used when calling needsReload later.
1448
cacheKey.setFormat(format);
1449
bundle.name = cacheKey.getName();
1450
bundle.locale = targetLocale;
1451
// Bundle provider might reuse instances. So we should make
1452
// sure to clear the expired flag here.
1453
bundle.expired = false;
1461
private static final boolean isValidBundle(ResourceBundle bundle) {
1462
return bundle != null && bundle != NONEXISTENT_BUNDLE;
1466
* Determines whether any of resource bundles in the parent chain,
1467
* including the leaf, have expired.
1469
private static final boolean hasValidParentChain(ResourceBundle bundle) {
1470
long now = System.currentTimeMillis();
1471
while (bundle != null) {
1472
if (bundle.expired) {
1475
CacheKey key = bundle.cacheKey;
1477
long expirationTime = key.expirationTime;
1478
if (expirationTime >= 0 && expirationTime <= now) {
1482
bundle = bundle.parent;
1488
* Throw a MissingResourceException with proper message
1490
private static final void throwMissingResourceException(String baseName,
1493
// If the cause is a MissingResourceException, avoid creating
1494
// a long chain. (6355009)
1495
if (cause instanceof MissingResourceException) {
1498
throw new MissingResourceException("Can't find bundle for base name "
1499
+ baseName + ", locale " + locale,
1500
baseName + "_" + locale, // className
1506
* Finds a bundle in the cache. Any expired bundles are marked as
1507
* `expired' and removed from the cache upon return.
1509
* @param cacheKey the key to look up the cache
1510
* @param control the Control to be used for the expiration control
1511
* @return the cached bundle, or null if the bundle is not found in the
1512
* cache or its parent has expired. <code>bundle.expire</code> is true
1513
* upon return if the bundle in the cache has expired.
1515
private static final ResourceBundle findBundleInCache(CacheKey cacheKey,
1517
BundleReference bundleRef = cacheList.get(cacheKey);
1518
if (bundleRef == null) {
1521
ResourceBundle bundle = bundleRef.get();
1522
if (bundle == null) {
1525
ResourceBundle p = bundle.parent;
1526
assert p != NONEXISTENT_BUNDLE;
1527
// If the parent has expired, then this one must also expire. We
1528
// check only the immediate parent because the actual loading is
1529
// done from the root (base) to leaf (child) and the purpose of
1530
// checking is to propagate expiration towards the leaf. For
1531
// example, if the requested locale is ja_JP_JP and there are
1532
// bundles for all of the candidates in the cache, we have a list,
1534
// base <- ja <- ja_JP <- ja_JP_JP
1536
// If ja has expired, then it will reload ja and the list becomes a
1540
// " <- ja (expired) <- ja_JP <- ja_JP_JP
1542
// When looking up ja_JP in the cache, it finds ja_JP in the cache
1543
// which references to the expired ja. Then, ja_JP is marked as
1544
// expired and removed from the cache. This will be propagated to
1547
// Now, it's possible, for example, that while loading new ja_JP,
1548
// someone else has started loading the same bundle and finds the
1549
// base bundle has expired. Then, what we get from the first
1550
// getBundle call includes the expired base bundle. However, if
1551
// someone else didn't start its loading, we wouldn't know if the
1552
// base bundle has expired at the end of the loading process. The
1553
// expiration control doesn't guarantee that the returned bundle and
1554
// its parents haven't expired.
1556
// We could check the entire parent chain to see if there's any in
1557
// the chain that has expired. But this process may never end. An
1558
// extreme case would be that getTimeToLive returns 0 and
1559
// needsReload always returns true.
1560
if (p != null && p.expired) {
1561
assert bundle != NONEXISTENT_BUNDLE;
1562
bundle.expired = true;
1563
bundle.cacheKey = null;
1564
cacheList.remove(cacheKey, bundleRef);
1567
CacheKey key = bundleRef.getCacheKey();
1568
long expirationTime = key.expirationTime;
1569
if (!bundle.expired && expirationTime >= 0 &&
1570
expirationTime <= System.currentTimeMillis()) {
1571
// its TTL period has expired.
1572
if (bundle != NONEXISTENT_BUNDLE) {
1573
// Synchronize here to call needsReload to avoid
1574
// redundant concurrent calls for the same bundle.
1575
synchronized (bundle) {
1576
expirationTime = key.expirationTime;
1577
if (!bundle.expired && expirationTime >= 0 &&
1578
expirationTime <= System.currentTimeMillis()) {
1580
bundle.expired = control.needsReload(key.getName(),
1586
} catch (Exception e) {
1587
cacheKey.setCause(e);
1589
if (bundle.expired) {
1590
// If the bundle needs to be reloaded, then
1591
// remove the bundle from the cache, but
1592
// return the bundle with the expired flag
1594
bundle.cacheKey = null;
1595
cacheList.remove(cacheKey, bundleRef);
1597
// Update the expiration control info. and reuse
1598
// the same bundle instance
1599
setExpirationTime(key, control);
1604
// We just remove NONEXISTENT_BUNDLE from the cache.
1605
cacheList.remove(cacheKey, bundleRef);
1614
* Put a new bundle in the cache.
1616
* @param cacheKey the key for the resource bundle
1617
* @param bundle the resource bundle to be put in the cache
1618
* @return the ResourceBundle for the cacheKey; if someone has put
1619
* the bundle before this call, the one found in the cache is
1622
private static final ResourceBundle putBundleInCache(CacheKey cacheKey,
1623
ResourceBundle bundle,
1625
setExpirationTime(cacheKey, control);
1626
if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) {
1627
CacheKey key = (CacheKey) cacheKey.clone();
1628
BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key);
1629
bundle.cacheKey = key;
1631
// Put the bundle in the cache if it's not been in the cache.
1632
BundleReference result = cacheList.putIfAbsent(key, bundleRef);
1634
// If someone else has put the same bundle in the cache before
1635
// us and it has not expired, we should use the one in the cache.
1636
if (result != null) {
1637
ResourceBundle rb = result.get();
1638
if (rb != null && !rb.expired) {
1639
// Clear the back link to the cache key
1640
bundle.cacheKey = null;
1642
// Clear the reference in the BundleReference so that
1643
// it won't be enqueued.
1646
// Replace the invalid (garbage collected or expired)
1647
// instance with the valid one.
1648
cacheList.put(key, bundleRef);
1655
private static final void setExpirationTime(CacheKey cacheKey, Control control) {
1656
long ttl = control.getTimeToLive(cacheKey.getName(),
1657
cacheKey.getLocale());
1659
// If any expiration time is specified, set the time to be
1660
// expired in the cache.
1661
long now = System.currentTimeMillis();
1662
cacheKey.loadTime = now;
1663
cacheKey.expirationTime = now + ttl;
1664
} else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) {
1665
cacheKey.expirationTime = ttl;
1667
throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
1672
* Removes all resource bundles from the cache that have been loaded
1673
* using the caller's class loader.
1676
* @see ResourceBundle.Control#getTimeToLive(String,Locale)
1678
@ikvm.internal.HasCallerID
1679
public static final void clearCache() {
1680
clearCache(getLoader(Reflection.getCallerClass(2)));
1684
* Removes all resource bundles from the cache that have been loaded
1685
* using the given class loader.
1687
* @param loader the class loader
1688
* @exception NullPointerException if <code>loader</code> is null
1690
* @see ResourceBundle.Control#getTimeToLive(String,Locale)
1692
public static final void clearCache(ClassLoader loader) {
1693
if (loader == null) {
1694
throw new NullPointerException();
1696
Set<CacheKey> set = cacheList.keySet();
1697
for (CacheKey key : set) {
1698
if (key.getLoader() == loader) {
1705
* Gets an object for the given key from this resource bundle.
1706
* Returns null if this resource bundle does not contain an
1707
* object for the given key.
1709
* @param key the key for the desired object
1710
* @exception NullPointerException if <code>key</code> is <code>null</code>
1711
* @return the object for the given key, or null
1713
protected abstract Object handleGetObject(String key);
1716
* Returns an enumeration of the keys.
1718
* @return an <code>Enumeration</code> of the keys contained in
1719
* this <code>ResourceBundle</code> and its parent bundles.
1721
public abstract Enumeration<String> getKeys();
1724
* Determines whether the given <code>key</code> is contained in
1725
* this <code>ResourceBundle</code> or its parent bundles.
1728
* the resource <code>key</code>
1729
* @return <code>true</code> if the given <code>key</code> is
1730
* contained in this <code>ResourceBundle</code> or its
1731
* parent bundles; <code>false</code> otherwise.
1732
* @exception NullPointerException
1733
* if <code>key</code> is <code>null</code>
1736
public boolean containsKey(String key) {
1738
throw new NullPointerException();
1740
for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
1741
if (rb.handleKeySet().contains(key)) {
1749
* Returns a <code>Set</code> of all keys contained in this
1750
* <code>ResourceBundle</code> and its parent bundles.
1752
* @return a <code>Set</code> of all keys contained in this
1753
* <code>ResourceBundle</code> and its parent bundles.
1756
public Set<String> keySet() {
1757
Set<String> keys = new HashSet<>();
1758
for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
1759
keys.addAll(rb.handleKeySet());
1765
* Returns a <code>Set</code> of the keys contained <em>only</em>
1766
* in this <code>ResourceBundle</code>.
1768
* <p>The default implementation returns a <code>Set</code> of the
1769
* keys returned by the {@link #getKeys() getKeys} method except
1770
* for the ones for which the {@link #handleGetObject(String)
1771
* handleGetObject} method returns <code>null</code>. Once the
1772
* <code>Set</code> has been created, the value is kept in this
1773
* <code>ResourceBundle</code> in order to avoid producing the
1774
* same <code>Set</code> in subsequent calls. Subclasses can
1775
* override this method for faster handling.
1777
* @return a <code>Set</code> of the keys contained only in this
1778
* <code>ResourceBundle</code>
1781
protected Set<String> handleKeySet() {
1782
if (keySet == null) {
1783
synchronized (this) {
1784
if (keySet == null) {
1785
Set<String> keys = new HashSet<>();
1786
Enumeration<String> enumKeys = getKeys();
1787
while (enumKeys.hasMoreElements()) {
1788
String key = enumKeys.nextElement();
1789
if (handleGetObject(key) != null) {
1803
* <code>ResourceBundle.Control</code> defines a set of callback methods
1804
* that are invoked by the {@link ResourceBundle#getBundle(String,
1805
* Locale, ClassLoader, Control) ResourceBundle.getBundle} factory
1806
* methods during the bundle loading process. In other words, a
1807
* <code>ResourceBundle.Control</code> collaborates with the factory
1808
* methods for loading resource bundles. The default implementation of
1809
* the callback methods provides the information necessary for the
1810
* factory methods to perform the <a
1811
* href="./ResourceBundle.html#default_behavior">default behavior</a>.
1813
* <p>In addition to the callback methods, the {@link
1814
* #toBundleName(String, Locale) toBundleName} and {@link
1815
* #toResourceName(String, String) toResourceName} methods are defined
1816
* primarily for convenience in implementing the callback
1817
* methods. However, the <code>toBundleName</code> method could be
1818
* overridden to provide different conventions in the organization and
1819
* packaging of localized resources. The <code>toResourceName</code>
1820
* method is <code>final</code> to avoid use of wrong resource and class
1823
* <p>Two factory methods, {@link #getControl(List)} and {@link
1824
* #getNoFallbackControl(List)}, provide
1825
* <code>ResourceBundle.Control</code> instances that implement common
1826
* variations of the default bundle loading process.
1828
* <p>The formats returned by the {@link Control#getFormats(String)
1829
* getFormats} method and candidate locales returned by the {@link
1830
* ResourceBundle.Control#getCandidateLocales(String, Locale)
1831
* getCandidateLocales} method must be consistent in all
1832
* <code>ResourceBundle.getBundle</code> invocations for the same base
1833
* bundle. Otherwise, the <code>ResourceBundle.getBundle</code> methods
1834
* may return unintended bundles. For example, if only
1835
* <code>"java.class"</code> is returned by the <code>getFormats</code>
1836
* method for the first call to <code>ResourceBundle.getBundle</code>
1837
* and only <code>"java.properties"</code> for the second call, then the
1838
* second call will return the class-based one that has been cached
1839
* during the first call.
1841
* <p>A <code>ResourceBundle.Control</code> instance must be thread-safe
1842
* if it's simultaneously used by multiple threads.
1843
* <code>ResourceBundle.getBundle</code> does not synchronize to call
1844
* the <code>ResourceBundle.Control</code> methods. The default
1845
* implementations of the methods are thread-safe.
1847
* <p>Applications can specify <code>ResourceBundle.Control</code>
1848
* instances returned by the <code>getControl</code> factory methods or
1849
* created from a subclass of <code>ResourceBundle.Control</code> to
1850
* customize the bundle loading process. The following are examples of
1851
* changing the default bundle loading process.
1853
* <p><b>Example 1</b>
1855
* <p>The following code lets <code>ResourceBundle.getBundle</code> look
1856
* up only properties-based resources.
1859
* import java.util.*;
1860
* import static java.util.ResourceBundle.Control.*;
1862
* ResourceBundle bundle =
1863
* ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"),
1864
* ResourceBundle.Control.getControl(FORMAT_PROPERTIES));
1867
* Given the resource bundles in the <a
1868
* href="./ResourceBundle.html#default_behavior_example">example</a> in
1869
* the <code>ResourceBundle.getBundle</code> description, this
1870
* <code>ResourceBundle.getBundle</code> call loads
1871
* <code>MyResources_fr_CH.properties</code> whose parent is
1872
* <code>MyResources_fr.properties</code> whose parent is
1873
* <code>MyResources.properties</code>. (<code>MyResources_fr_CH.properties</code>
1874
* is not hidden, but <code>MyResources_fr_CH.class</code> is.)
1876
* <p><b>Example 2</b>
1878
* <p>The following is an example of loading XML-based bundles
1879
* using {@link Properties#loadFromXML(java.io.InputStream)
1880
* Properties.loadFromXML}.
1883
* ResourceBundle rb = ResourceBundle.getBundle("Messages",
1884
* new ResourceBundle.Control() {
1885
* public List<String> getFormats(String baseName) {
1886
* if (baseName == null)
1887
* throw new NullPointerException();
1888
* return Arrays.asList("xml");
1890
* public ResourceBundle newBundle(String baseName,
1893
* ClassLoader loader,
1895
* throws IllegalAccessException,
1896
* InstantiationException,
1898
* if (baseName == null || locale == null
1899
* || format == null || loader == null)
1900
* throw new NullPointerException();
1901
* ResourceBundle bundle = null;
1902
* if (format.equals("xml")) {
1903
* String bundleName = toBundleName(baseName, locale);
1904
* String resourceName = toResourceName(bundleName, format);
1905
* InputStream stream = null;
1907
* URL url = loader.getResource(resourceName);
1908
* if (url != null) {
1909
* URLConnection connection = url.openConnection();
1910
* if (connection != null) {
1911
* // Disable caches to get fresh data for
1913
* connection.setUseCaches(false);
1914
* stream = connection.getInputStream();
1918
* stream = loader.getResourceAsStream(resourceName);
1920
* if (stream != null) {
1921
* BufferedInputStream bis = new BufferedInputStream(stream);
1922
* bundle = new XMLResourceBundle(bis);
1932
* private static class XMLResourceBundle extends ResourceBundle {
1933
* private Properties props;
1934
* XMLResourceBundle(InputStream stream) throws IOException {
1935
* props = new Properties();
1936
* props.loadFromXML(stream);
1938
* protected Object handleGetObject(String key) {
1939
* return props.getProperty(key);
1941
* public Enumeration<String> getKeys() {
1949
public static class Control {
1951
* The default format <code>List</code>, which contains the strings
1952
* <code>"java.class"</code> and <code>"java.properties"</code>, in
1953
* this order. This <code>List</code> is {@linkplain
1954
* Collections#unmodifiableList(List) unmodifiable}.
1956
* @see #getFormats(String)
1958
public static final List<String> FORMAT_DEFAULT
1959
= Collections.unmodifiableList(Arrays.asList("java.class",
1960
"java.properties"));
1963
* The class-only format <code>List</code> containing
1964
* <code>"java.class"</code>. This <code>List</code> is {@linkplain
1965
* Collections#unmodifiableList(List) unmodifiable}.
1967
* @see #getFormats(String)
1969
public static final List<String> FORMAT_CLASS
1970
= Collections.unmodifiableList(Arrays.asList("java.class"));
1973
* The properties-only format <code>List</code> containing
1974
* <code>"java.properties"</code>. This <code>List</code> is
1975
* {@linkplain Collections#unmodifiableList(List) unmodifiable}.
1977
* @see #getFormats(String)
1979
public static final List<String> FORMAT_PROPERTIES
1980
= Collections.unmodifiableList(Arrays.asList("java.properties"));
1983
* The time-to-live constant for not caching loaded resource bundle
1986
* @see #getTimeToLive(String, Locale)
1988
public static final long TTL_DONT_CACHE = -1;
1991
* The time-to-live constant for disabling the expiration control
1992
* for loaded resource bundle instances in the cache.
1994
* @see #getTimeToLive(String, Locale)
1996
public static final long TTL_NO_EXPIRATION_CONTROL = -2;
1998
private static final Control INSTANCE = new Control();
2001
* Sole constructor. (For invocation by subclass constructors,
2002
* typically implicit.)
2004
protected Control() {
2008
* Returns a <code>ResourceBundle.Control</code> in which the {@link
2009
* #getFormats(String) getFormats} method returns the specified
2010
* <code>formats</code>. The <code>formats</code> must be equal to
2011
* one of {@link Control#FORMAT_PROPERTIES}, {@link
2012
* Control#FORMAT_CLASS} or {@link
2013
* Control#FORMAT_DEFAULT}. <code>ResourceBundle.Control</code>
2014
* instances returned by this method are singletons and thread-safe.
2016
* <p>Specifying {@link Control#FORMAT_DEFAULT} is equivalent to
2017
* instantiating the <code>ResourceBundle.Control</code> class,
2018
* except that this method returns a singleton.
2021
* the formats to be returned by the
2022
* <code>ResourceBundle.Control.getFormats</code> method
2023
* @return a <code>ResourceBundle.Control</code> supporting the
2024
* specified <code>formats</code>
2025
* @exception NullPointerException
2026
* if <code>formats</code> is <code>null</code>
2027
* @exception IllegalArgumentException
2028
* if <code>formats</code> is unknown
2030
public static final Control getControl(List<String> formats) {
2031
if (formats.equals(Control.FORMAT_PROPERTIES)) {
2032
return SingleFormatControl.PROPERTIES_ONLY;
2034
if (formats.equals(Control.FORMAT_CLASS)) {
2035
return SingleFormatControl.CLASS_ONLY;
2037
if (formats.equals(Control.FORMAT_DEFAULT)) {
2038
return Control.INSTANCE;
2040
throw new IllegalArgumentException();
2044
* Returns a <code>ResourceBundle.Control</code> in which the {@link
2045
* #getFormats(String) getFormats} method returns the specified
2046
* <code>formats</code> and the {@link
2047
* Control#getFallbackLocale(String, Locale) getFallbackLocale}
2048
* method returns <code>null</code>. The <code>formats</code> must
2049
* be equal to one of {@link Control#FORMAT_PROPERTIES}, {@link
2050
* Control#FORMAT_CLASS} or {@link Control#FORMAT_DEFAULT}.
2051
* <code>ResourceBundle.Control</code> instances returned by this
2052
* method are singletons and thread-safe.
2055
* the formats to be returned by the
2056
* <code>ResourceBundle.Control.getFormats</code> method
2057
* @return a <code>ResourceBundle.Control</code> supporting the
2058
* specified <code>formats</code> with no fallback
2059
* <code>Locale</code> support
2060
* @exception NullPointerException
2061
* if <code>formats</code> is <code>null</code>
2062
* @exception IllegalArgumentException
2063
* if <code>formats</code> is unknown
2065
public static final Control getNoFallbackControl(List<String> formats) {
2066
if (formats.equals(Control.FORMAT_DEFAULT)) {
2067
return NoFallbackControl.NO_FALLBACK;
2069
if (formats.equals(Control.FORMAT_PROPERTIES)) {
2070
return NoFallbackControl.PROPERTIES_ONLY_NO_FALLBACK;
2072
if (formats.equals(Control.FORMAT_CLASS)) {
2073
return NoFallbackControl.CLASS_ONLY_NO_FALLBACK;
2075
throw new IllegalArgumentException();
2079
* Returns a <code>List</code> of <code>String</code>s containing
2080
* formats to be used to load resource bundles for the given
2081
* <code>baseName</code>. The <code>ResourceBundle.getBundle</code>
2082
* factory method tries to load resource bundles with formats in the
2083
* order specified by the list. The list returned by this method
2084
* must have at least one <code>String</code>. The predefined
2085
* formats are <code>"java.class"</code> for class-based resource
2086
* bundles and <code>"java.properties"</code> for {@linkplain
2087
* PropertyResourceBundle properties-based} ones. Strings starting
2088
* with <code>"java."</code> are reserved for future extensions and
2089
* must not be used by application-defined formats.
2091
* <p>It is not a requirement to return an immutable (unmodifiable)
2092
* <code>List</code>. However, the returned <code>List</code> must
2093
* not be mutated after it has been returned by
2094
* <code>getFormats</code>.
2096
* <p>The default implementation returns {@link #FORMAT_DEFAULT} so
2097
* that the <code>ResourceBundle.getBundle</code> factory method
2098
* looks up first class-based resource bundles, then
2099
* properties-based ones.
2102
* the base name of the resource bundle, a fully qualified class
2104
* @return a <code>List</code> of <code>String</code>s containing
2105
* formats for loading resource bundles.
2106
* @exception NullPointerException
2107
* if <code>baseName</code> is null
2108
* @see #FORMAT_DEFAULT
2109
* @see #FORMAT_CLASS
2110
* @see #FORMAT_PROPERTIES
2112
public List<String> getFormats(String baseName) {
2113
if (baseName == null) {
2114
throw new NullPointerException();
2116
return FORMAT_DEFAULT;
2120
* Returns a <code>List</code> of <code>Locale</code>s as candidate
2121
* locales for <code>baseName</code> and <code>locale</code>. This
2122
* method is called by the <code>ResourceBundle.getBundle</code>
2123
* factory method each time the factory method tries finding a
2124
* resource bundle for a target <code>Locale</code>.
2126
* <p>The sequence of the candidate locales also corresponds to the
2127
* runtime resource lookup path (also known as the <I>parent
2128
* chain</I>), if the corresponding resource bundles for the
2129
* candidate locales exist and their parents are not defined by
2130
* loaded resource bundles themselves. The last element of the list
2131
* must be a {@linkplain Locale#ROOT root locale} if it is desired to
2132
* have the base bundle as the terminal of the parent chain.
2134
* <p>If the given locale is equal to <code>Locale.ROOT</code> (the
2135
* root locale), a <code>List</code> containing only the root
2136
* <code>Locale</code> must be returned. In this case, the
2137
* <code>ResourceBundle.getBundle</code> factory method loads only
2138
* the base bundle as the resulting resource bundle.
2140
* <p>It is not a requirement to return an immutable (unmodifiable)
2141
* <code>List</code>. However, the returned <code>List</code> must not
2142
* be mutated after it has been returned by
2143
* <code>getCandidateLocales</code>.
2145
* <p>The default implementation returns a <code>List</code> containing
2146
* <code>Locale</code>s using the rules described below. In the
2147
* description below, <em>L</em>, <em>S</em>, <em>C</em> and <em>V</em>
2148
* respectively represent non-empty language, script, country, and
2149
* variant. For example, [<em>L</em>, <em>C</em>] represents a
2150
* <code>Locale</code> that has non-empty values only for language and
2151
* country. The form <em>L</em>("xx") represents the (non-empty)
2152
* language value is "xx". For all cases, <code>Locale</code>s whose
2153
* final component values are empty strings are omitted.
2155
* <ol><li>For an input <code>Locale</code> with an empty script value,
2156
* append candidate <code>Locale</code>s by omitting the final component
2157
* one by one as below:
2160
* <li> [<em>L</em>, <em>C</em>, <em>V</em>]
2161
* <li> [<em>L</em>, <em>C</em>]
2163
* <li> <code>Locale.ROOT</code>
2166
* <li>For an input <code>Locale</code> with a non-empty script value,
2167
* append candidate <code>Locale</code>s by omitting the final component
2168
* up to language, then append candidates generated from the
2169
* <code>Locale</code> with country and variant restored:
2172
* <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V</em>]
2173
* <li> [<em>L</em>, <em>S</em>, <em>C</em>]
2174
* <li> [<em>L</em>, <em>S</em>]
2175
* <li> [<em>L</em>, <em>C</em>, <em>V</em>]
2176
* <li> [<em>L</em>, <em>C</em>]
2178
* <li> <code>Locale.ROOT</code>
2181
* <li>For an input <code>Locale</code> with a variant value consisting
2182
* of multiple subtags separated by underscore, generate candidate
2183
* <code>Locale</code>s by omitting the variant subtags one by one, then
2184
* insert them after every occurence of <code> Locale</code>s with the
2185
* full variant value in the original list. For example, if the
2186
* the variant consists of two subtags <em>V1</em> and <em>V2</em>:
2189
* <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]
2190
* <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>]
2191
* <li> [<em>L</em>, <em>S</em>, <em>C</em>]
2192
* <li> [<em>L</em>, <em>S</em>]
2193
* <li> [<em>L</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]
2194
* <li> [<em>L</em>, <em>C</em>, <em>V1</em>]
2195
* <li> [<em>L</em>, <em>C</em>]
2197
* <li> <code>Locale.ROOT</code>
2200
* <li>Special cases for Chinese. When an input <code>Locale</code> has the
2201
* language "zh" (Chinese) and an empty script value, either "Hans" (Simplified) or
2202
* "Hant" (Traditional) might be supplied, depending on the country.
2203
* When the country is "CN" (China) or "SG" (Singapore), "Hans" is supplied.
2204
* When the country is "HK" (Hong Kong SAR China), "MO" (Macau SAR China),
2205
* or "TW" (Taiwan), "Hant" is supplied. For all other countries or when the country
2206
* is empty, no script is supplied. For example, for <code>Locale("zh", "CN")
2207
* </code>, the candidate list will be:
2209
* <li> [<em>L</em>("zh"), <em>S</em>("Hans"), <em>C</em>("CN")]
2210
* <li> [<em>L</em>("zh"), <em>S</em>("Hans")]
2211
* <li> [<em>L</em>("zh"), <em>C</em>("CN")]
2212
* <li> [<em>L</em>("zh")]
2213
* <li> <code>Locale.ROOT</code>
2216
* For <code>Locale("zh", "TW")</code>, the candidate list will be:
2218
* <li> [<em>L</em>("zh"), <em>S</em>("Hant"), <em>C</em>("TW")]
2219
* <li> [<em>L</em>("zh"), <em>S</em>("Hant")]
2220
* <li> [<em>L</em>("zh"), <em>C</em>("TW")]
2221
* <li> [<em>L</em>("zh")]
2222
* <li> <code>Locale.ROOT</code>
2225
* <li>Special cases for Norwegian. Both <code>Locale("no", "NO",
2226
* "NY")</code> and <code>Locale("nn", "NO")</code> represent Norwegian
2227
* Nynorsk. When a locale's language is "nn", the standard candidate
2228
* list is generated up to [<em>L</em>("nn")], and then the following
2229
* candidates are added:
2231
* <ul><li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("NY")]
2232
* <li> [<em>L</em>("no"), <em>C</em>("NO")]
2233
* <li> [<em>L</em>("no")]
2234
* <li> <code>Locale.ROOT</code>
2237
* If the locale is exactly <code>Locale("no", "NO", "NY")</code>, it is first
2238
* converted to <code>Locale("nn", "NO")</code> and then the above procedure is
2241
* <p>Also, Java treats the language "no" as a synonym of Norwegian
2242
* Bokmål "nb". Except for the single case <code>Locale("no",
2243
* "NO", "NY")</code> (handled above), when an input <code>Locale</code>
2244
* has language "no" or "nb", candidate <code>Locale</code>s with
2245
* language code "no" and "nb" are interleaved, first using the
2246
* requested language, then using its synonym. For example,
2247
* <code>Locale("nb", "NO", "POSIX")</code> generates the following
2251
* <li> [<em>L</em>("nb"), <em>C</em>("NO"), <em>V</em>("POSIX")]
2252
* <li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("POSIX")]
2253
* <li> [<em>L</em>("nb"), <em>C</em>("NO")]
2254
* <li> [<em>L</em>("no"), <em>C</em>("NO")]
2255
* <li> [<em>L</em>("nb")]
2256
* <li> [<em>L</em>("no")]
2257
* <li> <code>Locale.ROOT</code>
2260
* <code>Locale("no", "NO", "POSIX")</code> would generate the same list
2261
* except that locales with "no" would appear before the corresponding
2262
* locales with "nb".</li>
2267
* <p>The default implementation uses an {@link ArrayList} that
2268
* overriding implementations may modify before returning it to the
2269
* caller. However, a subclass must not modify it after it has
2270
* been returned by <code>getCandidateLocales</code>.
2272
* <p>For example, if the given <code>baseName</code> is "Messages"
2273
* and the given <code>locale</code> is
2274
* <code>Locale("ja", "", "XX")</code>, then a
2275
* <code>List</code> of <code>Locale</code>s:
2277
* Locale("ja", "", "XX")
2281
* is returned. And if the resource bundles for the "ja" and
2282
* "" <code>Locale</code>s are found, then the runtime resource
2283
* lookup path (parent chain) is:
2285
* Messages_ja -> Messages
2289
* the base name of the resource bundle, a fully
2290
* qualified class name
2292
* the locale for which a resource bundle is desired
2293
* @return a <code>List</code> of candidate
2294
* <code>Locale</code>s for the given <code>locale</code>
2295
* @exception NullPointerException
2296
* if <code>baseName</code> or <code>locale</code> is
2299
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
2300
if (baseName == null) {
2301
throw new NullPointerException();
2303
return new ArrayList<>(CANDIDATES_CACHE.get(locale.getBaseLocale()));
2306
private static final CandidateListCache CANDIDATES_CACHE = new CandidateListCache();
2308
private static class CandidateListCache extends LocaleObjectCache<BaseLocale, List<Locale>> {
2309
protected List<Locale> createObject(BaseLocale base) {
2310
String language = base.getLanguage();
2311
String script = base.getScript();
2312
String region = base.getRegion();
2313
String variant = base.getVariant();
2315
// Special handling for Norwegian
2316
boolean isNorwegianBokmal = false;
2317
boolean isNorwegianNynorsk = false;
2318
if (language.equals("no")) {
2319
if (region.equals("NO") && variant.equals("NY")) {
2321
isNorwegianNynorsk = true;
2323
isNorwegianBokmal = true;
2326
if (language.equals("nb") || isNorwegianBokmal) {
2327
List<Locale> tmpList = getDefaultList("nb", script, region, variant);
2328
// Insert a locale replacing "nb" with "no" for every list entry
2329
List<Locale> bokmalList = new LinkedList<>();
2330
for (Locale l : tmpList) {
2332
if (l.getLanguage().length() == 0) {
2335
bokmalList.add(Locale.getInstance("no", l.getScript(), l.getCountry(),
2336
l.getVariant(), null));
2339
} else if (language.equals("nn") || isNorwegianNynorsk) {
2340
// Insert no_NO_NY, no_NO, no after nn
2341
List<Locale> nynorskList = getDefaultList("nn", script, region, variant);
2342
int idx = nynorskList.size() - 1;
2343
nynorskList.add(idx++, Locale.getInstance("no", "NO", "NY"));
2344
nynorskList.add(idx++, Locale.getInstance("no", "NO", ""));
2345
nynorskList.add(idx++, Locale.getInstance("no", "", ""));
2348
// Special handling for Chinese
2349
else if (language.equals("zh")) {
2350
if (script.length() == 0 && region.length() > 0) {
2351
// Supply script for users who want to use zh_Hans/zh_Hant
2352
// as bundle names (recommended for Java7+)
2353
if (region.equals("TW") || region.equals("HK") || region.equals("MO")) {
2355
} else if (region.equals("CN") || region.equals("SG")) {
2358
} else if (script.length() > 0 && region.length() == 0) {
2359
// Supply region(country) for users who still package Chinese
2360
// bundles using old convension.
2361
if (script.equals("Hans")) {
2363
} else if (script.equals("Hant")) {
2369
return getDefaultList(language, script, region, variant);
2372
private static List<Locale> getDefaultList(String language, String script, String region, String variant) {
2373
List<String> variants = null;
2375
if (variant.length() > 0) {
2376
variants = new LinkedList<>();
2377
int idx = variant.length();
2379
variants.add(variant.substring(0, idx));
2380
idx = variant.lastIndexOf('_', --idx);
2384
List<Locale> list = new LinkedList<>();
2386
if (variants != null) {
2387
for (String v : variants) {
2388
list.add(Locale.getInstance(language, script, region, v, null));
2391
if (region.length() > 0) {
2392
list.add(Locale.getInstance(language, script, region, "", null));
2394
if (script.length() > 0) {
2395
list.add(Locale.getInstance(language, script, "", "", null));
2397
// With script, after truncating variant, region and script,
2398
// start over without script.
2399
if (variants != null) {
2400
for (String v : variants) {
2401
list.add(Locale.getInstance(language, "", region, v, null));
2404
if (region.length() > 0) {
2405
list.add(Locale.getInstance(language, "", region, "", null));
2408
if (language.length() > 0) {
2409
list.add(Locale.getInstance(language, "", "", "", null));
2411
// Add root locale at the end
2412
list.add(Locale.ROOT);
2419
* Returns a <code>Locale</code> to be used as a fallback locale for
2420
* further resource bundle searches by the
2421
* <code>ResourceBundle.getBundle</code> factory method. This method
2422
* is called from the factory method every time when no resulting
2423
* resource bundle has been found for <code>baseName</code> and
2424
* <code>locale</code>, where locale is either the parameter for
2425
* <code>ResourceBundle.getBundle</code> or the previous fallback
2426
* locale returned by this method.
2428
* <p>The method returns <code>null</code> if no further fallback
2429
* search is desired.
2431
* <p>The default implementation returns the {@linkplain
2432
* Locale#getDefault() default <code>Locale</code>} if the given
2433
* <code>locale</code> isn't the default one. Otherwise,
2434
* <code>null</code> is returned.
2437
* the base name of the resource bundle, a fully
2438
* qualified class name for which
2439
* <code>ResourceBundle.getBundle</code> has been
2440
* unable to find any resource bundles (except for the
2443
* the <code>Locale</code> for which
2444
* <code>ResourceBundle.getBundle</code> has been
2445
* unable to find any resource bundles (except for the
2447
* @return a <code>Locale</code> for the fallback search,
2448
* or <code>null</code> if no further fallback search
2450
* @exception NullPointerException
2451
* if <code>baseName</code> or <code>locale</code>
2452
* is <code>null</code>
2454
public Locale getFallbackLocale(String baseName, Locale locale) {
2455
if (baseName == null) {
2456
throw new NullPointerException();
2458
Locale defaultLocale = Locale.getDefault();
2459
return locale.equals(defaultLocale) ? null : defaultLocale;
2463
* Instantiates a resource bundle for the given bundle name of the
2464
* given format and locale, using the given class loader if
2465
* necessary. This method returns <code>null</code> if there is no
2466
* resource bundle available for the given parameters. If a resource
2467
* bundle can't be instantiated due to an unexpected error, the
2468
* error must be reported by throwing an <code>Error</code> or
2469
* <code>Exception</code> rather than simply returning
2470
* <code>null</code>.
2472
* <p>If the <code>reload</code> flag is <code>true</code>, it
2473
* indicates that this method is being called because the previously
2474
* loaded resource bundle has expired.
2476
* <p>The default implementation instantiates a
2477
* <code>ResourceBundle</code> as follows.
2481
* <li>The bundle name is obtained by calling {@link
2482
* #toBundleName(String, Locale) toBundleName(baseName,
2485
* <li>If <code>format</code> is <code>"java.class"</code>, the
2486
* {@link Class} specified by the bundle name is loaded by calling
2487
* {@link ClassLoader#loadClass(String)}. Then, a
2488
* <code>ResourceBundle</code> is instantiated by calling {@link
2489
* Class#newInstance()}. Note that the <code>reload</code> flag is
2490
* ignored for loading class-based resource bundles in this default
2491
* implementation.</li>
2493
* <li>If <code>format</code> is <code>"java.properties"</code>,
2494
* {@link #toResourceName(String, String) toResourceName(bundlename,
2495
* "properties")} is called to get the resource name.
2496
* If <code>reload</code> is <code>true</code>, {@link
2497
* ClassLoader#getResource(String) load.getResource} is called
2498
* to get a {@link URL} for creating a {@link
2499
* URLConnection}. This <code>URLConnection</code> is used to
2500
* {@linkplain URLConnection#setUseCaches(boolean) disable the
2501
* caches} of the underlying resource loading layers,
2502
* and to {@linkplain URLConnection#getInputStream() get an
2503
* <code>InputStream</code>}.
2504
* Otherwise, {@link ClassLoader#getResourceAsStream(String)
2505
* loader.getResourceAsStream} is called to get an {@link
2506
* InputStream}. Then, a {@link
2507
* PropertyResourceBundle} is constructed with the
2508
* <code>InputStream</code>.</li>
2510
* <li>If <code>format</code> is neither <code>"java.class"</code>
2511
* nor <code>"java.properties"</code>, an
2512
* <code>IllegalArgumentException</code> is thrown.</li>
2517
* the base bundle name of the resource bundle, a fully
2518
* qualified class name
2520
* the locale for which the resource bundle should be
2523
* the resource bundle format to be loaded
2525
* the <code>ClassLoader</code> to use to load the bundle
2527
* the flag to indicate bundle reloading; <code>true</code>
2528
* if reloading an expired resource bundle,
2529
* <code>false</code> otherwise
2530
* @return the resource bundle instance,
2531
* or <code>null</code> if none could be found.
2532
* @exception NullPointerException
2533
* if <code>bundleName</code>, <code>locale</code>,
2534
* <code>format</code>, or <code>loader</code> is
2535
* <code>null</code>, or if <code>null</code> is returned by
2536
* {@link #toBundleName(String, Locale) toBundleName}
2537
* @exception IllegalArgumentException
2538
* if <code>format</code> is unknown, or if the resource
2539
* found for the given parameters contains malformed data.
2540
* @exception ClassCastException
2541
* if the loaded class cannot be cast to <code>ResourceBundle</code>
2542
* @exception IllegalAccessException
2543
* if the class or its nullary constructor is not
2545
* @exception InstantiationException
2546
* if the instantiation of a class fails for some other
2548
* @exception ExceptionInInitializerError
2549
* if the initialization provoked by this method fails.
2550
* @exception SecurityException
2551
* If a security manager is present and creation of new
2552
* instances is denied. See {@link Class#newInstance()}
2554
* @exception IOException
2555
* if an error occurred when reading resources using
2556
* any I/O operations
2558
public ResourceBundle newBundle(String baseName, Locale locale, String format,
2559
ClassLoader loader, boolean reload)
2560
throws IllegalAccessException, InstantiationException, IOException {
2561
String bundleName = toBundleName(baseName, locale);
2562
ResourceBundle bundle = null;
2563
if (format.equals("java.class")) {
2565
Class<? extends ResourceBundle> bundleClass
2566
= (Class<? extends ResourceBundle>)loader.loadClass(bundleName);
2568
// If the class isn't a ResourceBundle subclass, throw a
2569
// ClassCastException.
2570
if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
2571
bundle = bundleClass.newInstance();
2573
throw new ClassCastException(bundleClass.getName()
2574
+ " cannot be cast to ResourceBundle");
2576
} catch (ClassNotFoundException e) {
2578
} else if (format.equals("java.properties")) {
2579
final String resourceName = toResourceName(bundleName, "properties");
2580
final ClassLoader classLoader = loader;
2581
final boolean reloadFlag = reload;
2582
InputStream stream = null;
2584
stream = AccessController.doPrivileged(
2585
new PrivilegedExceptionAction<InputStream>() {
2586
public InputStream run() throws IOException {
2587
InputStream is = null;
2589
URL url = classLoader.getResource(resourceName);
2591
URLConnection connection = url.openConnection();
2592
if (connection != null) {
2593
// Disable caches to get fresh data for
2595
connection.setUseCaches(false);
2596
is = connection.getInputStream();
2600
is = classLoader.getResourceAsStream(resourceName);
2605
} catch (PrivilegedActionException e) {
2606
throw (IOException) e.getException();
2608
if (stream != null) {
2610
bundle = new PropertyResourceBundle(stream);
2616
throw new IllegalArgumentException("unknown format: " + format);
2622
* Returns the time-to-live (TTL) value for resource bundles that
2623
* are loaded under this
2624
* <code>ResourceBundle.Control</code>. Positive time-to-live values
2625
* specify the number of milliseconds a bundle can remain in the
2626
* cache without being validated against the source data from which
2627
* it was constructed. The value 0 indicates that a bundle must be
2628
* validated each time it is retrieved from the cache. {@link
2629
* #TTL_DONT_CACHE} specifies that loaded resource bundles are not
2630
* put in the cache. {@link #TTL_NO_EXPIRATION_CONTROL} specifies
2631
* that loaded resource bundles are put in the cache with no
2632
* expiration control.
2634
* <p>The expiration affects only the bundle loading process by the
2635
* <code>ResourceBundle.getBundle</code> factory method. That is,
2636
* if the factory method finds a resource bundle in the cache that
2637
* has expired, the factory method calls the {@link
2638
* #needsReload(String, Locale, String, ClassLoader, ResourceBundle,
2639
* long) needsReload} method to determine whether the resource
2640
* bundle needs to be reloaded. If <code>needsReload</code> returns
2641
* <code>true</code>, the cached resource bundle instance is removed
2642
* from the cache. Otherwise, the instance stays in the cache,
2643
* updated with the new TTL value returned by this method.
2645
* <p>All cached resource bundles are subject to removal from the
2646
* cache due to memory constraints of the runtime environment.
2647
* Returning a large positive value doesn't mean to lock loaded
2648
* resource bundles in the cache.
2650
* <p>The default implementation returns {@link #TTL_NO_EXPIRATION_CONTROL}.
2653
* the base name of the resource bundle for which the
2654
* expiration value is specified.
2656
* the locale of the resource bundle for which the
2657
* expiration value is specified.
2658
* @return the time (0 or a positive millisecond offset from the
2659
* cached time) to get loaded bundles expired in the cache,
2660
* {@link #TTL_NO_EXPIRATION_CONTROL} to disable the
2661
* expiration control, or {@link #TTL_DONT_CACHE} to disable
2663
* @exception NullPointerException
2664
* if <code>baseName</code> or <code>locale</code> is
2667
public long getTimeToLive(String baseName, Locale locale) {
2668
if (baseName == null || locale == null) {
2669
throw new NullPointerException();
2671
return TTL_NO_EXPIRATION_CONTROL;
2675
* Determines if the expired <code>bundle</code> in the cache needs
2676
* to be reloaded based on the loading time given by
2677
* <code>loadTime</code> or some other criteria. The method returns
2678
* <code>true</code> if reloading is required; <code>false</code>
2679
* otherwise. <code>loadTime</code> is a millisecond offset since
2680
* the <a href="Calendar.html#Epoch"> <code>Calendar</code>
2683
* The calling <code>ResourceBundle.getBundle</code> factory method
2684
* calls this method on the <code>ResourceBundle.Control</code>
2685
* instance used for its current invocation, not on the instance
2686
* used in the invocation that originally loaded the resource
2689
* <p>The default implementation compares <code>loadTime</code> and
2690
* the last modified time of the source data of the resource
2691
* bundle. If it's determined that the source data has been modified
2692
* since <code>loadTime</code>, <code>true</code> is
2693
* returned. Otherwise, <code>false</code> is returned. This
2694
* implementation assumes that the given <code>format</code> is the
2695
* same string as its file suffix if it's not one of the default
2696
* formats, <code>"java.class"</code> or
2697
* <code>"java.properties"</code>.
2700
* the base bundle name of the resource bundle, a
2701
* fully qualified class name
2703
* the locale for which the resource bundle
2704
* should be instantiated
2706
* the resource bundle format to be loaded
2708
* the <code>ClassLoader</code> to use to load the bundle
2710
* the resource bundle instance that has been expired
2713
* the time when <code>bundle</code> was loaded and put
2715
* @return <code>true</code> if the expired bundle needs to be
2716
* reloaded; <code>false</code> otherwise.
2717
* @exception NullPointerException
2718
* if <code>baseName</code>, <code>locale</code>,
2719
* <code>format</code>, <code>loader</code>, or
2720
* <code>bundle</code> is <code>null</code>
2722
public boolean needsReload(String baseName, Locale locale,
2723
String format, ClassLoader loader,
2724
ResourceBundle bundle, long loadTime) {
2725
if (bundle == null) {
2726
throw new NullPointerException();
2728
if (format.equals("java.class") || format.equals("java.properties")) {
2729
format = format.substring(5);
2731
boolean result = false;
2733
String resourceName = toResourceName(toBundleName(baseName, locale), format);
2734
URL url = loader.getResource(resourceName);
2736
long lastModified = 0;
2737
URLConnection connection = url.openConnection();
2738
if (connection != null) {
2739
// disable caches to get the correct data
2740
connection.setUseCaches(false);
2741
if (connection instanceof JarURLConnection) {
2742
JarEntry ent = ((JarURLConnection)connection).getJarEntry();
2744
lastModified = ent.getTime();
2745
if (lastModified == -1) {
2750
lastModified = connection.getLastModified();
2753
result = lastModified >= loadTime;
2755
} catch (NullPointerException npe) {
2757
} catch (Exception e) {
2758
// ignore other exceptions
2764
* Converts the given <code>baseName</code> and <code>locale</code>
2765
* to the bundle name. This method is called from the default
2766
* implementation of the {@link #newBundle(String, Locale, String,
2767
* ClassLoader, boolean) newBundle} and {@link #needsReload(String,
2768
* Locale, String, ClassLoader, ResourceBundle, long) needsReload}
2771
* <p>This implementation returns the following value:
2773
* baseName + "_" + language + "_" + script + "_" + country + "_" + variant
2775
* where <code>language</code>, <code>script</code>, <code>country</code>,
2776
* and <code>variant</code> are the language, script, country, and variant
2777
* values of <code>locale</code>, respectively. Final component values that
2778
* are empty Strings are omitted along with the preceding '_'. When the
2779
* script is empty, the script value is ommitted along with the preceding '_'.
2780
* If all of the values are empty strings, then <code>baseName</code>
2783
* <p>For example, if <code>baseName</code> is
2784
* <code>"baseName"</code> and <code>locale</code> is
2785
* <code>Locale("ja", "", "XX")</code>, then
2786
* <code>"baseName_ja_ _XX"</code> is returned. If the given
2787
* locale is <code>Locale("en")</code>, then
2788
* <code>"baseName_en"</code> is returned.
2790
* <p>Overriding this method allows applications to use different
2791
* conventions in the organization and packaging of localized
2795
* the base name of the resource bundle, a fully
2796
* qualified class name
2798
* the locale for which a resource bundle should be
2800
* @return the bundle name for the resource bundle
2801
* @exception NullPointerException
2802
* if <code>baseName</code> or <code>locale</code>
2803
* is <code>null</code>
2805
public String toBundleName(String baseName, Locale locale) {
2806
if (locale == Locale.ROOT) {
2810
String language = locale.getLanguage();
2811
String script = locale.getScript();
2812
String country = locale.getCountry();
2813
String variant = locale.getVariant();
2815
if (language == "" && country == "" && variant == "") {
2819
StringBuilder sb = new StringBuilder(baseName);
2822
if (variant != "") {
2823
sb.append(language).append('_').append(script).append('_').append(country).append('_').append(variant);
2824
} else if (country != "") {
2825
sb.append(language).append('_').append(script).append('_').append(country);
2827
sb.append(language).append('_').append(script);
2830
if (variant != "") {
2831
sb.append(language).append('_').append(country).append('_').append(variant);
2832
} else if (country != "") {
2833
sb.append(language).append('_').append(country);
2835
sb.append(language);
2838
return sb.toString();
2843
* Converts the given <code>bundleName</code> to the form required
2844
* by the {@link ClassLoader#getResource ClassLoader.getResource}
2845
* method by replacing all occurrences of <code>'.'</code> in
2846
* <code>bundleName</code> with <code>'/'</code> and appending a
2847
* <code>'.'</code> and the given file <code>suffix</code>. For
2848
* example, if <code>bundleName</code> is
2849
* <code>"foo.bar.MyResources_ja_JP"</code> and <code>suffix</code>
2850
* is <code>"properties"</code>, then
2851
* <code>"foo/bar/MyResources_ja_JP.properties"</code> is returned.
2856
* the file type suffix
2857
* @return the converted resource name
2858
* @exception NullPointerException
2859
* if <code>bundleName</code> or <code>suffix</code>
2860
* is <code>null</code>
2862
public final String toResourceName(String bundleName, String suffix) {
2863
StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length());
2864
sb.append(bundleName.replace('.', '/')).append('.').append(suffix);
2865
return sb.toString();
2869
private static class SingleFormatControl extends Control {
2870
private static final Control PROPERTIES_ONLY
2871
= new SingleFormatControl(FORMAT_PROPERTIES);
2873
private static final Control CLASS_ONLY
2874
= new SingleFormatControl(FORMAT_CLASS);
2876
private final List<String> formats;
2878
protected SingleFormatControl(List<String> formats) {
2879
this.formats = formats;
2882
public List<String> getFormats(String baseName) {
2883
if (baseName == null) {
2884
throw new NullPointerException();
2890
private static final class NoFallbackControl extends SingleFormatControl {
2891
private static final Control NO_FALLBACK
2892
= new NoFallbackControl(FORMAT_DEFAULT);
2894
private static final Control PROPERTIES_ONLY_NO_FALLBACK
2895
= new NoFallbackControl(FORMAT_PROPERTIES);
2897
private static final Control CLASS_ONLY_NO_FALLBACK
2898
= new NoFallbackControl(FORMAT_CLASS);
2900
protected NoFallbackControl(List<String> formats) {
2904
public Locale getFallbackLocale(String baseName, Locale locale) {
2905
if (baseName == null || locale == null) {
2906
throw new NullPointerException();