~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to external/ikvm/openjdk/java/util/ResourceBundle.java

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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.
 
4
 *
 
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.
 
10
 *
 
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).
 
16
 *
 
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.
 
20
 *
 
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
 
23
 * questions.
 
24
 */
 
25
 
 
26
/*
 
27
 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
 
28
 * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
 
29
 *
 
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.
 
35
 *
 
36
 * This notice and attribution to Taligent may not be removed.
 
37
 * Taligent is a registered trademark of Taligent, Inc.
 
38
 *
 
39
 */
 
40
 
 
41
package java.util;
 
42
 
 
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;
 
49
import java.net.URL;
 
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;
 
61
 
 
62
 
 
63
/**
 
64
 *
 
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.
 
71
 *
 
72
 * <p>
 
73
 * This allows you to write programs that can:
 
74
 * <UL type=SQUARE>
 
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
 
78
 * </UL>
 
79
 *
 
80
 * <P>
 
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".
 
90
 *
 
91
 * <P>
 
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".
 
98
 *
 
99
 * <P>
 
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.
 
105
 *
 
106
 * <P>
 
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}
 
110
 * method:
 
111
 * <blockquote>
 
112
 * <pre>
 
113
 * ResourceBundle myResources =
 
114
 *      ResourceBundle.getBundle("MyResources", currentLocale);
 
115
 * </pre>
 
116
 * </blockquote>
 
117
 *
 
118
 * <P>
 
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:
 
123
 * <blockquote>
 
124
 * <pre>
 
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")
 
129
 *             {"OkKey", "OK"},
 
130
 *             {"CancelKey", "Cancel"},
 
131
 *             // END OF MATERIAL TO LOCALIZE
 
132
 *        };
 
133
 *     }
 
134
 * }
 
135
 * </pre>
 
136
 * </blockquote>
 
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.
 
142
 *
 
143
 * <P>
 
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:
 
147
 * <blockquote>
 
148
 * <pre>
 
149
 * button1 = new Button(myResources.getString("OkKey"));
 
150
 * button2 = new Button(myResources.getString("CancelKey"));
 
151
 * </pre>
 
152
 * </blockquote>
 
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>.
 
156
 *
 
157
 * <P>
 
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:
 
163
 * <blockquote>
 
164
 * <pre>
 
165
 * int[] myIntegers = (int[]) myResources.getObject("intList");
 
166
 * </pre>
 
167
 * </blockquote>
 
168
 *
 
169
 * <P>
 
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
 
176
 * its resources.
 
177
 *
 
178
 * <p>
 
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>.
 
183
 *
 
184
 * <h4>ResourceBundle.Control</h4>
 
185
 *
 
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.
 
194
 *
 
195
 * <h4>Cache Management</h4>
 
196
 *
 
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.
 
210
 *
 
211
 * <h4>Example</h4>
 
212
 *
 
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).
 
219
 * <blockquote>
 
220
 * <pre>
 
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";
 
226
 *         return null;
 
227
 *     }
 
228
 *
 
229
 *     public Enumeration&lt;String&gt; getKeys() {
 
230
 *         return Collections.enumeration(keySet());
 
231
 *     }
 
232
 *
 
233
 *     // Overrides handleKeySet() so that the getKeys() implementation
 
234
 *     // can rely on the keySet() value.
 
235
 *     protected Set&lt;String&gt; handleKeySet() {
 
236
 *         return new HashSet&lt;String&gt;(Arrays.asList("okKey", "cancelKey"));
 
237
 *     }
 
238
 * }
 
239
 *
 
240
 * // German language
 
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";
 
245
 *         return null;
 
246
 *     }
 
247
 *
 
248
 *     protected Set&lt;String&gt; handleKeySet() {
 
249
 *         return new HashSet&lt;String&gt;(Arrays.asList("cancelKey"));
 
250
 *     }
 
251
 * }
 
252
 * </pre>
 
253
 * </blockquote>
 
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.
 
260
 *
 
261
 * @see ListResourceBundle
 
262
 * @see PropertyResourceBundle
 
263
 * @see MissingResourceException
 
264
 * @since JDK1.1
 
265
 */
 
266
public abstract class ResourceBundle {
 
267
 
 
268
    /** initial size of the bundle cache */
 
269
    private static final int INITIAL_CACHE_SIZE = 32;
 
270
 
 
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"; }
 
276
        };
 
277
 
 
278
 
 
279
    /**
 
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
 
282
     * BundleReference.
 
283
     *
 
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.
 
287
     *
 
288
     * This variable would be better named "cache", but we keep the old
 
289
     * name for compatibility with some workarounds for bug 4212439.
 
290
     */
 
291
    private static final ConcurrentMap<CacheKey, BundleReference> cacheList
 
292
        = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
 
293
 
 
294
    /**
 
295
     * Queue for reference objects referring to class loaders or bundles.
 
296
     */
 
297
    private static final ReferenceQueue referenceQueue = new ReferenceQueue();
 
298
 
 
299
    /**
 
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.
 
303
     */
 
304
    protected ResourceBundle parent = null;
 
305
 
 
306
    /**
 
307
     * The locale for this bundle.
 
308
     */
 
309
    private Locale locale = null;
 
310
 
 
311
    /**
 
312
     * The base bundle name for this bundle.
 
313
     */
 
314
    private String name;
 
315
 
 
316
    /**
 
317
     * The flag indicating this bundle has expired in the cache.
 
318
     */
 
319
    private volatile boolean expired;
 
320
 
 
321
    /**
 
322
     * The back link to the cache key. null if this bundle isn't in
 
323
     * the cache (yet) or has expired.
 
324
     */
 
325
    private volatile CacheKey cacheKey;
 
326
 
 
327
    /**
 
328
     * A Set of the keys contained only in this ResourceBundle.
 
329
     */
 
330
    private volatile Set<String> keySet;
 
331
 
 
332
    /**
 
333
     * Sole constructor.  (For invocation by subclass constructors, typically
 
334
     * implicit.)
 
335
     */
 
336
    public ResourceBundle() {
 
337
    }
 
338
 
 
339
    /**
 
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
 
342
     * <blockquote>
 
343
     * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>.
 
344
     * </blockquote>
 
345
     *
 
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
 
351
     */
 
352
    public final String getString(String key) {
 
353
        return (String) getObject(key);
 
354
    }
 
355
 
 
356
    /**
 
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
 
359
     * <blockquote>
 
360
     * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>.
 
361
     * </blockquote>
 
362
     *
 
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
 
368
     */
 
369
    public final String[] getStringArray(String key) {
 
370
        return (String[]) getObject(key);
 
371
    }
 
372
 
 
373
    /**
 
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.
 
380
     *
 
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
 
385
     */
 
386
    public final Object getObject(String key) {
 
387
        Object obj = handleGetObject(key);
 
388
        if (obj == null) {
 
389
            if (parent != null) {
 
390
                obj = parent.getObject(key);
 
391
            }
 
392
            if (obj == null)
 
393
                throw new MissingResourceException("Can't find resource for bundle "
 
394
                                                   +this.getClass().getName()
 
395
                                                   +", key "+key,
 
396
                                                   this.getClass().getName(),
 
397
                                                   key);
 
398
        }
 
399
        return obj;
 
400
    }
 
401
 
 
402
    /**
 
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.
 
406
     *
 
407
     * @return the locale of this resource bundle
 
408
     */
 
409
    public Locale getLocale() {
 
410
        return locale;
 
411
    }
 
412
 
 
413
    /*
 
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
 
416
     * caller's caller.
 
417
     */
 
418
    private static ClassLoader getLoader(Class c) {
 
419
        ClassLoader cl = (c == null) ? null : c.getClassLoader();
 
420
        if (cl == null) {
 
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;
 
429
        }
 
430
        return cl;
 
431
    }
 
432
 
 
433
    /**
 
434
     * A wrapper of ClassLoader.getSystemClassLoader().
 
435
     */
 
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();
 
441
                        }
 
442
                    });
 
443
        private static final ClassLoader loader = ClassLoader.getSystemClassLoader();
 
444
 
 
445
        private RBClassLoader() {
 
446
        }
 
447
        public Class<?> loadClass(String name) throws ClassNotFoundException {
 
448
            if (loader != null) {
 
449
                return loader.loadClass(name);
 
450
            }
 
451
            return Class.forName(name);
 
452
        }
 
453
        public URL getResource(String name) {
 
454
            if (loader != null) {
 
455
                return loader.getResource(name);
 
456
            }
 
457
            return ClassLoader.getSystemResource(name);
 
458
        }
 
459
        public InputStream getResourceAsStream(String name) {
 
460
            if (loader != null) {
 
461
                return loader.getResourceAsStream(name);
 
462
            }
 
463
            return ClassLoader.getSystemResourceAsStream(name);
 
464
        }
 
465
    }
 
466
 
 
467
    /**
 
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.
 
471
     *
 
472
     * @param parent this bundle's parent bundle.
 
473
     */
 
474
    protected void setParent(ResourceBundle parent) {
 
475
        assert parent != NONEXISTENT_BUNDLE;
 
476
        this.parent = parent;
 
477
    }
 
478
 
 
479
    /**
 
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
 
484
     * value.
 
485
     */
 
486
    private static final class CacheKey implements Cloneable {
 
487
        // These three are the actual keys for lookup in Map.
 
488
        private String name;
 
489
        private Locale locale;
 
490
        private LoaderReference loaderRef;
 
491
 
 
492
        // bundle format which is necessary for calling
 
493
        // Control.needsReload().
 
494
        private String format;
 
495
 
 
496
        // These time values are in CacheKey so that NONEXISTENT_BUNDLE
 
497
        // doesn't need to be cloned for caching.
 
498
 
 
499
        // The time when the bundle has been loaded
 
500
        private volatile long loadTime;
 
501
 
 
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;
 
505
 
 
506
        // Placeholder for an error report by a Throwable
 
507
        private Throwable cause;
 
508
 
 
509
        // Hash code value cache to avoid recalculating the hash code
 
510
        // of this instance.
 
511
        private int hashCodeCache;
 
512
 
 
513
        CacheKey(String baseName, Locale locale, ClassLoader loader) {
 
514
            this.name = baseName;
 
515
            this.locale = locale;
 
516
            if (loader == null) {
 
517
                this.loaderRef = null;
 
518
            } else {
 
519
                loaderRef = new LoaderReference(loader, referenceQueue, this);
 
520
            }
 
521
            calculateHashCode();
 
522
        }
 
523
 
 
524
        String getName() {
 
525
            return name;
 
526
        }
 
527
 
 
528
        CacheKey setName(String baseName) {
 
529
            if (!this.name.equals(baseName)) {
 
530
                this.name = baseName;
 
531
                calculateHashCode();
 
532
            }
 
533
            return this;
 
534
        }
 
535
 
 
536
        Locale getLocale() {
 
537
            return locale;
 
538
        }
 
539
 
 
540
        CacheKey setLocale(Locale locale) {
 
541
            if (!this.locale.equals(locale)) {
 
542
                this.locale = locale;
 
543
                calculateHashCode();
 
544
            }
 
545
            return this;
 
546
        }
 
547
 
 
548
        ClassLoader getLoader() {
 
549
            return (loaderRef != null) ? loaderRef.get() : null;
 
550
        }
 
551
 
 
552
        public boolean equals(Object other) {
 
553
            if (this == other) {
 
554
                return true;
 
555
            }
 
556
            try {
 
557
                final CacheKey otherEntry = (CacheKey)other;
 
558
                //quick check to see if they are not equal
 
559
                if (hashCodeCache != otherEntry.hashCodeCache) {
 
560
                    return false;
 
561
                }
 
562
                //are the names the same?
 
563
                if (!name.equals(otherEntry.name)) {
 
564
                    return false;
 
565
                }
 
566
                // are the locales the same?
 
567
                if (!locale.equals(otherEntry.locale)) {
 
568
                    return false;
 
569
                }
 
570
                //are refs (both non-null) or (both null)?
 
571
                if (loaderRef == null) {
 
572
                    return otherEntry.loaderRef == null;
 
573
                }
 
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
 
579
                        && (loader != null)
 
580
                        && (loader == otherEntry.loaderRef.get());
 
581
            } catch (NullPointerException e) {
 
582
            } catch (ClassCastException e) {
 
583
            }
 
584
            return false;
 
585
        }
 
586
 
 
587
        public int hashCode() {
 
588
            return hashCodeCache;
 
589
        }
 
590
 
 
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();
 
597
            }
 
598
        }
 
599
 
 
600
        public Object clone() {
 
601
            try {
 
602
                CacheKey clone = (CacheKey) super.clone();
 
603
                if (loaderRef != null) {
 
604
                    clone.loaderRef = new LoaderReference(loaderRef.get(),
 
605
                                                          referenceQueue, clone);
 
606
                }
 
607
                // Clear the reference to a Throwable
 
608
                clone.cause = null;
 
609
                return clone;
 
610
            } catch (CloneNotSupportedException e) {
 
611
                //this should never happen
 
612
                throw new InternalError();
 
613
            }
 
614
        }
 
615
 
 
616
        String getFormat() {
 
617
            return format;
 
618
        }
 
619
 
 
620
        void setFormat(String format) {
 
621
            this.format = format;
 
622
        }
 
623
 
 
624
        private void setCause(Throwable cause) {
 
625
            if (this.cause == null) {
 
626
                this.cause = cause;
 
627
            } else {
 
628
                // Override the cause if the previous one is
 
629
                // ClassNotFoundException.
 
630
                if (this.cause instanceof ClassNotFoundException) {
 
631
                    this.cause = cause;
 
632
                }
 
633
            }
 
634
        }
 
635
 
 
636
        private Throwable getCause() {
 
637
            return cause;
 
638
        }
 
639
 
 
640
        public String toString() {
 
641
            String l = locale.toString();
 
642
            if (l.length() == 0) {
 
643
                if (locale.getVariant().length() != 0) {
 
644
                    l = "__" + locale.getVariant();
 
645
                } else {
 
646
                    l = "\"\"";
 
647
                }
 
648
            }
 
649
            return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader()
 
650
                + "(format=" + format + ")]";
 
651
        }
 
652
    }
 
653
 
 
654
    /**
 
655
     * The common interface to get a CacheKey in LoaderReference and
 
656
     * BundleReference.
 
657
     */
 
658
    private static interface CacheKeyReference {
 
659
        public CacheKey getCacheKey();
 
660
    }
 
661
 
 
662
    /**
 
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.
 
666
     */
 
667
    private static final class LoaderReference extends WeakReference<ClassLoader>
 
668
                                               implements CacheKeyReference {
 
669
        private CacheKey cacheKey;
 
670
 
 
671
        LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) {
 
672
            super(referent, q);
 
673
            cacheKey = key;
 
674
        }
 
675
 
 
676
        public CacheKey getCacheKey() {
 
677
            return cacheKey;
 
678
        }
 
679
    }
 
680
 
 
681
    /**
 
682
     * References to bundles are soft references so that they can be garbage
 
683
     * collected when they have no hard references.
 
684
     */
 
685
    private static final class BundleReference extends SoftReference<ResourceBundle>
 
686
                                               implements CacheKeyReference {
 
687
        private CacheKey cacheKey;
 
688
 
 
689
        BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) {
 
690
            super(referent, q);
 
691
            cacheKey = key;
 
692
        }
 
693
 
 
694
        public CacheKey getCacheKey() {
 
695
            return cacheKey;
 
696
        }
 
697
    }
 
698
 
 
699
    /**
 
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
 
702
     * <blockquote>
 
703
     * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>,
 
704
     * </blockquote>
 
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.
 
709
     *
 
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
 
716
     */
 
717
    @ikvm.internal.HasCallerID
 
718
    public static final ResourceBundle getBundle(String baseName)
 
719
    {
 
720
        return getBundleImpl(baseName, Locale.getDefault(),
 
721
                             /* must determine loader here, else we break stack invariant */
 
722
                             getLoader(Reflection.getCallerClass(2)),
 
723
                             Control.INSTANCE);
 
724
    }
 
725
 
 
726
    /**
 
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
 
730
     * <pre>
 
731
     * getBundle(baseName, Locale.getDefault(),
 
732
     *           this.getClass().getClassLoader(), control),
 
733
     * </pre>
 
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>.
 
739
     *
 
740
     * @param baseName
 
741
     *        the base name of the resource bundle, a fully qualified class
 
742
     *        name
 
743
     * @param control
 
744
     *        the control which gives information for the resource bundle
 
745
     *        loading process
 
746
     * @return a resource bundle for the given base name and the default
 
747
     *        locale
 
748
     * @exception NullPointerException
 
749
     *        if <code>baseName</code> or <code>control</code> is
 
750
     *        <code>null</code>
 
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
 
757
     *        needed.
 
758
     * @since 1.6
 
759
     */
 
760
    @ikvm.internal.HasCallerID
 
761
    public static final ResourceBundle getBundle(String baseName,
 
762
                                                 Control control) {
 
763
        return getBundleImpl(baseName, Locale.getDefault(),
 
764
                             /* must determine loader here, else we break stack invariant */
 
765
                             getLoader(Reflection.getCallerClass(2)),
 
766
                             control);
 
767
    }
 
768
 
 
769
    /**
 
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
 
772
     * <blockquote>
 
773
     * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>,
 
774
     * </blockquote>
 
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.
 
779
     *
 
780
     * @param baseName
 
781
     *        the base name of the resource bundle, a fully qualified class name
 
782
     * @param locale
 
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
 
789
     */
 
790
    @ikvm.internal.HasCallerID
 
791
    public static final ResourceBundle getBundle(String baseName,
 
792
                                                 Locale locale)
 
793
    {
 
794
        return getBundleImpl(baseName, locale,
 
795
                             /* must determine loader here, else we break stack invariant */
 
796
                             getLoader(Reflection.getCallerClass(2)),
 
797
                             Control.INSTANCE);
 
798
    }
 
799
 
 
800
    /**
 
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
 
804
     * <pre>
 
805
     * getBundle(baseName, targetLocale, this.getClass().getClassLoader(),
 
806
     *           control),
 
807
     * </pre>
 
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>.
 
813
     *
 
814
     * @param baseName
 
815
     *        the base name of the resource bundle, a fully qualified
 
816
     *        class name
 
817
     * @param targetLocale
 
818
     *        the locale for which a resource bundle is desired
 
819
     * @param control
 
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
 
834
     *        needed.
 
835
     * @since 1.6
 
836
     */
 
837
    @ikvm.internal.HasCallerID
 
838
    public static final ResourceBundle getBundle(String baseName, Locale targetLocale,
 
839
                                                 Control control) {
 
840
        return getBundleImpl(baseName, targetLocale,
 
841
                             /* must determine loader here, else we break stack invariant */
 
842
                             getLoader(Reflection.getCallerClass(2)),
 
843
                             control);
 
844
    }
 
845
 
 
846
    /**
 
847
     * Gets a resource bundle using the specified base name, locale, and class
 
848
     * loader.
 
849
     *
 
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.
 
853
     *
 
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:
 
863
     *
 
864
     * <pre>
 
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
 
871
     * </pre>
 
872
     *
 
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.
 
880
     *
 
881
     * <pre>
 
882
     *     MyResource_de__JAVA
 
883
     *     MyResource_de
 
884
     * </pre>
 
885
     *
 
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:
 
892
     *
 
893
     * <pre>
 
894
     * MyResource_en_Latn_US_WINDOWS_VISTA
 
895
     * MyResource_en_Latn_US_WINDOWS
 
896
     * MyResource_en_Latn_US
 
897
     * MyResource_en_Latn
 
898
     * MyResource_en_US_WINDOWS_VISTA
 
899
     * MyResource_en_US_WINDOWS
 
900
     * MyResource_en_US
 
901
     * MyResource_en
 
902
     * </pre>
 
903
     *
 
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>
 
909
     *
 
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:
 
916
     *
 
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
 
922
     * bundle</em>.
 
923
     *
 
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>
 
936
     *
 
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.
 
943
     *
 
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.
 
946
     *
 
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
 
950
     * complete.
 
951
     *
 
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).
 
959
     *
 
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.
 
964
     *
 
965
     * <p>Once the parent chain is complete, the bundle is returned.
 
966
     *
 
967
     * <p><b>Note:</b> <code>getBundle</code> caches instantiated resource
 
968
     * bundles and might return the same resource bundle instance multiple times.
 
969
     *
 
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
 
975
     * ".").
 
976
     *
 
977
     * <p><a name="default_behavior_example"/>
 
978
     * <strong>Example:</strong>
 
979
     * <p>
 
980
     * The following class and property files are provided:
 
981
     * <pre>
 
982
     *     MyResources.class
 
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
 
989
     * </pre>
 
990
     *
 
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>.
 
995
     *
 
996
     * <p>Calling <code>getBundle</code> with the locale arguments below will
 
997
     * instantiate resource bundles as follows:
 
998
     *
 
999
     * <table>
 
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>
 
1005
     * </table>
 
1006
     *
 
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.
 
1010
     *
 
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
 
1019
     * @since 1.2
 
1020
     */
 
1021
    public static ResourceBundle getBundle(String baseName, Locale locale,
 
1022
                                           ClassLoader loader)
 
1023
    {
 
1024
        if (loader == null) {
 
1025
            throw new NullPointerException();
 
1026
        }
 
1027
        return getBundleImpl(baseName, locale, loader, Control.INSTANCE);
 
1028
    }
 
1029
 
 
1030
    /**
 
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.
 
1038
     *
 
1039
     * <p>
 
1040
     * <ol>
 
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>
 
1048
     *
 
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>
 
1058
     *
 
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>
 
1063
     *
 
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>.
 
1077
     *
 
1078
     * <table style="width: 50%; text-align: left; margin-left: 40px;"
 
1079
     *  border="0" cellpadding="2" cellspacing="2">
 
1080
     * <tbody><code>
 
1081
     * <tr>
 
1082
     * <td
 
1083
     * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">Locale<br>
 
1084
     * </td>
 
1085
     * <td
 
1086
     * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">format<br>
 
1087
     * </td>
 
1088
     * </tr>
 
1089
     * <tr>
 
1090
     * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")<br>
 
1091
     * </td>
 
1092
     * <td style="vertical-align: top; width: 50%;">java.class<br>
 
1093
     * </td>
 
1094
     * </tr>
 
1095
     * <tr>
 
1096
     * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")</td>
 
1097
     * <td style="vertical-align: top; width: 50%;">java.properties<br>
 
1098
     * </td>
 
1099
     * </tr>
 
1100
     * <tr>
 
1101
     * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
 
1102
     * <td style="vertical-align: top; width: 50%;">java.class</td>
 
1103
     * </tr>
 
1104
     * <tr>
 
1105
     * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
 
1106
     * <td style="vertical-align: top; width: 50%;">java.properties</td>
 
1107
     * </tr>
 
1108
     * <tr>
 
1109
     * <td style="vertical-align: top; width: 50%;">Locale("")<br>
 
1110
     * </td>
 
1111
     * <td style="vertical-align: top; width: 50%;">java.class</td>
 
1112
     * </tr>
 
1113
     * <tr>
 
1114
     * <td style="vertical-align: top; width: 50%;">Locale("")</td>
 
1115
     * <td style="vertical-align: top; width: 50%;">java.properties</td>
 
1116
     * </tr>
 
1117
     * </code></tbody>
 
1118
     * </table>
 
1119
     * </li>
 
1120
     *
 
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>
 
1129
     *
 
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>
 
1138
     *
 
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>
 
1145
     * </ol>
 
1146
     *
 
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>.
 
1162
     *
 
1163
     * <p>All resource bundles loaded are cached by default. Refer to
 
1164
     * {@link Control#getTimeToLive(String,Locale)
 
1165
     * control.getTimeToLive} for details.
 
1166
     *
 
1167
     * <p>The following is an example of the bundle loading process with the
 
1168
     * default <code>ResourceBundle.Control</code> implementation.
 
1169
     *
 
1170
     * <p>Conditions:
 
1171
     * <ul>
 
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>
 
1178
     * </ul>
 
1179
     *
 
1180
     * <p>First, <code>getBundle</code> tries loading a resource bundle in
 
1181
     * the following sequence.
 
1182
     *
 
1183
     * <ul>
 
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>
 
1190
     * </ul>
 
1191
     *
 
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.
 
1199
     *
 
1200
     * <ul>
 
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>
 
1205
     * </ul>
 
1206
     *
 
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>.
 
1215
     *
 
1216
     * @param baseName
 
1217
     *        the base name of the resource bundle, a fully qualified
 
1218
     *        class name
 
1219
     * @param targetLocale
 
1220
     *        the locale for which a resource bundle is desired
 
1221
     * @param loader
 
1222
     *        the class loader from which to load the resource bundle
 
1223
     * @param control
 
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
 
1230
     *        <code>null</code>
 
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
 
1237
     *        needed.
 
1238
     * @since 1.6
 
1239
     */
 
1240
    public static ResourceBundle getBundle(String baseName, Locale targetLocale,
 
1241
                                           ClassLoader loader, Control control) {
 
1242
        if (loader == null || control == null) {
 
1243
            throw new NullPointerException();
 
1244
        }
 
1245
        return getBundleImpl(baseName, targetLocale, loader, control);
 
1246
    }
 
1247
 
 
1248
    private static ResourceBundle getBundleImpl(String baseName, Locale locale,
 
1249
                                                ClassLoader loader, Control control) {
 
1250
        if (locale == null || control == null) {
 
1251
            throw new NullPointerException();
 
1252
        }
 
1253
 
 
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;
 
1260
 
 
1261
        // Quick lookup of the cache.
 
1262
        BundleReference bundleRef = cacheList.get(cacheKey);
 
1263
        if (bundleRef != null) {
 
1264
            bundle = bundleRef.get();
 
1265
            bundleRef = null;
 
1266
        }
 
1267
 
 
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)) {
 
1273
            return bundle;
 
1274
        }
 
1275
 
 
1276
        // No valid bundle was found in the cache, so we need to load the
 
1277
        // resource bundle and its parents.
 
1278
 
 
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");
 
1284
        }
 
1285
 
 
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");
 
1293
            }
 
1294
 
 
1295
            bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);
 
1296
 
 
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)))) {
 
1307
                    break;
 
1308
                }
 
1309
 
 
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;
 
1315
                }
 
1316
            }
 
1317
        }
 
1318
 
 
1319
        if (bundle == null) {
 
1320
            if (baseBundle == null) {
 
1321
                throwMissingResourceException(baseName, locale, cacheKey.getCause());
 
1322
            }
 
1323
            bundle = baseBundle;
 
1324
        }
 
1325
 
 
1326
        return bundle;
 
1327
    }
 
1328
 
 
1329
    /**
 
1330
     * Checks if the given <code>List</code> is not null, not empty,
 
1331
     * not having null in its elements.
 
1332
     */
 
1333
    private static final boolean checkList(List a) {
 
1334
        boolean valid = (a != null && a.size() != 0);
 
1335
        if (valid) {
 
1336
            int size = a.size();
 
1337
            for (int i = 0; valid && i < size; i++) {
 
1338
                valid = (a.get(i) != null);
 
1339
            }
 
1340
        }
 
1341
        return valid;
 
1342
    }
 
1343
 
 
1344
    private static final ResourceBundle findBundle(CacheKey cacheKey,
 
1345
                                                   List<Locale> candidateLocales,
 
1346
                                                   List<String> formats,
 
1347
                                                   int index,
 
1348
                                                   Control control,
 
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)) {
 
1356
            return baseBundle;
 
1357
        }
 
1358
 
 
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.
 
1363
        Object ref;
 
1364
        while ((ref = referenceQueue.poll()) != null) {
 
1365
            cacheList.remove(((CacheKeyReference)ref).getCacheKey());
 
1366
        }
 
1367
 
 
1368
        // flag indicating the resource bundle has expired in the cache
 
1369
        boolean expiredBundle = false;
 
1370
 
 
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
 
1382
                // requested.)
 
1383
                if (bundle.parent == parent) {
 
1384
                    return bundle;
 
1385
                }
 
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);
 
1391
                }
 
1392
            }
 
1393
        }
 
1394
 
 
1395
        if (bundle != NONEXISTENT_BUNDLE) {
 
1396
            CacheKey constKey = (CacheKey) cacheKey.clone();
 
1397
 
 
1398
            try {
 
1399
                bundle = loadBundle(cacheKey, formats, control, expiredBundle);
 
1400
                if (bundle != null) {
 
1401
                    if (bundle.parent == null) {
 
1402
                        bundle.setParent(parent);
 
1403
                    }
 
1404
                    bundle.locale = targetLocale;
 
1405
                    bundle = putBundleInCache(cacheKey, bundle, control);
 
1406
                    return bundle;
 
1407
                }
 
1408
 
 
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);
 
1412
            } finally {
 
1413
                if (constKey.getCause() instanceof InterruptedException) {
 
1414
                    Thread.currentThread().interrupt();
 
1415
                }
 
1416
            }
 
1417
        }
 
1418
        return parent;
 
1419
    }
 
1420
 
 
1421
    private static final ResourceBundle loadBundle(CacheKey cacheKey,
 
1422
                                                   List<String> formats,
 
1423
                                                   Control control,
 
1424
                                                   boolean reload) {
 
1425
 
 
1426
        // Here we actually load the bundle in the order of formats
 
1427
        // specified by the getFormats() value.
 
1428
        Locale targetLocale = cacheKey.getLocale();
 
1429
 
 
1430
        ResourceBundle bundle = null;
 
1431
        int size = formats.size();
 
1432
        for (int i = 0; i < size; i++) {
 
1433
            String format = formats.get(i);
 
1434
            try {
 
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);
 
1444
            }
 
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;
 
1454
                break;
 
1455
            }
 
1456
        }
 
1457
 
 
1458
        return bundle;
 
1459
    }
 
1460
 
 
1461
    private static final boolean isValidBundle(ResourceBundle bundle) {
 
1462
        return bundle != null && bundle != NONEXISTENT_BUNDLE;
 
1463
    }
 
1464
 
 
1465
    /**
 
1466
     * Determines whether any of resource bundles in the parent chain,
 
1467
     * including the leaf, have expired.
 
1468
     */
 
1469
    private static final boolean hasValidParentChain(ResourceBundle bundle) {
 
1470
        long now = System.currentTimeMillis();
 
1471
        while (bundle != null) {
 
1472
            if (bundle.expired) {
 
1473
                return false;
 
1474
            }
 
1475
            CacheKey key = bundle.cacheKey;
 
1476
            if (key != null) {
 
1477
                long expirationTime = key.expirationTime;
 
1478
                if (expirationTime >= 0 && expirationTime <= now) {
 
1479
                    return false;
 
1480
                }
 
1481
            }
 
1482
            bundle = bundle.parent;
 
1483
        }
 
1484
        return true;
 
1485
    }
 
1486
 
 
1487
    /**
 
1488
     * Throw a MissingResourceException with proper message
 
1489
     */
 
1490
    private static final void throwMissingResourceException(String baseName,
 
1491
                                                            Locale locale,
 
1492
                                                            Throwable cause) {
 
1493
        // If the cause is a MissingResourceException, avoid creating
 
1494
        // a long chain. (6355009)
 
1495
        if (cause instanceof MissingResourceException) {
 
1496
            cause = null;
 
1497
        }
 
1498
        throw new MissingResourceException("Can't find bundle for base name "
 
1499
                                           + baseName + ", locale " + locale,
 
1500
                                           baseName + "_" + locale, // className
 
1501
                                           "",                      // key
 
1502
                                           cause);
 
1503
    }
 
1504
 
 
1505
    /**
 
1506
     * Finds a bundle in the cache. Any expired bundles are marked as
 
1507
     * `expired' and removed from the cache upon return.
 
1508
     *
 
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.
 
1514
     */
 
1515
    private static final ResourceBundle findBundleInCache(CacheKey cacheKey,
 
1516
                                                          Control control) {
 
1517
        BundleReference bundleRef = cacheList.get(cacheKey);
 
1518
        if (bundleRef == null) {
 
1519
            return null;
 
1520
        }
 
1521
        ResourceBundle bundle = bundleRef.get();
 
1522
        if (bundle == null) {
 
1523
            return null;
 
1524
        }
 
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,
 
1533
        //
 
1534
        // base <- ja <- ja_JP <- ja_JP_JP
 
1535
        //
 
1536
        // If ja has expired, then it will reload ja and the list becomes a
 
1537
        // tree.
 
1538
        //
 
1539
        // base <- ja (new)
 
1540
        //  "   <- ja (expired) <- ja_JP <- ja_JP_JP
 
1541
        //
 
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
 
1545
        // ja_JP_JP.
 
1546
        //
 
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.
 
1555
        //
 
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);
 
1565
            bundle = null;
 
1566
        } else {
 
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()) {
 
1579
                            try {
 
1580
                                bundle.expired = control.needsReload(key.getName(),
 
1581
                                                                     key.getLocale(),
 
1582
                                                                     key.getFormat(),
 
1583
                                                                     key.getLoader(),
 
1584
                                                                     bundle,
 
1585
                                                                     key.loadTime);
 
1586
                            } catch (Exception e) {
 
1587
                                cacheKey.setCause(e);
 
1588
                            }
 
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
 
1593
                                // on.
 
1594
                                bundle.cacheKey = null;
 
1595
                                cacheList.remove(cacheKey, bundleRef);
 
1596
                            } else {
 
1597
                                // Update the expiration control info. and reuse
 
1598
                                // the same bundle instance
 
1599
                                setExpirationTime(key, control);
 
1600
                            }
 
1601
                        }
 
1602
                    }
 
1603
                } else {
 
1604
                    // We just remove NONEXISTENT_BUNDLE from the cache.
 
1605
                    cacheList.remove(cacheKey, bundleRef);
 
1606
                    bundle = null;
 
1607
                }
 
1608
            }
 
1609
        }
 
1610
        return bundle;
 
1611
    }
 
1612
 
 
1613
    /**
 
1614
     * Put a new bundle in the cache.
 
1615
     *
 
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
 
1620
     * returned.
 
1621
     */
 
1622
    private static final ResourceBundle putBundleInCache(CacheKey cacheKey,
 
1623
                                                         ResourceBundle bundle,
 
1624
                                                         Control control) {
 
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;
 
1630
 
 
1631
            // Put the bundle in the cache if it's not been in the cache.
 
1632
            BundleReference result = cacheList.putIfAbsent(key, bundleRef);
 
1633
 
 
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;
 
1641
                    bundle = rb;
 
1642
                    // Clear the reference in the BundleReference so that
 
1643
                    // it won't be enqueued.
 
1644
                    bundleRef.clear();
 
1645
                } else {
 
1646
                    // Replace the invalid (garbage collected or expired)
 
1647
                    // instance with the valid one.
 
1648
                    cacheList.put(key, bundleRef);
 
1649
                }
 
1650
            }
 
1651
        }
 
1652
        return bundle;
 
1653
    }
 
1654
 
 
1655
    private static final void setExpirationTime(CacheKey cacheKey, Control control) {
 
1656
        long ttl = control.getTimeToLive(cacheKey.getName(),
 
1657
                                         cacheKey.getLocale());
 
1658
        if (ttl >= 0) {
 
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;
 
1666
        } else {
 
1667
            throw new IllegalArgumentException("Invalid Control: TTL=" + ttl);
 
1668
        }
 
1669
    }
 
1670
 
 
1671
    /**
 
1672
     * Removes all resource bundles from the cache that have been loaded
 
1673
     * using the caller's class loader.
 
1674
     *
 
1675
     * @since 1.6
 
1676
     * @see ResourceBundle.Control#getTimeToLive(String,Locale)
 
1677
     */
 
1678
    @ikvm.internal.HasCallerID
 
1679
    public static final void clearCache() {
 
1680
        clearCache(getLoader(Reflection.getCallerClass(2)));
 
1681
    }
 
1682
 
 
1683
    /**
 
1684
     * Removes all resource bundles from the cache that have been loaded
 
1685
     * using the given class loader.
 
1686
     *
 
1687
     * @param loader the class loader
 
1688
     * @exception NullPointerException if <code>loader</code> is null
 
1689
     * @since 1.6
 
1690
     * @see ResourceBundle.Control#getTimeToLive(String,Locale)
 
1691
     */
 
1692
    public static final void clearCache(ClassLoader loader) {
 
1693
        if (loader == null) {
 
1694
            throw new NullPointerException();
 
1695
        }
 
1696
        Set<CacheKey> set = cacheList.keySet();
 
1697
        for (CacheKey key : set) {
 
1698
            if (key.getLoader() == loader) {
 
1699
                set.remove(key);
 
1700
            }
 
1701
        }
 
1702
    }
 
1703
 
 
1704
    /**
 
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.
 
1708
     *
 
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
 
1712
     */
 
1713
    protected abstract Object handleGetObject(String key);
 
1714
 
 
1715
    /**
 
1716
     * Returns an enumeration of the keys.
 
1717
     *
 
1718
     * @return an <code>Enumeration</code> of the keys contained in
 
1719
     *         this <code>ResourceBundle</code> and its parent bundles.
 
1720
     */
 
1721
    public abstract Enumeration<String> getKeys();
 
1722
 
 
1723
    /**
 
1724
     * Determines whether the given <code>key</code> is contained in
 
1725
     * this <code>ResourceBundle</code> or its parent bundles.
 
1726
     *
 
1727
     * @param key
 
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>
 
1734
     * @since 1.6
 
1735
     */
 
1736
    public boolean containsKey(String key) {
 
1737
        if (key == null) {
 
1738
            throw new NullPointerException();
 
1739
        }
 
1740
        for (ResourceBundle rb = this; rb != null; rb = rb.parent) {
 
1741
            if (rb.handleKeySet().contains(key)) {
 
1742
                return true;
 
1743
            }
 
1744
        }
 
1745
        return false;
 
1746
    }
 
1747
 
 
1748
    /**
 
1749
     * Returns a <code>Set</code> of all keys contained in this
 
1750
     * <code>ResourceBundle</code> and its parent bundles.
 
1751
     *
 
1752
     * @return a <code>Set</code> of all keys contained in this
 
1753
     *         <code>ResourceBundle</code> and its parent bundles.
 
1754
     * @since 1.6
 
1755
     */
 
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());
 
1760
        }
 
1761
        return keys;
 
1762
    }
 
1763
 
 
1764
    /**
 
1765
     * Returns a <code>Set</code> of the keys contained <em>only</em>
 
1766
     * in this <code>ResourceBundle</code>.
 
1767
     *
 
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.
 
1776
     *
 
1777
     * @return a <code>Set</code> of the keys contained only in this
 
1778
     *        <code>ResourceBundle</code>
 
1779
     * @since 1.6
 
1780
     */
 
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) {
 
1790
                            keys.add(key);
 
1791
                        }
 
1792
                    }
 
1793
                    keySet = keys;
 
1794
                }
 
1795
            }
 
1796
        }
 
1797
        return keySet;
 
1798
    }
 
1799
 
 
1800
 
 
1801
 
 
1802
    /**
 
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>.
 
1812
     *
 
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
 
1821
     * name separators.
 
1822
     *
 
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.
 
1827
     *
 
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.
 
1840
     *
 
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.
 
1846
     *
 
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.
 
1852
     *
 
1853
     * <p><b>Example 1</b>
 
1854
     *
 
1855
     * <p>The following code lets <code>ResourceBundle.getBundle</code> look
 
1856
     * up only properties-based resources.
 
1857
     *
 
1858
     * <pre>
 
1859
     * import java.util.*;
 
1860
     * import static java.util.ResourceBundle.Control.*;
 
1861
     * ...
 
1862
     * ResourceBundle bundle =
 
1863
     *   ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"),
 
1864
     *                            ResourceBundle.Control.getControl(FORMAT_PROPERTIES));
 
1865
     * </pre>
 
1866
     *
 
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.)
 
1875
     *
 
1876
     * <p><b>Example 2</b>
 
1877
     *
 
1878
     * <p>The following is an example of loading XML-based bundles
 
1879
     * using {@link Properties#loadFromXML(java.io.InputStream)
 
1880
     * Properties.loadFromXML}.
 
1881
     *
 
1882
     * <pre>
 
1883
     * ResourceBundle rb = ResourceBundle.getBundle("Messages",
 
1884
     *     new ResourceBundle.Control() {
 
1885
     *         public List&lt;String&gt; getFormats(String baseName) {
 
1886
     *             if (baseName == null)
 
1887
     *                 throw new NullPointerException();
 
1888
     *             return Arrays.asList("xml");
 
1889
     *         }
 
1890
     *         public ResourceBundle newBundle(String baseName,
 
1891
     *                                         Locale locale,
 
1892
     *                                         String format,
 
1893
     *                                         ClassLoader loader,
 
1894
     *                                         boolean reload)
 
1895
     *                          throws IllegalAccessException,
 
1896
     *                                 InstantiationException,
 
1897
     *                                 IOException {
 
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;
 
1906
     *                 if (reload) {
 
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
 
1912
     *                             // reloading.
 
1913
     *                             connection.setUseCaches(false);
 
1914
     *                             stream = connection.getInputStream();
 
1915
     *                         }
 
1916
     *                     }
 
1917
     *                 } else {
 
1918
     *                     stream = loader.getResourceAsStream(resourceName);
 
1919
     *                 }
 
1920
     *                 if (stream != null) {
 
1921
     *                     BufferedInputStream bis = new BufferedInputStream(stream);
 
1922
     *                     bundle = new XMLResourceBundle(bis);
 
1923
     *                     bis.close();
 
1924
     *                 }
 
1925
     *             }
 
1926
     *             return bundle;
 
1927
     *         }
 
1928
     *     });
 
1929
     *
 
1930
     * ...
 
1931
     *
 
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);
 
1937
     *     }
 
1938
     *     protected Object handleGetObject(String key) {
 
1939
     *         return props.getProperty(key);
 
1940
     *     }
 
1941
     *     public Enumeration&lt;String&gt; getKeys() {
 
1942
     *         ...
 
1943
     *     }
 
1944
     * }
 
1945
     * </pre>
 
1946
     *
 
1947
     * @since 1.6
 
1948
     */
 
1949
    public static class Control {
 
1950
        /**
 
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}.
 
1955
         *
 
1956
         * @see #getFormats(String)
 
1957
         */
 
1958
        public static final List<String> FORMAT_DEFAULT
 
1959
            = Collections.unmodifiableList(Arrays.asList("java.class",
 
1960
                                                         "java.properties"));
 
1961
 
 
1962
        /**
 
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}.
 
1966
         *
 
1967
         * @see #getFormats(String)
 
1968
         */
 
1969
        public static final List<String> FORMAT_CLASS
 
1970
            = Collections.unmodifiableList(Arrays.asList("java.class"));
 
1971
 
 
1972
        /**
 
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}.
 
1976
         *
 
1977
         * @see #getFormats(String)
 
1978
         */
 
1979
        public static final List<String> FORMAT_PROPERTIES
 
1980
            = Collections.unmodifiableList(Arrays.asList("java.properties"));
 
1981
 
 
1982
        /**
 
1983
         * The time-to-live constant for not caching loaded resource bundle
 
1984
         * instances.
 
1985
         *
 
1986
         * @see #getTimeToLive(String, Locale)
 
1987
         */
 
1988
        public static final long TTL_DONT_CACHE = -1;
 
1989
 
 
1990
        /**
 
1991
         * The time-to-live constant for disabling the expiration control
 
1992
         * for loaded resource bundle instances in the cache.
 
1993
         *
 
1994
         * @see #getTimeToLive(String, Locale)
 
1995
         */
 
1996
        public static final long TTL_NO_EXPIRATION_CONTROL = -2;
 
1997
 
 
1998
        private static final Control INSTANCE = new Control();
 
1999
 
 
2000
        /**
 
2001
         * Sole constructor. (For invocation by subclass constructors,
 
2002
         * typically implicit.)
 
2003
         */
 
2004
        protected Control() {
 
2005
        }
 
2006
 
 
2007
        /**
 
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.
 
2015
         *
 
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.
 
2019
         *
 
2020
         * @param formats
 
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
 
2029
         */
 
2030
        public static final Control getControl(List<String> formats) {
 
2031
            if (formats.equals(Control.FORMAT_PROPERTIES)) {
 
2032
                return SingleFormatControl.PROPERTIES_ONLY;
 
2033
            }
 
2034
            if (formats.equals(Control.FORMAT_CLASS)) {
 
2035
                return SingleFormatControl.CLASS_ONLY;
 
2036
            }
 
2037
            if (formats.equals(Control.FORMAT_DEFAULT)) {
 
2038
                return Control.INSTANCE;
 
2039
            }
 
2040
            throw new IllegalArgumentException();
 
2041
        }
 
2042
 
 
2043
        /**
 
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.
 
2053
         *
 
2054
         * @param formats
 
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
 
2064
         */
 
2065
        public static final Control getNoFallbackControl(List<String> formats) {
 
2066
            if (formats.equals(Control.FORMAT_DEFAULT)) {
 
2067
                return NoFallbackControl.NO_FALLBACK;
 
2068
            }
 
2069
            if (formats.equals(Control.FORMAT_PROPERTIES)) {
 
2070
                return NoFallbackControl.PROPERTIES_ONLY_NO_FALLBACK;
 
2071
            }
 
2072
            if (formats.equals(Control.FORMAT_CLASS)) {
 
2073
                return NoFallbackControl.CLASS_ONLY_NO_FALLBACK;
 
2074
            }
 
2075
            throw new IllegalArgumentException();
 
2076
        }
 
2077
 
 
2078
        /**
 
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.
 
2090
         *
 
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>.
 
2095
         *
 
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.
 
2100
         *
 
2101
         * @param baseName
 
2102
         *        the base name of the resource bundle, a fully qualified class
 
2103
         *        name
 
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
 
2111
         */
 
2112
        public List<String> getFormats(String baseName) {
 
2113
            if (baseName == null) {
 
2114
                throw new NullPointerException();
 
2115
            }
 
2116
            return FORMAT_DEFAULT;
 
2117
        }
 
2118
 
 
2119
        /**
 
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>.
 
2125
         *
 
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.
 
2133
         *
 
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.
 
2139
         *
 
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>.
 
2144
         *
 
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.
 
2154
         *
 
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:
 
2158
         *
 
2159
         * <ul>
 
2160
         * <li> [<em>L</em>, <em>C</em>, <em>V</em>]
 
2161
         * <li> [<em>L</em>, <em>C</em>]
 
2162
         * <li> [<em>L</em>]
 
2163
         * <li> <code>Locale.ROOT</code>
 
2164
         * </ul>
 
2165
         *
 
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:
 
2170
         *
 
2171
         * <ul>
 
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>]
 
2177
         * <li> [<em>L</em>]
 
2178
         * <li> <code>Locale.ROOT</code>
 
2179
         * </ul>
 
2180
         *
 
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>:
 
2187
         *
 
2188
         * <ul>
 
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>]
 
2196
         * <li> [<em>L</em>]
 
2197
         * <li> <code>Locale.ROOT</code>
 
2198
         * </ul>
 
2199
         *
 
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:
 
2208
         * <ul>
 
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>
 
2214
         * </ul>
 
2215
         *
 
2216
         * For <code>Locale("zh", "TW")</code>, the candidate list will be:
 
2217
         * <ul>
 
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>
 
2223
         * </ul>
 
2224
         *
 
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:
 
2230
         *
 
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>
 
2235
         * </ul>
 
2236
         *
 
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
 
2239
         * followed.
 
2240
         *
 
2241
         * <p>Also, Java treats the language "no" as a synonym of Norwegian
 
2242
         * Bokm&#xE5;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
 
2248
         * candidate list:
 
2249
         *
 
2250
         * <ul>
 
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>
 
2258
         * </ul>
 
2259
         *
 
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>
 
2263
         *
 
2264
         * </li>
 
2265
         * </ol>
 
2266
         *
 
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>.
 
2271
         *
 
2272
         * <p>For example, if the given <code>baseName</code> is "Messages"
 
2273
         * and the given <code>locale</code> is
 
2274
         * <code>Locale("ja",&nbsp;"",&nbsp;"XX")</code>, then a
 
2275
         * <code>List</code> of <code>Locale</code>s:
 
2276
         * <pre>
 
2277
         *     Locale("ja", "", "XX")
 
2278
         *     Locale("ja")
 
2279
         *     Locale.ROOT
 
2280
         * </pre>
 
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:
 
2284
         * <pre>
 
2285
         *     Messages_ja -> Messages
 
2286
         * </pre>
 
2287
         *
 
2288
         * @param baseName
 
2289
         *        the base name of the resource bundle, a fully
 
2290
         *        qualified class name
 
2291
         * @param locale
 
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
 
2297
         *        <code>null</code>
 
2298
         */
 
2299
        public List<Locale> getCandidateLocales(String baseName, Locale locale) {
 
2300
            if (baseName == null) {
 
2301
                throw new NullPointerException();
 
2302
            }
 
2303
            return new ArrayList<>(CANDIDATES_CACHE.get(locale.getBaseLocale()));
 
2304
        }
 
2305
 
 
2306
        private static final CandidateListCache CANDIDATES_CACHE = new CandidateListCache();
 
2307
 
 
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();
 
2314
 
 
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")) {
 
2320
                        variant = "";
 
2321
                        isNorwegianNynorsk = true;
 
2322
                    } else {
 
2323
                        isNorwegianBokmal = true;
 
2324
                    }
 
2325
                }
 
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) {
 
2331
                        bokmalList.add(l);
 
2332
                        if (l.getLanguage().length() == 0) {
 
2333
                            break;
 
2334
                        }
 
2335
                        bokmalList.add(Locale.getInstance("no", l.getScript(), l.getCountry(),
 
2336
                                l.getVariant(), null));
 
2337
                    }
 
2338
                    return bokmalList;
 
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", "", ""));
 
2346
                    return nynorskList;
 
2347
                }
 
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")) {
 
2354
                            script = "Hant";
 
2355
                        } else if (region.equals("CN") || region.equals("SG")) {
 
2356
                            script = "Hans";
 
2357
                        }
 
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")) {
 
2362
                            region = "CN";
 
2363
                        } else if (script.equals("Hant")) {
 
2364
                            region = "TW";
 
2365
                        }
 
2366
                    }
 
2367
                }
 
2368
 
 
2369
                return getDefaultList(language, script, region, variant);
 
2370
            }
 
2371
 
 
2372
            private static List<Locale> getDefaultList(String language, String script, String region, String variant) {
 
2373
                List<String> variants = null;
 
2374
 
 
2375
                if (variant.length() > 0) {
 
2376
                    variants = new LinkedList<>();
 
2377
                    int idx = variant.length();
 
2378
                    while (idx != -1) {
 
2379
                        variants.add(variant.substring(0, idx));
 
2380
                        idx = variant.lastIndexOf('_', --idx);
 
2381
                    }
 
2382
                }
 
2383
 
 
2384
                List<Locale> list = new LinkedList<>();
 
2385
 
 
2386
                if (variants != null) {
 
2387
                    for (String v : variants) {
 
2388
                        list.add(Locale.getInstance(language, script, region, v, null));
 
2389
                    }
 
2390
                }
 
2391
                if (region.length() > 0) {
 
2392
                    list.add(Locale.getInstance(language, script, region, "", null));
 
2393
                }
 
2394
                if (script.length() > 0) {
 
2395
                    list.add(Locale.getInstance(language, script, "", "", null));
 
2396
 
 
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));
 
2402
                        }
 
2403
                    }
 
2404
                    if (region.length() > 0) {
 
2405
                        list.add(Locale.getInstance(language, "", region, "", null));
 
2406
                    }
 
2407
                }
 
2408
                if (language.length() > 0) {
 
2409
                    list.add(Locale.getInstance(language, "", "", "", null));
 
2410
                }
 
2411
                // Add root locale at the end
 
2412
                list.add(Locale.ROOT);
 
2413
 
 
2414
                return list;
 
2415
            }
 
2416
        }
 
2417
 
 
2418
        /**
 
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.
 
2427
         *
 
2428
         * <p>The method returns <code>null</code> if no further fallback
 
2429
         * search is desired.
 
2430
         *
 
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.
 
2435
         *
 
2436
         * @param baseName
 
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
 
2441
         *        base bundle)
 
2442
         * @param locale
 
2443
         *        the <code>Locale</code> for which
 
2444
         *        <code>ResourceBundle.getBundle</code> has been
 
2445
         *        unable to find any resource bundles (except for the
 
2446
         *        base bundle)
 
2447
         * @return a <code>Locale</code> for the fallback search,
 
2448
         *        or <code>null</code> if no further fallback search
 
2449
         *        is desired.
 
2450
         * @exception NullPointerException
 
2451
         *        if <code>baseName</code> or <code>locale</code>
 
2452
         *        is <code>null</code>
 
2453
         */
 
2454
        public Locale getFallbackLocale(String baseName, Locale locale) {
 
2455
            if (baseName == null) {
 
2456
                throw new NullPointerException();
 
2457
            }
 
2458
            Locale defaultLocale = Locale.getDefault();
 
2459
            return locale.equals(defaultLocale) ? null : defaultLocale;
 
2460
        }
 
2461
 
 
2462
        /**
 
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>.
 
2471
         *
 
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.
 
2475
         *
 
2476
         * <p>The default implementation instantiates a
 
2477
         * <code>ResourceBundle</code> as follows.
 
2478
         *
 
2479
         * <ul>
 
2480
         *
 
2481
         * <li>The bundle name is obtained by calling {@link
 
2482
         * #toBundleName(String, Locale) toBundleName(baseName,
 
2483
         * locale)}.</li>
 
2484
         *
 
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>
 
2492
         *
 
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>
 
2509
         *
 
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>
 
2513
         *
 
2514
         * </ul>
 
2515
         *
 
2516
         * @param baseName
 
2517
         *        the base bundle name of the resource bundle, a fully
 
2518
         *        qualified class name
 
2519
         * @param locale
 
2520
         *        the locale for which the resource bundle should be
 
2521
         *        instantiated
 
2522
         * @param format
 
2523
         *        the resource bundle format to be loaded
 
2524
         * @param loader
 
2525
         *        the <code>ClassLoader</code> to use to load the bundle
 
2526
         * @param reload
 
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
 
2544
         *        accessible.
 
2545
         * @exception InstantiationException
 
2546
         *        if the instantiation of a class fails for some other
 
2547
         *        reason.
 
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()}
 
2553
         *        for details.
 
2554
         * @exception IOException
 
2555
         *        if an error occurred when reading resources using
 
2556
         *        any I/O operations
 
2557
         */
 
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")) {
 
2564
                try {
 
2565
                    Class<? extends ResourceBundle> bundleClass
 
2566
                        = (Class<? extends ResourceBundle>)loader.loadClass(bundleName);
 
2567
 
 
2568
                    // If the class isn't a ResourceBundle subclass, throw a
 
2569
                    // ClassCastException.
 
2570
                    if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
 
2571
                        bundle = bundleClass.newInstance();
 
2572
                    } else {
 
2573
                        throw new ClassCastException(bundleClass.getName()
 
2574
                                     + " cannot be cast to ResourceBundle");
 
2575
                    }
 
2576
                } catch (ClassNotFoundException e) {
 
2577
                }
 
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;
 
2583
                try {
 
2584
                    stream = AccessController.doPrivileged(
 
2585
                        new PrivilegedExceptionAction<InputStream>() {
 
2586
                            public InputStream run() throws IOException {
 
2587
                                InputStream is = null;
 
2588
                                if (reloadFlag) {
 
2589
                                    URL url = classLoader.getResource(resourceName);
 
2590
                                    if (url != null) {
 
2591
                                        URLConnection connection = url.openConnection();
 
2592
                                        if (connection != null) {
 
2593
                                            // Disable caches to get fresh data for
 
2594
                                            // reloading.
 
2595
                                            connection.setUseCaches(false);
 
2596
                                            is = connection.getInputStream();
 
2597
                                        }
 
2598
                                    }
 
2599
                                } else {
 
2600
                                    is = classLoader.getResourceAsStream(resourceName);
 
2601
                                }
 
2602
                                return is;
 
2603
                            }
 
2604
                        });
 
2605
                } catch (PrivilegedActionException e) {
 
2606
                    throw (IOException) e.getException();
 
2607
                }
 
2608
                if (stream != null) {
 
2609
                    try {
 
2610
                        bundle = new PropertyResourceBundle(stream);
 
2611
                    } finally {
 
2612
                        stream.close();
 
2613
                    }
 
2614
                }
 
2615
            } else {
 
2616
                throw new IllegalArgumentException("unknown format: " + format);
 
2617
            }
 
2618
            return bundle;
 
2619
        }
 
2620
 
 
2621
        /**
 
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.
 
2633
         *
 
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.
 
2644
         *
 
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.
 
2649
         *
 
2650
         * <p>The default implementation returns {@link #TTL_NO_EXPIRATION_CONTROL}.
 
2651
         *
 
2652
         * @param baseName
 
2653
         *        the base name of the resource bundle for which the
 
2654
         *        expiration value is specified.
 
2655
         * @param locale
 
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
 
2662
         *        caching.
 
2663
         * @exception NullPointerException
 
2664
         *        if <code>baseName</code> or <code>locale</code> is
 
2665
         *        <code>null</code>
 
2666
         */
 
2667
        public long getTimeToLive(String baseName, Locale locale) {
 
2668
            if (baseName == null || locale == null) {
 
2669
                throw new NullPointerException();
 
2670
            }
 
2671
            return TTL_NO_EXPIRATION_CONTROL;
 
2672
        }
 
2673
 
 
2674
        /**
 
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>
 
2681
         * Epoch</a>.
 
2682
         *
 
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
 
2687
         * bundle.
 
2688
         *
 
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>.
 
2698
         *
 
2699
         * @param baseName
 
2700
         *        the base bundle name of the resource bundle, a
 
2701
         *        fully qualified class name
 
2702
         * @param locale
 
2703
         *        the locale for which the resource bundle
 
2704
         *        should be instantiated
 
2705
         * @param format
 
2706
         *        the resource bundle format to be loaded
 
2707
         * @param loader
 
2708
         *        the <code>ClassLoader</code> to use to load the bundle
 
2709
         * @param bundle
 
2710
         *        the resource bundle instance that has been expired
 
2711
         *        in the cache
 
2712
         * @param loadTime
 
2713
         *        the time when <code>bundle</code> was loaded and put
 
2714
         *        in the cache
 
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>
 
2721
         */
 
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();
 
2727
            }
 
2728
            if (format.equals("java.class") || format.equals("java.properties")) {
 
2729
                format = format.substring(5);
 
2730
            }
 
2731
            boolean result = false;
 
2732
            try {
 
2733
                String resourceName = toResourceName(toBundleName(baseName, locale), format);
 
2734
                URL url = loader.getResource(resourceName);
 
2735
                if (url != null) {
 
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();
 
2743
                            if (ent != null) {
 
2744
                                lastModified = ent.getTime();
 
2745
                                if (lastModified == -1) {
 
2746
                                    lastModified = 0;
 
2747
                                }
 
2748
                            }
 
2749
                        } else {
 
2750
                            lastModified = connection.getLastModified();
 
2751
                        }
 
2752
                    }
 
2753
                    result = lastModified >= loadTime;
 
2754
                }
 
2755
            } catch (NullPointerException npe) {
 
2756
                throw npe;
 
2757
            } catch (Exception e) {
 
2758
                // ignore other exceptions
 
2759
            }
 
2760
            return result;
 
2761
        }
 
2762
 
 
2763
        /**
 
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}
 
2769
         * methods.
 
2770
         *
 
2771
         * <p>This implementation returns the following value:
 
2772
         * <pre>
 
2773
         *     baseName + "_" + language + "_" + script + "_" + country + "_" + variant
 
2774
         * </pre>
 
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>
 
2781
         * is returned.
 
2782
         *
 
2783
         * <p>For example, if <code>baseName</code> is
 
2784
         * <code>"baseName"</code> and <code>locale</code> is
 
2785
         * <code>Locale("ja",&nbsp;"",&nbsp;"XX")</code>, then
 
2786
         * <code>"baseName_ja_&thinsp;_XX"</code> is returned. If the given
 
2787
         * locale is <code>Locale("en")</code>, then
 
2788
         * <code>"baseName_en"</code> is returned.
 
2789
         *
 
2790
         * <p>Overriding this method allows applications to use different
 
2791
         * conventions in the organization and packaging of localized
 
2792
         * resources.
 
2793
         *
 
2794
         * @param baseName
 
2795
         *        the base name of the resource bundle, a fully
 
2796
         *        qualified class name
 
2797
         * @param locale
 
2798
         *        the locale for which a resource bundle should be
 
2799
         *        loaded
 
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>
 
2804
         */
 
2805
        public String toBundleName(String baseName, Locale locale) {
 
2806
            if (locale == Locale.ROOT) {
 
2807
                return baseName;
 
2808
            }
 
2809
 
 
2810
            String language = locale.getLanguage();
 
2811
            String script = locale.getScript();
 
2812
            String country = locale.getCountry();
 
2813
            String variant = locale.getVariant();
 
2814
 
 
2815
            if (language == "" && country == "" && variant == "") {
 
2816
                return baseName;
 
2817
            }
 
2818
 
 
2819
            StringBuilder sb = new StringBuilder(baseName);
 
2820
            sb.append('_');
 
2821
            if (script != "") {
 
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);
 
2826
                } else {
 
2827
                    sb.append(language).append('_').append(script);
 
2828
                }
 
2829
            } else {
 
2830
                if (variant != "") {
 
2831
                    sb.append(language).append('_').append(country).append('_').append(variant);
 
2832
                } else if (country != "") {
 
2833
                    sb.append(language).append('_').append(country);
 
2834
                } else {
 
2835
                    sb.append(language);
 
2836
                }
 
2837
            }
 
2838
            return sb.toString();
 
2839
 
 
2840
        }
 
2841
 
 
2842
        /**
 
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.
 
2852
         *
 
2853
         * @param bundleName
 
2854
         *        the bundle name
 
2855
         * @param suffix
 
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>
 
2861
         */
 
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();
 
2866
        }
 
2867
    }
 
2868
 
 
2869
    private static class SingleFormatControl extends Control {
 
2870
        private static final Control PROPERTIES_ONLY
 
2871
            = new SingleFormatControl(FORMAT_PROPERTIES);
 
2872
 
 
2873
        private static final Control CLASS_ONLY
 
2874
            = new SingleFormatControl(FORMAT_CLASS);
 
2875
 
 
2876
        private final List<String> formats;
 
2877
 
 
2878
        protected SingleFormatControl(List<String> formats) {
 
2879
            this.formats = formats;
 
2880
        }
 
2881
 
 
2882
        public List<String> getFormats(String baseName) {
 
2883
            if (baseName == null) {
 
2884
                throw new NullPointerException();
 
2885
            }
 
2886
            return formats;
 
2887
        }
 
2888
    }
 
2889
 
 
2890
    private static final class NoFallbackControl extends SingleFormatControl {
 
2891
        private static final Control NO_FALLBACK
 
2892
            = new NoFallbackControl(FORMAT_DEFAULT);
 
2893
 
 
2894
        private static final Control PROPERTIES_ONLY_NO_FALLBACK
 
2895
            = new NoFallbackControl(FORMAT_PROPERTIES);
 
2896
 
 
2897
        private static final Control CLASS_ONLY_NO_FALLBACK
 
2898
            = new NoFallbackControl(FORMAT_CLASS);
 
2899
 
 
2900
        protected NoFallbackControl(List<String> formats) {
 
2901
            super(formats);
 
2902
        }
 
2903
 
 
2904
        public Locale getFallbackLocale(String baseName, Locale locale) {
 
2905
            if (baseName == null || locale == null) {
 
2906
                throw new NullPointerException();
 
2907
            }
 
2908
            return null;
 
2909
        }
 
2910
    }
 
2911
}