Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ResourceBundle |
|
| 0.0;0 | ||||
ResourceBundle$1 |
|
| 0.0;0 | ||||
ResourceBundle$BundleReference |
|
| 0.0;0 | ||||
ResourceBundle$CacheKey |
|
| 0.0;0 | ||||
ResourceBundle$CacheKeyReference |
|
| 0.0;0 | ||||
ResourceBundle$Control |
|
| 0.0;0 | ||||
ResourceBundle$Control$1 |
|
| 0.0;0 | ||||
ResourceBundle$LoaderReference |
|
| 0.0;0 | ||||
ResourceBundle$NoFallbackControl |
|
| 0.0;0 | ||||
ResourceBundle$RBClassLoader |
|
| 0.0;0 | ||||
ResourceBundle$RBClassLoader$1 |
|
| 0.0;0 | ||||
ResourceBundle$SingleFormatControl |
|
| 0.0;0 |
1 | /* | |
2 | * Copyright 1996-2008 Sun Microsystems, Inc. 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. Sun designates this | |
8 | * particular file as subject to the "Classpath" exception as provided | |
9 | * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, | |
22 | * CA 95054 USA or visit www.sun.com if you need additional information or | |
23 | * have any 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 | /* | |
42 | * NOTE: | |
43 | * | |
44 | * This class was backported from the JDK6 runtime library, because some of | |
45 | * the functionality in this class aren't available on JDK5, which is at | |
46 | * the moment the target JRE environment for running JavaFX applications. | |
47 | * Once JDK5 is not a supported platform anymore, this class should be | |
48 | * removed. | |
49 | */ | |
50 | ||
51 | package com.sun.javafx.runtime.util.backport; | |
52 | ||
53 | import java.io.IOException; | |
54 | import java.io.InputStream; | |
55 | import java.lang.ref.ReferenceQueue; | |
56 | import java.lang.ref.SoftReference; | |
57 | import java.lang.ref.WeakReference; | |
58 | import java.net.JarURLConnection; | |
59 | import java.net.URL; | |
60 | import java.net.URLConnection; | |
61 | import java.security.AccessController; | |
62 | import java.security.PrivilegedAction; | |
63 | import java.security.PrivilegedActionException; | |
64 | import java.security.PrivilegedExceptionAction; | |
65 | import java.util.concurrent.ConcurrentHashMap; | |
66 | import java.util.concurrent.ConcurrentMap; | |
67 | import java.util.jar.JarEntry; | |
68 | import java.util.*; | |
69 | ||
70 | ||
71 | /** | |
72 | * | |
73 | * Resource bundles contain locale-specific objects. | |
74 | * When your program needs a locale-specific resource, | |
75 | * a <code>String</code> for example, your program can load it | |
76 | * from the resource bundle that is appropriate for the | |
77 | * current user's locale. In this way, you can write | |
78 | * program code that is largely independent of the user's | |
79 | * locale isolating most, if not all, of the locale-specific | |
80 | * information in resource bundles. | |
81 | * | |
82 | * <p> | |
83 | * This allows you to write programs that can: | |
84 | * <UL type=SQUARE> | |
85 | * <LI> be easily localized, or translated, into different languages | |
86 | * <LI> handle multiple locales at once | |
87 | * <LI> be easily modified later to support even more locales | |
88 | * </UL> | |
89 | * | |
90 | * <P> | |
91 | * Resource bundles belong to families whose members share a common base | |
92 | * name, but whose names also have additional components that identify | |
93 | * their locales. For example, the base name of a family of resource | |
94 | * bundles might be "MyResources". The family should have a default | |
95 | * resource bundle which simply has the same name as its family - | |
96 | * "MyResources" - and will be used as the bundle of last resort if a | |
97 | * specific locale is not supported. The family can then provide as | |
98 | * many locale-specific members as needed, for example a German one | |
99 | * named "MyResources_de". | |
100 | * | |
101 | * <P> | |
102 | * Each resource bundle in a family contains the same items, but the items have | |
103 | * been translated for the locale represented by that resource bundle. | |
104 | * For example, both "MyResources" and "MyResources_de" may have a | |
105 | * <code>String</code> that's used on a button for canceling operations. | |
106 | * In "MyResources" the <code>String</code> may contain "Cancel" and in | |
107 | * "MyResources_de" it may contain "Abbrechen". | |
108 | * | |
109 | * <P> | |
110 | * If there are different resources for different countries, you | |
111 | * can make specializations: for example, "MyResources_de_CH" contains objects for | |
112 | * the German language (de) in Switzerland (CH). If you want to only | |
113 | * modify some of the resources | |
114 | * in the specialization, you can do so. | |
115 | * | |
116 | * <P> | |
117 | * When your program needs a locale-specific object, it loads | |
118 | * the <code>ResourceBundle</code> class using the | |
119 | * {@link #getBundle(java.lang.String, java.util.Locale) getBundle} | |
120 | * method: | |
121 | * <blockquote> | |
122 | * <pre> | |
123 | * ResourceBundle myResources = | |
124 | * ResourceBundle.getBundle("MyResources", currentLocale); | |
125 | * </pre> | |
126 | * </blockquote> | |
127 | * | |
128 | * <P> | |
129 | * Resource bundles contain key/value pairs. The keys uniquely | |
130 | * identify a locale-specific object in the bundle. Here's an | |
131 | * example of a <code>ListResourceBundle</code> that contains | |
132 | * two key/value pairs: | |
133 | * <blockquote> | |
134 | * <pre> | |
135 | * public class MyResources extends ListResourceBundle { | |
136 | * protected Object[][] getContents() { | |
137 | * return new Object[][] { | |
138 | * // LOCALIZE THE SECOND STRING OF EACH ARRAY (e.g., "OK") | |
139 | * {"OkKey", "OK"}, | |
140 | * {"CancelKey", "Cancel"}, | |
141 | * // END OF MATERIAL TO LOCALIZE | |
142 | * }; | |
143 | * } | |
144 | * } | |
145 | * </pre> | |
146 | * </blockquote> | |
147 | * Keys are always <code>String</code>s. | |
148 | * In this example, the keys are "OkKey" and "CancelKey". | |
149 | * In the above example, the values | |
150 | * are also <code>String</code>s--"OK" and "Cancel"--but | |
151 | * they don't have to be. The values can be any type of object. | |
152 | * | |
153 | * <P> | |
154 | * You retrieve an object from resource bundle using the appropriate | |
155 | * getter method. Because "OkKey" and "CancelKey" | |
156 | * are both strings, you would use <code>getString</code> to retrieve them: | |
157 | * <blockquote> | |
158 | * <pre> | |
159 | * button1 = new Button(myResources.getString("OkKey")); | |
160 | * button2 = new Button(myResources.getString("CancelKey")); | |
161 | * </pre> | |
162 | * </blockquote> | |
163 | * The getter methods all require the key as an argument and return | |
164 | * the object if found. If the object is not found, the getter method | |
165 | * throws a <code>MissingResourceException</code>. | |
166 | * | |
167 | * <P> | |
168 | * Besides <code>getString</code>, <code>ResourceBundle</code> also provides | |
169 | * a method for getting string arrays, <code>getStringArray</code>, | |
170 | * as well as a generic <code>getObject</code> method for any other | |
171 | * type of object. When using <code>getObject</code>, you'll | |
172 | * have to cast the result to the appropriate type. For example: | |
173 | * <blockquote> | |
174 | * <pre> | |
175 | * int[] myIntegers = (int[]) myResources.getObject("intList"); | |
176 | * </pre> | |
177 | * </blockquote> | |
178 | * | |
179 | * <P> | |
180 | * The Java Platform provides two subclasses of <code>ResourceBundle</code>, | |
181 | * <code>ListResourceBundle</code> and <code>PropertyResourceBundle</code>, | |
182 | * that provide a fairly simple way to create resources. | |
183 | * As you saw briefly in a previous example, <code>ListResourceBundle</code> | |
184 | * manages its resource as a list of key/value pairs. | |
185 | * <code>PropertyResourceBundle</code> uses a properties file to manage | |
186 | * its resources. | |
187 | * | |
188 | * <p> | |
189 | * If <code>ListResourceBundle</code> or <code>PropertyResourceBundle</code> | |
190 | * do not suit your needs, you can write your own <code>ResourceBundle</code> | |
191 | * subclass. Your subclasses must override two methods: <code>handleGetObject</code> | |
192 | * and <code>getKeys()</code>. | |
193 | * | |
194 | * <h4>ResourceBundle.Control</h4> | |
195 | * | |
196 | * The {@link ResourceBundle.Control} class provides information necessary | |
197 | * to perform the bundle loading process by the <code>getBundle</code> | |
198 | * factory methods that take a <code>ResourceBundle.Control</code> | |
199 | * instance. You can implement your own subclass in order to enable | |
200 | * non-standard resource bundle formats, change the search strategy, or | |
201 | * define caching parameters. Refer to the descriptions of the class and the | |
202 | * {@link #getBundle(String, Locale, ClassLoader, Control) getBundle} | |
203 | * factory method for details. | |
204 | * | |
205 | * <h4>Cache Management</h4> | |
206 | * | |
207 | * Resource bundle instances created by the <code>getBundle</code> factory | |
208 | * methods are cached by default, and the factory methods return the same | |
209 | * resource bundle instance multiple times if it has been | |
210 | * cached. <code>getBundle</code> clients may clear the cache, manage the | |
211 | * lifetime of cached resource bundle instances using time-to-live values, | |
212 | * or specify not to cache resource bundle instances. Refer to the | |
213 | * descriptions of the {@linkplain #getBundle(String, Locale, ClassLoader, | |
214 | * Control) <code>getBundle</code> factory method}, {@link | |
215 | * #clearCache(ClassLoader) clearCache}, {@link | |
216 | * Control#getTimeToLive(String, Locale) | |
217 | * ResourceBundle.Control.getTimeToLive}, and {@link | |
218 | * Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle, | |
219 | * long) ResourceBundle.Control.needsReload} for details. | |
220 | * | |
221 | * <h4>Example</h4> | |
222 | * | |
223 | * The following is a very simple example of a <code>ResourceBundle</code> | |
224 | * subclass, <code>MyResources</code>, that manages two resources (for a larger number of | |
225 | * resources you would probably use a <code>Map</code>). | |
226 | * Notice that you don't need to supply a value if | |
227 | * a "parent-level" <code>ResourceBundle</code> handles the same | |
228 | * key with the same value (as for the okKey below). | |
229 | * <blockquote> | |
230 | * <pre> | |
231 | * // default (English language, United States) | |
232 | * public class MyResources extends ResourceBundle { | |
233 | * public Object handleGetObject(String key) { | |
234 | * if (key.equals("okKey")) return "Ok"; | |
235 | * if (key.equals("cancelKey")) return "Cancel"; | |
236 | * return null; | |
237 | * } | |
238 | * | |
239 | * public Enumeration<String> getKeys() { | |
240 | * return Collections.enumeration(keySet()); | |
241 | * } | |
242 | * | |
243 | * // Overrides handleKeySet() so that the getKeys() implementation | |
244 | * // can rely on the keySet() value. | |
245 | * protected Set<String> handleKeySet() { | |
246 | * return new HashSet<String>(Arrays.asList("okKey", "cancelKey")); | |
247 | * } | |
248 | * } | |
249 | * | |
250 | * // German language | |
251 | * public class MyResources_de extends MyResources { | |
252 | * public Object handleGetObject(String key) { | |
253 | * // don't need okKey, since parent level handles it. | |
254 | * if (key.equals("cancelKey")) return "Abbrechen"; | |
255 | * return null; | |
256 | * } | |
257 | * | |
258 | * protected Set<String> handleKeySet() { | |
259 | * return new HashSet<String>(Arrays.asList("cancelKey")); | |
260 | * } | |
261 | * } | |
262 | * </pre> | |
263 | * </blockquote> | |
264 | * You do not have to restrict yourself to using a single family of | |
265 | * <code>ResourceBundle</code>s. For example, you could have a set of bundles for | |
266 | * exception messages, <code>ExceptionResources</code> | |
267 | * (<code>ExceptionResources_fr</code>, <code>ExceptionResources_de</code>, ...), | |
268 | * and one for widgets, <code>WidgetResource</code> (<code>WidgetResources_fr</code>, | |
269 | * <code>WidgetResources_de</code>, ...); breaking up the resources however you like. | |
270 | * | |
271 | * @see ListResourceBundle | |
272 | * @see PropertyResourceBundle | |
273 | * @see MissingResourceException | |
274 | * @since JDK1.1 | |
275 | */ | |
276 | 220 | public abstract class ResourceBundle { |
277 | ||
278 | 8 | private static final Locale ROOTLOCALE = new Locale(""); |
279 | ||
280 | /** initial size of the bundle cache */ | |
281 | private static final int INITIAL_CACHE_SIZE = 32; | |
282 | ||
283 | /** constant indicating that no resource bundle exists */ | |
284 | 8 | private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() { |
285 | 0 | public Enumeration<String> getKeys() { return null; } |
286 | 0 | protected Object handleGetObject(String key) { return null; } |
287 | @Override | |
288 | 0 | public String toString() { return "NONEXISTENT_BUNDLE"; } |
289 | }; | |
290 | ||
291 | ||
292 | /** | |
293 | * The cache is a map from cache keys (with bundle base name, locale, and | |
294 | * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a | |
295 | * BundleReference. | |
296 | * | |
297 | * The cache is a ConcurrentMap, allowing the cache to be searched | |
298 | * concurrently by multiple threads. This will also allow the cache keys | |
299 | * to be reclaimed along with the ClassLoaders they reference. | |
300 | * | |
301 | * This variable would be better named "cache", but we keep the old | |
302 | * name for compatibility with some workarounds for bug 4212439. | |
303 | */ | |
304 | 8 | private static final ConcurrentMap<CacheKey, BundleReference> cacheList |
305 | = new ConcurrentHashMap<CacheKey, BundleReference>(INITIAL_CACHE_SIZE); | |
306 | ||
307 | /** | |
308 | * This ConcurrentMap is used to keep multiple threads from loading the | |
309 | * same bundle concurrently. The table entries are <CacheKey, Thread> | |
310 | * where CacheKey is the key for the bundle that is under construction | |
311 | * and Thread is the thread that is constructing the bundle. | |
312 | * This list is manipulated in findBundleInCache and putBundleInCache. | |
313 | */ | |
314 | 8 | private static final ConcurrentMap<CacheKey, Thread> underConstruction |
315 | = new ConcurrentHashMap<CacheKey, Thread>(); | |
316 | ||
317 | /** | |
318 | * Queue for reference objects referring to class loaders or bundles. | |
319 | */ | |
320 | 8 | private static final ReferenceQueue referenceQueue = new ReferenceQueue(); |
321 | ||
322 | /** | |
323 | * The parent bundle of this bundle. | |
324 | * The parent bundle is searched by {@link #getObject getObject} | |
325 | * when this bundle does not contain a particular resource. | |
326 | */ | |
327 | 29 | protected ResourceBundle parent = null; |
328 | ||
329 | /** | |
330 | * The locale for this bundle. | |
331 | */ | |
332 | 29 | private Locale locale = null; |
333 | ||
334 | /** | |
335 | * The base bundle name for this bundle. | |
336 | */ | |
337 | private String name; | |
338 | ||
339 | /** | |
340 | * The flag indicating this bundle has expired in the cache. | |
341 | */ | |
342 | private volatile boolean expired; | |
343 | ||
344 | /** | |
345 | * The back link to the cache key. null if this bundle isn't in | |
346 | * the cache (yet) or has expired. | |
347 | */ | |
348 | private volatile CacheKey cacheKey; | |
349 | ||
350 | /** | |
351 | * A Set of the keys contained only in this ResourceBundle. | |
352 | */ | |
353 | private volatile Set<String> keySet; | |
354 | ||
355 | /** | |
356 | * Sole constructor. (For invocation by subclass constructors, typically | |
357 | * implicit.) | |
358 | */ | |
359 | 29 | public ResourceBundle() { |
360 | 29 | } |
361 | ||
362 | /** | |
363 | * Gets a string for the given key from this resource bundle or one of its parents. | |
364 | * Calling this method is equivalent to calling | |
365 | * <blockquote> | |
366 | * <code>(String) {@link #getObject(java.lang.String) getObject}(key)</code>. | |
367 | * </blockquote> | |
368 | * | |
369 | * @param key the key for the desired string | |
370 | * @exception NullPointerException if <code>key</code> is <code>null</code> | |
371 | * @exception MissingResourceException if no object for the given key can be found | |
372 | * @exception ClassCastException if the object found for the given key is not a string | |
373 | * @return the string for the given key | |
374 | */ | |
375 | public final String getString(String key) { | |
376 | 53 | return (String) getObject(key); |
377 | } | |
378 | ||
379 | /** | |
380 | * Gets a string array for the given key from this resource bundle or one of its parents. | |
381 | * Calling this method is equivalent to calling | |
382 | * <blockquote> | |
383 | * <code>(String[]) {@link #getObject(java.lang.String) getObject}(key)</code>. | |
384 | * </blockquote> | |
385 | * | |
386 | * @param key the key for the desired string array | |
387 | * @exception NullPointerException if <code>key</code> is <code>null</code> | |
388 | * @exception MissingResourceException if no object for the given key can be found | |
389 | * @exception ClassCastException if the object found for the given key is not a string array | |
390 | * @return the string array for the given key | |
391 | */ | |
392 | public final String[] getStringArray(String key) { | |
393 | 0 | return (String[]) getObject(key); |
394 | } | |
395 | ||
396 | /** | |
397 | * Gets an object for the given key from this resource bundle or one of its parents. | |
398 | * This method first tries to obtain the object from this resource bundle using | |
399 | * {@link #handleGetObject(java.lang.String) handleGetObject}. | |
400 | * If not successful, and the parent resource bundle is not null, | |
401 | * it calls the parent's <code>getObject</code> method. | |
402 | * If still not successful, it throws a MissingResourceException. | |
403 | * | |
404 | * @param key the key for the desired object | |
405 | * @exception NullPointerException if <code>key</code> is <code>null</code> | |
406 | * @exception MissingResourceException if no object for the given key can be found | |
407 | * @return the object for the given key | |
408 | */ | |
409 | public final Object getObject(String key) { | |
410 | 57 | Object obj = handleGetObject(key); |
411 | 57 | if (obj == null) { |
412 | 4 | if (parent != null) { |
413 | 4 | obj = parent.getObject(key); |
414 | } | |
415 | 4 | if (obj == null) |
416 | 0 | throw new MissingResourceException("Can't find resource for bundle " |
417 | +this.getClass().getName() | |
418 | +", key "+key, | |
419 | this.getClass().getName(), | |
420 | key); | |
421 | } | |
422 | 57 | return obj; |
423 | } | |
424 | ||
425 | /** | |
426 | * Returns the locale of this resource bundle. This method can be used after a | |
427 | * call to getBundle() to determine whether the resource bundle returned really | |
428 | * corresponds to the requested locale or is a fallback. | |
429 | * | |
430 | * @return the locale of this resource bundle | |
431 | */ | |
432 | public Locale getLocale() { | |
433 | 0 | return locale; |
434 | } | |
435 | ||
436 | /* | |
437 | * Automatic determination of the ClassLoader to be used to load | |
438 | * resources on behalf of the client. N.B. The client is getLoader's | |
439 | * caller's caller. | |
440 | */ | |
441 | private static ClassLoader getLoader() { | |
442 | // Class[] stack = getClassContext(); | |
443 | /* Magic number 2 identifies our caller's caller */ | |
444 | // Class c = stack[2]; | |
445 | 0 | Class c = com.sun.javafx.runtime.util.StringLocalization.class; |
446 | 0 | ClassLoader cl = (c == null) ? null : c.getClassLoader(); |
447 | 0 | if (cl == null) { |
448 | // When the caller's loader is the boot class loader, cl is null | |
449 | // here. In that case, ClassLoader.getSystemClassLoader() may | |
450 | // return the same class loader that the application is | |
451 | // using. We therefore use a wrapper ClassLoader to create a | |
452 | // separate scope for bundles loaded on behalf of the Java | |
453 | // runtime so that these bundles cannot be returned from the | |
454 | // cache to the application (5048280). | |
455 | 0 | cl = RBClassLoader.INSTANCE; |
456 | } | |
457 | 0 | return cl; |
458 | } | |
459 | ||
460 | // private static native Class[] getClassContext(); | |
461 | ||
462 | /** | |
463 | * A wrapper of ClassLoader.getSystemClassLoader(). | |
464 | */ | |
465 | 0 | private static class RBClassLoader extends ClassLoader { |
466 | 0 | private static final RBClassLoader INSTANCE = AccessController.doPrivileged( |
467 | 0 | new PrivilegedAction<RBClassLoader>() { |
468 | public RBClassLoader run() { | |
469 | 0 | return new RBClassLoader(); |
470 | } | |
471 | }); | |
472 | 0 | private static final ClassLoader loader = ClassLoader.getSystemClassLoader(); |
473 | ||
474 | 0 | private RBClassLoader() { |
475 | 0 | } |
476 | @Override | |
477 | public Class<?> loadClass(String name) throws ClassNotFoundException { | |
478 | 0 | if (loader != null) { |
479 | 0 | return loader.loadClass(name); |
480 | } | |
481 | 0 | return Class.forName(name); |
482 | } | |
483 | @Override | |
484 | public URL getResource(String name) { | |
485 | 0 | if (loader != null) { |
486 | 0 | return loader.getResource(name); |
487 | } | |
488 | 0 | return ClassLoader.getSystemResource(name); |
489 | } | |
490 | @Override | |
491 | public InputStream getResourceAsStream(String name) { | |
492 | 0 | if (loader != null) { |
493 | 0 | return loader.getResourceAsStream(name); |
494 | } | |
495 | 0 | return ClassLoader.getSystemResourceAsStream(name); |
496 | } | |
497 | } | |
498 | ||
499 | /** | |
500 | * Sets the parent bundle of this bundle. | |
501 | * The parent bundle is searched by {@link #getObject getObject} | |
502 | * when this bundle does not contain a particular resource. | |
503 | * | |
504 | * @param parent this bundle's parent bundle. | |
505 | */ | |
506 | protected void setParent(ResourceBundle parent) { | |
507 | 26 | assert parent != NONEXISTENT_BUNDLE; |
508 | 26 | this.parent = parent; |
509 | 26 | } |
510 | ||
511 | /** | |
512 | * Key used for cached resource bundles. The key checks the base | |
513 | * name, the locale, and the class loader to determine if the | |
514 | * resource is a match to the requested one. The loader may be | |
515 | * null, but the base name and the locale must have a non-null | |
516 | * value. | |
517 | */ | |
518 | 232 | private static final class CacheKey implements Cloneable { |
519 | // These three are the actual keys for lookup in Map. | |
520 | private String name; | |
521 | private Locale locale; | |
522 | private LoaderReference loaderRef; | |
523 | ||
524 | // bundle format which is necessary for calling | |
525 | // Control.needsReload(). | |
526 | private String format; | |
527 | ||
528 | // These time values are in CacheKey so that NONEXISTENT_BUNDLE | |
529 | // doesn't need to be cloned for caching. | |
530 | ||
531 | // The time when the bundle has been loaded | |
532 | private volatile long loadTime; | |
533 | ||
534 | // The time when the bundle expires in the cache, or either | |
535 | // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL. | |
536 | private volatile long expirationTime; | |
537 | ||
538 | // Placeholder for an error report by a Throwable | |
539 | private Throwable cause; | |
540 | ||
541 | // Hash code value cache to avoid recalculating the hash code | |
542 | // of this instance. | |
543 | private int hashCodeCache; | |
544 | ||
545 | 53 | CacheKey(String baseName, Locale locale, ClassLoader loader) { |
546 | 53 | this.name = baseName; |
547 | 53 | this.locale = locale; |
548 | 53 | if (loader == null) { |
549 | 0 | this.loaderRef = null; |
550 | } else { | |
551 | 53 | loaderRef = new LoaderReference(loader, referenceQueue, this); |
552 | } | |
553 | 53 | calculateHashCode(); |
554 | 53 | } |
555 | ||
556 | String getName() { | |
557 | 106 | return name; |
558 | } | |
559 | ||
560 | CacheKey setName(String baseName) { | |
561 | 0 | if (!this.name.equals(baseName)) { |
562 | 0 | this.name = baseName; |
563 | 0 | calculateHashCode(); |
564 | } | |
565 | 0 | return this; |
566 | } | |
567 | ||
568 | Locale getLocale() { | |
569 | 80 | return locale; |
570 | } | |
571 | ||
572 | CacheKey setLocale(Locale locale) { | |
573 | 150 | if (!this.locale.equals(locale)) { |
574 | 150 | this.locale = locale; |
575 | 150 | calculateHashCode(); |
576 | } | |
577 | 150 | return this; |
578 | } | |
579 | ||
580 | ClassLoader getLoader() { | |
581 | 243 | return (loaderRef != null) ? loaderRef.get() : null; |
582 | } | |
583 | ||
584 | @Override | |
585 | public boolean equals(Object other) { | |
586 | 188 | if (this == other) { |
587 | 40 | return true; |
588 | } | |
589 | try { | |
590 | 148 | final CacheKey otherEntry = (CacheKey)other; |
591 | //quick check to see if they are not equal | |
592 | 148 | if (hashCodeCache != otherEntry.hashCodeCache) { |
593 | 0 | return false; |
594 | } | |
595 | //are the names the same? | |
596 | 148 | if (!name.equals(otherEntry.name)) { |
597 | 0 | return false; |
598 | } | |
599 | // are the locales the same? | |
600 | 148 | if (!locale.equals(otherEntry.locale)) { |
601 | 0 | return false; |
602 | } | |
603 | //are refs (both non-null) or (both null)? | |
604 | 148 | if (loaderRef == null) { |
605 | 0 | return otherEntry.loaderRef == null; |
606 | } | |
607 | 148 | ClassLoader loader = loaderRef.get(); |
608 | 148 | return (otherEntry.loaderRef != null) |
609 | // with a null reference we can no longer find | |
610 | // out which class loader was referenced; so | |
611 | // treat it as unequal | |
612 | && (loader != null) | |
613 | && (loader == otherEntry.loaderRef.get()); | |
614 | 0 | } catch (NullPointerException e) { |
615 | 0 | } catch (ClassCastException e) { |
616 | 0 | } |
617 | 0 | return false; |
618 | } | |
619 | ||
620 | @Override | |
621 | public int hashCode() { | |
622 | 323 | return hashCodeCache; |
623 | } | |
624 | ||
625 | private void calculateHashCode() { | |
626 | 203 | hashCodeCache = name.hashCode() << 3; |
627 | 203 | hashCodeCache ^= locale.hashCode(); |
628 | 203 | ClassLoader loader = getLoader(); |
629 | 203 | if (loader != null) { |
630 | 203 | hashCodeCache ^= loader.hashCode(); |
631 | } | |
632 | 203 | } |
633 | ||
634 | @Override | |
635 | public Object clone() { | |
636 | try { | |
637 | 80 | CacheKey clone = (CacheKey) super.clone(); |
638 | 80 | if (loaderRef != null) { |
639 | 80 | clone.loaderRef = new LoaderReference(loaderRef.get(), |
640 | referenceQueue, clone); | |
641 | } | |
642 | // Clear the reference to a Throwable | |
643 | 80 | clone.cause = null; |
644 | 80 | return clone; |
645 | 0 | } catch (CloneNotSupportedException e) { |
646 | //this should never happen | |
647 | 0 | throw new InternalError(); |
648 | } | |
649 | } | |
650 | ||
651 | String getFormat() { | |
652 | 0 | return format; |
653 | } | |
654 | ||
655 | void setFormat(String format) { | |
656 | 26 | this.format = format; |
657 | 26 | } |
658 | ||
659 | private void setCause(Throwable cause) { | |
660 | 0 | if (this.cause == null) { |
661 | 0 | this.cause = cause; |
662 | } else { | |
663 | // Override the cause if the previous one is | |
664 | // ClassNotFoundException. | |
665 | 0 | if (this.cause instanceof ClassNotFoundException) { |
666 | 0 | this.cause = cause; |
667 | } | |
668 | } | |
669 | 0 | } |
670 | ||
671 | private Throwable getCause() { | |
672 | 40 | return cause; |
673 | } | |
674 | ||
675 | @Override | |
676 | public String toString() { | |
677 | 0 | String l = locale.toString(); |
678 | 0 | if (l.length() == 0) { |
679 | 0 | if (locale.getVariant().length() != 0) { |
680 | 0 | l = "__" + locale.getVariant(); |
681 | } else { | |
682 | 0 | l = "\"\""; |
683 | } | |
684 | } | |
685 | 0 | return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader() |
686 | + "(format=" + format + ")]"; | |
687 | } | |
688 | } | |
689 | ||
690 | /** | |
691 | * The common interface to get a CacheKey in LoaderReference and | |
692 | * BundleReference. | |
693 | */ | |
694 | private static interface CacheKeyReference { | |
695 | public CacheKey getCacheKey(); | |
696 | } | |
697 | ||
698 | /** | |
699 | * References to class loaders are weak references, so that they can be | |
700 | * garbage collected when nobody else is using them. The ResourceBundle | |
701 | * class has no reason to keep class loaders alive. | |
702 | */ | |
703 | private static final class LoaderReference extends WeakReference<ClassLoader> | |
704 | implements CacheKeyReference { | |
705 | private CacheKey cacheKey; | |
706 | ||
707 | LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) { | |
708 | 133 | super(referent, q); |
709 | 133 | cacheKey = key; |
710 | 133 | } |
711 | ||
712 | public CacheKey getCacheKey() { | |
713 | 0 | return cacheKey; |
714 | } | |
715 | } | |
716 | ||
717 | /** | |
718 | * References to bundles are soft references so that they can be garbage | |
719 | * collected when they have no hard references. | |
720 | */ | |
721 | private static final class BundleReference extends SoftReference<ResourceBundle> | |
722 | implements CacheKeyReference { | |
723 | private CacheKey cacheKey; | |
724 | ||
725 | BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) { | |
726 | 40 | super(referent, q); |
727 | 40 | cacheKey = key; |
728 | 40 | } |
729 | ||
730 | public CacheKey getCacheKey() { | |
731 | 110 | return cacheKey; |
732 | } | |
733 | } | |
734 | ||
735 | /** | |
736 | * Gets a resource bundle using the specified base name, the default locale, | |
737 | * and the caller's class loader. Calling this method is equivalent to calling | |
738 | * <blockquote> | |
739 | * <code>getBundle(baseName, Locale.getDefault(), this.getClass().getClassLoader())</code>, | |
740 | * </blockquote> | |
741 | * except that <code>getClassLoader()</code> is run with the security | |
742 | * privileges of <code>ResourceBundle</code>. | |
743 | * See {@link #getBundle(String, Locale, ClassLoader) getBundle} | |
744 | * for a complete description of the search and instantiation strategy. | |
745 | * | |
746 | * @param baseName the base name of the resource bundle, a fully qualified class name | |
747 | * @exception java.lang.NullPointerException | |
748 | * if <code>baseName</code> is <code>null</code> | |
749 | * @exception MissingResourceException | |
750 | * if no resource bundle for the specified base name can be found | |
751 | * @return a resource bundle for the given base name and the default locale | |
752 | */ | |
753 | public static final ResourceBundle getBundle(String baseName) | |
754 | { | |
755 | 0 | return getBundleImpl(baseName, Locale.getDefault(), |
756 | /* must determine loader here, else we break stack invariant */ | |
757 | getLoader(), | |
758 | Control.INSTANCE); | |
759 | } | |
760 | ||
761 | /** | |
762 | * Returns a resource bundle using the specified base name, the | |
763 | * default locale and the specified control. Calling this method | |
764 | * is equivalent to calling | |
765 | * <pre> | |
766 | * getBundle(baseName, Locale.getDefault(), | |
767 | * this.getClass().getClassLoader(), control), | |
768 | * </pre> | |
769 | * except that <code>getClassLoader()</code> is run with the security | |
770 | * privileges of <code>ResourceBundle</code>. See {@link | |
771 | * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the | |
772 | * complete description of the resource bundle loading process with a | |
773 | * <code>ResourceBundle.Control</code>. | |
774 | * | |
775 | * @param baseName | |
776 | * the base name of the resource bundle, a fully qualified class | |
777 | * name | |
778 | * @param control | |
779 | * the control which gives information for the resource bundle | |
780 | * loading process | |
781 | * @return a resource bundle for the given base name and the default | |
782 | * locale | |
783 | * @exception NullPointerException | |
784 | * if <code>baseName</code> or <code>control</code> is | |
785 | * <code>null</code> | |
786 | * @exception MissingResourceException | |
787 | * if no resource bundle for the specified base name can be found | |
788 | * @exception IllegalArgumentException | |
789 | * if the given <code>control</code> doesn't perform properly | |
790 | * (e.g., <code>control.getCandidateLocales</code> returns null.) | |
791 | * Note that validation of <code>control</code> is performed as | |
792 | * needed. | |
793 | * @since 1.6 | |
794 | */ | |
795 | public static final ResourceBundle getBundle(String baseName, | |
796 | Control control) { | |
797 | 0 | return getBundleImpl(baseName, Locale.getDefault(), |
798 | /* must determine loader here, else we break stack invariant */ | |
799 | getLoader(), | |
800 | control); | |
801 | } | |
802 | ||
803 | /** | |
804 | * Gets a resource bundle using the specified base name and locale, | |
805 | * and the caller's class loader. Calling this method is equivalent to calling | |
806 | * <blockquote> | |
807 | * <code>getBundle(baseName, locale, this.getClass().getClassLoader())</code>, | |
808 | * </blockquote> | |
809 | * except that <code>getClassLoader()</code> is run with the security | |
810 | * privileges of <code>ResourceBundle</code>. | |
811 | * See {@link #getBundle(String, Locale, ClassLoader) getBundle} | |
812 | * for a complete description of the search and instantiation strategy. | |
813 | * | |
814 | * @param baseName | |
815 | * the base name of the resource bundle, a fully qualified class name | |
816 | * @param locale | |
817 | * the locale for which a resource bundle is desired | |
818 | * @exception NullPointerException | |
819 | * if <code>baseName</code> or <code>locale</code> is <code>null</code> | |
820 | * @exception MissingResourceException | |
821 | * if no resource bundle for the specified base name can be found | |
822 | * @return a resource bundle for the given base name and locale | |
823 | */ | |
824 | public static final ResourceBundle getBundle(String baseName, | |
825 | Locale locale) | |
826 | { | |
827 | 0 | return getBundleImpl(baseName, locale, |
828 | /* must determine loader here, else we break stack invariant */ | |
829 | getLoader(), | |
830 | Control.INSTANCE); | |
831 | } | |
832 | ||
833 | /** | |
834 | * Returns a resource bundle using the specified base name, target | |
835 | * locale and control, and the caller's class loader. Calling this | |
836 | * method is equivalent to calling | |
837 | * <pre> | |
838 | * getBundle(baseName, targetLocale, this.getClass().getClassLoader(), | |
839 | * control), | |
840 | * </pre> | |
841 | * except that <code>getClassLoader()</code> is run with the security | |
842 | * privileges of <code>ResourceBundle</code>. See {@link | |
843 | * #getBundle(String, Locale, ClassLoader, Control) getBundle} for the | |
844 | * complete description of the resource bundle loading process with a | |
845 | * <code>ResourceBundle.Control</code>. | |
846 | * | |
847 | * @param baseName | |
848 | * the base name of the resource bundle, a fully qualified | |
849 | * class name | |
850 | * @param targetLocale | |
851 | * the locale for which a resource bundle is desired | |
852 | * @param control | |
853 | * the control which gives information for the resource | |
854 | * bundle loading process | |
855 | * @return a resource bundle for the given base name and a | |
856 | * <code>Locale</code> in <code>locales</code> | |
857 | * @exception NullPointerException | |
858 | * if <code>baseName</code>, <code>locales</code> or | |
859 | * <code>control</code> is <code>null</code> | |
860 | * @exception MissingResourceException | |
861 | * if no resource bundle for the specified base name in any | |
862 | * of the <code>locales</code> can be found. | |
863 | * @exception IllegalArgumentException | |
864 | * if the given <code>control</code> doesn't perform properly | |
865 | * (e.g., <code>control.getCandidateLocales</code> returns null.) | |
866 | * Note that validation of <code>control</code> is performed as | |
867 | * needed. | |
868 | * @since 1.6 | |
869 | */ | |
870 | public static final ResourceBundle getBundle(String baseName, Locale targetLocale, | |
871 | Control control) { | |
872 | 0 | return getBundleImpl(baseName, targetLocale, |
873 | /* must determine loader here, else we break stack invariant */ | |
874 | getLoader(), | |
875 | control); | |
876 | } | |
877 | ||
878 | /** | |
879 | * Gets a resource bundle using the specified base name, locale, and class loader. | |
880 | * | |
881 | * <p><a name="default_behavior"/> | |
882 | * Conceptually, <code>getBundle</code> uses the following strategy for locating and instantiating | |
883 | * resource bundles: | |
884 | * <p> | |
885 | * <code>getBundle</code> uses the base name, the specified locale, and the default | |
886 | * locale (obtained from {@link java.util.Locale#getDefault() Locale.getDefault}) | |
887 | * to generate a sequence of <a name="candidates"><em>candidate bundle names</em></a>. | |
888 | * If the specified locale's language, country, and variant are all empty | |
889 | * strings, then the base name is the only candidate bundle name. | |
890 | * Otherwise, the following sequence is generated from the attribute | |
891 | * values of the specified locale (language1, country1, and variant1) | |
892 | * and of the default locale (language2, country2, and variant2): | |
893 | * <ul> | |
894 | * <li> baseName + "_" + language1 + "_" + country1 + "_" + variant1 | |
895 | * <li> baseName + "_" + language1 + "_" + country1 | |
896 | * <li> baseName + "_" + language1 | |
897 | * <li> baseName + "_" + language2 + "_" + country2 + "_" + variant2 | |
898 | * <li> baseName + "_" + language2 + "_" + country2 | |
899 | * <li> baseName + "_" + language2 | |
900 | * <li> baseName | |
901 | * </ul> | |
902 | * <p> | |
903 | * Candidate bundle names where the final component is an empty string are omitted. | |
904 | * For example, if country1 is an empty string, the second candidate bundle name is omitted. | |
905 | * | |
906 | * <p> | |
907 | * <code>getBundle</code> then iterates over the candidate bundle names to find the first | |
908 | * one for which it can <em>instantiate</em> an actual resource bundle. For each candidate | |
909 | * bundle name, it attempts to create a resource bundle: | |
910 | * <ul> | |
911 | * <li> | |
912 | * First, it attempts to load a class using the candidate bundle name. | |
913 | * If such a class can be found and loaded using the specified class loader, is assignment | |
914 | * compatible with ResourceBundle, is accessible from ResourceBundle, and can be instantiated, | |
915 | * <code>getBundle</code> creates a new instance of this class and uses it as the <em>result | |
916 | * resource bundle</em>. | |
917 | * <li> | |
918 | * Otherwise, <code>getBundle</code> attempts to locate a property resource file. | |
919 | * It generates a path name from the candidate bundle name by replacing all "." characters | |
920 | * with "/" and appending the string ".properties". | |
921 | * It attempts to find a "resource" with this name using | |
922 | * {@link java.lang.ClassLoader#getResource(java.lang.String) ClassLoader.getResource}. | |
923 | * (Note that a "resource" in the sense of <code>getResource</code> has nothing to do with | |
924 | * the contents of a resource bundle, it is just a container of data, such as a file.) | |
925 | * If it finds a "resource", it attempts to create a new | |
926 | * {@link PropertyResourceBundle} instance from its contents. | |
927 | * If successful, this instance becomes the <em>result resource bundle</em>. | |
928 | * </ul> | |
929 | * | |
930 | * <p> | |
931 | * If no result resource bundle has been found, a <code>MissingResourceException</code> | |
932 | * is thrown. | |
933 | * | |
934 | * <p><a name="parent_chain"/> | |
935 | * Once a result resource bundle has been found, its <em>parent chain</em> is instantiated. | |
936 | * <code>getBundle</code> iterates over the candidate bundle names that can be | |
937 | * obtained by successively removing variant, country, and language | |
938 | * (each time with the preceding "_") from the bundle name of the result resource bundle. | |
939 | * As above, candidate bundle names where the final component is an empty string are omitted. | |
940 | * With each of the candidate bundle names it attempts to instantiate a resource bundle, as | |
941 | * described above. | |
942 | * Whenever it succeeds, it calls the previously instantiated resource | |
943 | * bundle's {@link #setParent(java.util.ResourceBundle) setParent} method | |
944 | * with the new resource bundle, unless the previously instantiated resource | |
945 | * bundle already has a non-null parent. | |
946 | * | |
947 | * <p> | |
948 | * <code>getBundle</code> caches instantiated resource bundles and | |
949 | * may return the same resource bundle instance multiple | |
950 | * times. | |
951 | * | |
952 | * <p> | |
953 | * The <code>baseName</code> argument should be a fully qualified class name. However, for | |
954 | * compatibility with earlier versions, Sun's Java SE Runtime Environments do not verify this, | |
955 | * and so it is possible to access <code>PropertyResourceBundle</code>s by specifying a | |
956 | * path name (using "/") instead of a fully qualified class name (using "."). | |
957 | * | |
958 | * <p><a name="default_behavior_example"/> | |
959 | * <strong>Example:</strong><br>The following class and property files are provided: | |
960 | * <pre> | |
961 | * MyResources.class | |
962 | * MyResources.properties | |
963 | * MyResources_fr.properties | |
964 | * MyResources_fr_CH.class | |
965 | * MyResources_fr_CH.properties | |
966 | * MyResources_en.properties | |
967 | * MyResources_es_ES.class | |
968 | * </pre> | |
969 | * The contents of all files are valid (that is, public non-abstract subclasses of <code>ResourceBundle</code> for | |
970 | * the ".class" files, syntactically correct ".properties" files). | |
971 | * The default locale is <code>Locale("en", "GB")</code>. | |
972 | * <p> | |
973 | * Calling <code>getBundle</code> with the shown locale argument values instantiates | |
974 | * resource bundles from the following sources: | |
975 | * <ul> | |
976 | * <li>Locale("fr", "CH"): result MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class | |
977 | * <li>Locale("fr", "FR"): result MyResources_fr.properties, parent MyResources.class | |
978 | * <li>Locale("de", "DE"): result MyResources_en.properties, parent MyResources.class | |
979 | * <li>Locale("en", "US"): result MyResources_en.properties, parent MyResources.class | |
980 | * <li>Locale("es", "ES"): result MyResources_es_ES.class, parent MyResources.class | |
981 | * </ul> | |
982 | * <p>The file MyResources_fr_CH.properties is never used because it is hidden by | |
983 | * MyResources_fr_CH.class. Likewise, MyResources.properties is also hidden by | |
984 | * MyResources.class. | |
985 | * | |
986 | * @param baseName the base name of the resource bundle, a fully qualified class name | |
987 | * @param locale the locale for which a resource bundle is desired | |
988 | * @param loader the class loader from which to load the resource bundle | |
989 | * @return a resource bundle for the given base name and locale | |
990 | * @exception java.lang.NullPointerException | |
991 | * if <code>baseName</code>, <code>locale</code>, or <code>loader</code> is <code>null</code> | |
992 | * @exception MissingResourceException | |
993 | * if no resource bundle for the specified base name can be found | |
994 | * @since 1.2 | |
995 | */ | |
996 | public static ResourceBundle getBundle(String baseName, Locale locale, | |
997 | ClassLoader loader) | |
998 | { | |
999 | 0 | if (loader == null) { |
1000 | 0 | throw new NullPointerException(); |
1001 | } | |
1002 | 0 | return getBundleImpl(baseName, locale, loader, Control.INSTANCE); |
1003 | } | |
1004 | ||
1005 | /** | |
1006 | * Returns a resource bundle using the specified base name, target | |
1007 | * locale, class loader and control. Unlike the {@linkplain | |
1008 | * #getBundle(String, Locale, ClassLoader) <code>getBundle</code> | |
1009 | * factory methods with no <code>control</code> argument}, the given | |
1010 | * <code>control</code> specifies how to locate and instantiate resource | |
1011 | * bundles. Conceptually, the bundle loading process with the given | |
1012 | * <code>control</code> is performed in the following steps. | |
1013 | * | |
1014 | * <p> | |
1015 | * <ol> | |
1016 | * <li>This factory method looks up the resource bundle in the cache for | |
1017 | * the specified <code>baseName</code>, <code>targetLocale</code> and | |
1018 | * <code>loader</code>. If the requested resource bundle instance is | |
1019 | * found in the cache and the time-to-live periods of the instance and | |
1020 | * all of its parent instances have not expired, the instance is returned | |
1021 | * to the caller. Otherwise, this factory method proceeds with the | |
1022 | * loading process below.</li> | |
1023 | * | |
1024 | * <li>The {@link ResourceBundle.Control#getFormats(String) | |
1025 | * control.getFormats} method is called to get resource bundle formats | |
1026 | * to produce bundle or resource names. The strings | |
1027 | * <code>"java.class"</code> and <code>"java.properties"</code> | |
1028 | * designate class-based and {@linkplain PropertyResourceBundle | |
1029 | * property}-based resource bundles, respectively. Other strings | |
1030 | * starting with <code>"java."</code> are reserved for future extensions | |
1031 | * and must not be used for application-defined formats. Other strings | |
1032 | * designate application-defined formats.</li> | |
1033 | * | |
1034 | * <li>The {@link ResourceBundle.Control#getCandidateLocales(String, | |
1035 | * Locale) control.getCandidateLocales} method is called with the target | |
1036 | * locale to get a list of <em>candidate <code>Locale</code>s</em> for | |
1037 | * which resource bundles are searched.</li> | |
1038 | * | |
1039 | * <li>The {@link ResourceBundle.Control#newBundle(String, Locale, | |
1040 | * String, ClassLoader, boolean) control.newBundle} method is called to | |
1041 | * instantiate a <code>ResourceBundle</code> for the base bundle name, a | |
1042 | * candidate locale, and a format. (Refer to the note on the cache | |
1043 | * lookup below.) This step is iterated over all combinations of the | |
1044 | * candidate locales and formats until the <code>newBundle</code> method | |
1045 | * returns a <code>ResourceBundle</code> instance or the iteration has | |
1046 | * used up all the combinations. For example, if the candidate locales | |
1047 | * are <code>Locale("de", "DE")</code>, <code>Locale("de")</code> and | |
1048 | * <code>Locale("")</code> and the formats are <code>"java.class"</code> | |
1049 | * and <code>"java.properties"</code>, then the following is the | |
1050 | * sequence of locale-format combinations to be used to call | |
1051 | * <code>control.newBundle</code>. | |
1052 | * | |
1053 | * <table style="width: 50%; text-align: left; margin-left: 40px;" | |
1054 | * border="0" cellpadding="2" cellspacing="2"> | |
1055 | * <tbody><code> | |
1056 | * <tr> | |
1057 | * <td | |
1058 | * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">Locale<br> | |
1059 | * </td> | |
1060 | * <td | |
1061 | * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">format<br> | |
1062 | * </td> | |
1063 | * </tr> | |
1064 | * <tr> | |
1065 | * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")<br> | |
1066 | * </td> | |
1067 | * <td style="vertical-align: top; width: 50%;">java.class<br> | |
1068 | * </td> | |
1069 | * </tr> | |
1070 | * <tr> | |
1071 | * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")</td> | |
1072 | * <td style="vertical-align: top; width: 50%;">java.properties<br> | |
1073 | * </td> | |
1074 | * </tr> | |
1075 | * <tr> | |
1076 | * <td style="vertical-align: top; width: 50%;">Locale("de")</td> | |
1077 | * <td style="vertical-align: top; width: 50%;">java.class</td> | |
1078 | * </tr> | |
1079 | * <tr> | |
1080 | * <td style="vertical-align: top; width: 50%;">Locale("de")</td> | |
1081 | * <td style="vertical-align: top; width: 50%;">java.properties</td> | |
1082 | * </tr> | |
1083 | * <tr> | |
1084 | * <td style="vertical-align: top; width: 50%;">Locale("")<br> | |
1085 | * </td> | |
1086 | * <td style="vertical-align: top; width: 50%;">java.class</td> | |
1087 | * </tr> | |
1088 | * <tr> | |
1089 | * <td style="vertical-align: top; width: 50%;">Locale("")</td> | |
1090 | * <td style="vertical-align: top; width: 50%;">java.properties</td> | |
1091 | * </tr> | |
1092 | * </code></tbody> | |
1093 | * </table> | |
1094 | * </li> | |
1095 | * | |
1096 | * <li>If the previous step has found no resource bundle, proceed to | |
1097 | * Step 6. If a bundle has been found that is a base bundle (a bundle | |
1098 | * for <code>Locale("")</code>), and the candidate locale list only contained | |
1099 | * <code>Locale("")</code>, return the bundle to the caller. If a bundle | |
1100 | * has been found that is a base bundle, but the candidate locale list | |
1101 | * contained locales other than Locale(""), put the bundle on hold and | |
1102 | * proceed to Step 6. If a bundle has been found that is not a base | |
1103 | * bundle, proceed to Step 7.</li> | |
1104 | * | |
1105 | * <li>The {@link ResourceBundle.Control#getFallbackLocale(String, | |
1106 | * Locale) control.getFallbackLocale} method is called to get a fallback | |
1107 | * locale (alternative to the current target locale) to try further | |
1108 | * finding a resource bundle. If the method returns a non-null locale, | |
1109 | * it becomes the next target locale and the loading process starts over | |
1110 | * from Step 3. Otherwise, if a base bundle was found and put on hold in | |
1111 | * a previous Step 5, it is returned to the caller now. Otherwise, a | |
1112 | * MissingResourceException is thrown.</li> | |
1113 | * | |
1114 | * <li>At this point, we have found a resource bundle that's not the | |
1115 | * base bundle. If this bundle set its parent during its instantiation, | |
1116 | * it is returned to the caller. Otherwise, its <a | |
1117 | * href="./ResourceBundle.html#parent_chain">parent chain</a> is | |
1118 | * instantiated based on the list of candidate locales from which it was | |
1119 | * found. Finally, the bundle is returned to the caller.</li> | |
1120 | * | |
1121 | * | |
1122 | * </ol> | |
1123 | * | |
1124 | * <p>During the resource bundle loading process above, this factory | |
1125 | * method looks up the cache before calling the {@link | |
1126 | * Control#newBundle(String, Locale, String, ClassLoader, boolean) | |
1127 | * control.newBundle} method. If the time-to-live period of the | |
1128 | * resource bundle found in the cache has expired, the factory method | |
1129 | * calls the {@link ResourceBundle.Control#needsReload(String, Locale, | |
1130 | * String, ClassLoader, ResourceBundle, long) control.needsReload} | |
1131 | * method to determine whether the resource bundle needs to be reloaded. | |
1132 | * If reloading is required, the factory method calls | |
1133 | * <code>control.newBundle</code> to reload the resource bundle. If | |
1134 | * <code>control.newBundle</code> returns <code>null</code>, the factory | |
1135 | * method puts a dummy resource bundle in the cache as a mark of | |
1136 | * nonexistent resource bundles in order to avoid lookup overhead for | |
1137 | * subsequent requests. Such dummy resource bundles are under the same | |
1138 | * expiration control as specified by <code>control</code>. | |
1139 | * | |
1140 | * <p>All resource bundles loaded are cached by default. Refer to | |
1141 | * {@link Control#getTimeToLive(String,Locale) | |
1142 | * control.getTimeToLive} for details. | |
1143 | * | |
1144 | * | |
1145 | * <p>The following is an example of the bundle loading process with the | |
1146 | * default <code>ResourceBundle.Control</code> implementation. | |
1147 | * | |
1148 | * <p>Conditions: | |
1149 | * <ul> | |
1150 | * <li>Base bundle name: <code>foo.bar.Messages</code> | |
1151 | * <li>Requested <code>Locale</code>: {@link Locale#ITALY}</li> | |
1152 | * <li>Default <code>Locale</code>: {@link Locale#FRENCH}</li> | |
1153 | * <li>Available resource bundles: | |
1154 | * <code>foo/bar/Messages_fr.properties</code> and | |
1155 | * <code>foo/bar/Messages.properties</code></li> | |
1156 | * | |
1157 | * </ul> | |
1158 | * | |
1159 | * <p>First, <code>getBundle</code> tries loading a resource bundle in | |
1160 | * the following sequence. | |
1161 | * | |
1162 | * <ul> | |
1163 | * <li>class <code>foo.bar.Messages_it_IT</code> | |
1164 | * <li>file <code>foo/bar/Messages_it_IT.properties</code> | |
1165 | * <li>class <code>foo.bar.Messages_it</code></li> | |
1166 | * <li>file <code>foo/bar/Messages_it.properties</code></li> | |
1167 | * <li>class <code>foo.bar.Messages</code></li> | |
1168 | * <li>file <code>foo/bar/Messages.properties</code></li> | |
1169 | * </ul> | |
1170 | * | |
1171 | * <p>At this point, <code>getBundle</code> finds | |
1172 | * <code>foo/bar/Messages.properties</code>, which is put on hold | |
1173 | * because it's the base bundle. <code>getBundle</code> calls {@link | |
1174 | * Control#getFallbackLocale(String, Locale) | |
1175 | * control.getFallbackLocale("foo.bar.Messages", Locale.ITALY)} which | |
1176 | * returns <code>Locale.FRENCH</code>. Next, <code>getBundle</code> | |
1177 | * tries loading a bundle in the following sequence. | |
1178 | * | |
1179 | * <ul> | |
1180 | * <li>class <code>foo.bar.Messages_fr</code></li> | |
1181 | * <li>file <code>foo/bar/Messages_fr.properties</code></li> | |
1182 | * <li>class <code>foo.bar.Messages</code></li> | |
1183 | * <li>file <code>foo/bar/Messages.properties</code></li> | |
1184 | * </ul> | |
1185 | * | |
1186 | * <p><code>getBundle</code> finds | |
1187 | * <code>foo/bar/Messages_fr.properties</code> and creates a | |
1188 | * <code>ResourceBundle</code> instance. Then, <code>getBundle</code> | |
1189 | * sets up its parent chain from the list of the candiate locales. Only | |
1190 | * <code>foo/bar/Messages.properties</code> is found in the list and | |
1191 | * <code>getBundle</code> creates a <code>ResourceBundle</code> instance | |
1192 | * that becomes the parent of the instance for | |
1193 | * <code>foo/bar/Messages_fr.properties</code>. | |
1194 | * | |
1195 | * @param baseName | |
1196 | * the base name of the resource bundle, a fully qualified | |
1197 | * class name | |
1198 | * @param targetLocale | |
1199 | * the locale for which a resource bundle is desired | |
1200 | * @param loader | |
1201 | * the class loader from which to load the resource bundle | |
1202 | * @param control | |
1203 | * the control which gives information for the resource | |
1204 | * bundle loading process | |
1205 | * @return a resource bundle for the given base name and locale | |
1206 | * @exception NullPointerException | |
1207 | * if <code>baseName</code>, <code>targetLocale</code>, | |
1208 | * <code>loader</code>, or <code>control</code> is | |
1209 | * <code>null</code> | |
1210 | * @exception MissingResourceException | |
1211 | * if no resource bundle for the specified base name can be found | |
1212 | * @exception IllegalArgumentException | |
1213 | * if the given <code>control</code> doesn't perform properly | |
1214 | * (e.g., <code>control.getCandidateLocales</code> returns null.) | |
1215 | * Note that validation of <code>control</code> is performed as | |
1216 | * needed. | |
1217 | * @since 1.6 | |
1218 | */ | |
1219 | public static ResourceBundle getBundle(String baseName, Locale targetLocale, | |
1220 | ClassLoader loader, Control control) { | |
1221 | 53 | if (loader == null || control == null) { |
1222 | 0 | throw new NullPointerException(); |
1223 | } | |
1224 | 53 | return getBundleImpl(baseName, targetLocale, loader, control); |
1225 | } | |
1226 | ||
1227 | private static ResourceBundle getBundleImpl(String baseName, Locale locale, | |
1228 | ClassLoader loader, Control control) { | |
1229 | 53 | if (locale == null || control == null) { |
1230 | 0 | throw new NullPointerException(); |
1231 | } | |
1232 | ||
1233 | // We create a CacheKey here for use by this call. The base | |
1234 | // name and loader will never change during the bundle loading | |
1235 | // process. We have to make sure that the locale is set before | |
1236 | // using it as a cache key. | |
1237 | 53 | CacheKey cacheKey = new CacheKey(baseName, locale, loader); |
1238 | 53 | ResourceBundle bundle = null; |
1239 | ||
1240 | // Quick lookup of the cache. | |
1241 | 53 | BundleReference bundleRef = cacheList.get(cacheKey); |
1242 | 53 | if (bundleRef != null) { |
1243 | 38 | bundle = bundleRef.get(); |
1244 | 38 | bundleRef = null; |
1245 | } | |
1246 | ||
1247 | // If this bundle and all of its parents are valid (not expired), | |
1248 | // then return this bundle. If any of the bundles is expired, we | |
1249 | // don't call control.needsReload here but instead drop into the | |
1250 | // complete loading process below. | |
1251 | 53 | if (isValidBundle(bundle) && hasValidParentChain(bundle)) { |
1252 | 1 | return bundle; |
1253 | } | |
1254 | ||
1255 | // No valid bundle was found in the cache, so we need to load the | |
1256 | // resource bundle and its parents. | |
1257 | ||
1258 | 52 | boolean isKnownControl = (control == Control.INSTANCE) || |
1259 | (control instanceof SingleFormatControl); | |
1260 | 52 | List<String> formats = control.getFormats(baseName); |
1261 | 52 | if (!isKnownControl && !checkList(formats)) { |
1262 | 0 | throw new IllegalArgumentException("Invalid Control: getFormats"); |
1263 | } | |
1264 | ||
1265 | 52 | ResourceBundle baseBundle = null; |
1266 | 52 | for (Locale targetLocale = locale; |
1267 | 60 | targetLocale != null; |
1268 | 8 | targetLocale = control.getFallbackLocale(baseName, targetLocale)) { |
1269 | 52 | List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale); |
1270 | 52 | if (!isKnownControl && !checkList(candidateLocales)) { |
1271 | 0 | throw new IllegalArgumentException("Invalid Control: getCandidateLocales"); |
1272 | } | |
1273 | ||
1274 | 52 | bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle); |
1275 | ||
1276 | // If the loaded bundle is the base bundle and exactly for the | |
1277 | // requested locale or the only candidate locale, then take the | |
1278 | // bundle as the resulting one. If the loaded bundle is the base | |
1279 | // bundle, it's put on hold until we finish processing all | |
1280 | // fallback locales. | |
1281 | 52 | if (isValidBundle(bundle)) { |
1282 | 52 | boolean isBaseBundle = ROOTLOCALE.equals(bundle.locale); |
1283 | 52 | if (!isBaseBundle || bundle.locale.equals(locale) |
1284 | || (candidateLocales.size() == 1 | |
1285 | && bundle.locale.equals(candidateLocales.get(0)))) { | |
1286 | 0 | break; |
1287 | } | |
1288 | ||
1289 | // If the base bundle has been loaded, keep the reference in | |
1290 | // baseBundle so that we can avoid any redundant loading in case | |
1291 | // the control specify not to cache bundles. | |
1292 | 8 | if (isBaseBundle && baseBundle == null) { |
1293 | 8 | baseBundle = bundle; |
1294 | } | |
1295 | } | |
1296 | } | |
1297 | ||
1298 | 52 | if (bundle == null) { |
1299 | 0 | if (baseBundle == null) { |
1300 | 0 | throwMissingResourceException(baseName, locale, cacheKey.getCause()); |
1301 | } | |
1302 | 0 | bundle = baseBundle; |
1303 | } | |
1304 | ||
1305 | 52 | return bundle; |
1306 | } | |
1307 | ||
1308 | /** | |
1309 | * Checks if the given <code>List</code> is not null, not empty, | |
1310 | * not having null in its elements. | |
1311 | */ | |
1312 | private static final boolean checkList(List a) { | |
1313 | 104 | boolean valid = (a != null && a.size() != 0); |
1314 | 104 | if (valid) { |
1315 | 104 | int size = a.size(); |
1316 | 306 | for (int i = 0; valid && i < size; i++) { |
1317 | 202 | valid = (a.get(i) != null); |
1318 | } | |
1319 | } | |
1320 | 104 | return valid; |
1321 | } | |
1322 | ||
1323 | private static final ResourceBundle findBundle(CacheKey cacheKey, | |
1324 | List<Locale> candidateLocales, | |
1325 | List<String> formats, | |
1326 | int index, | |
1327 | Control control, | |
1328 | ResourceBundle baseBundle) { | |
1329 | 150 | Locale targetLocale = candidateLocales.get(index); |
1330 | 150 | ResourceBundle parent = null; |
1331 | 150 | if (index != candidateLocales.size() - 1) { |
1332 | 98 | parent = findBundle(cacheKey, candidateLocales, formats, index + 1, |
1333 | control, baseBundle); | |
1334 | 52 | } else if (baseBundle != null && ROOTLOCALE.equals(targetLocale)) { |
1335 | 0 | return baseBundle; |
1336 | } | |
1337 | ||
1338 | // Before we do the real loading work, see whether we need to | |
1339 | // do some housekeeping: If references to class loaders or | |
1340 | // resource bundles have been nulled out, remove all related | |
1341 | // information from the cache. | |
1342 | Object ref; | |
1343 | 150 | while ((ref = referenceQueue.poll()) != null) { |
1344 | 0 | cacheList.remove(((CacheKeyReference)ref).getCacheKey()); |
1345 | } | |
1346 | ||
1347 | // flag indicating the resource bundle has expired in the cache | |
1348 | 150 | boolean expiredBundle = false; |
1349 | ||
1350 | // First, look up the cache to see if it's in the cache, without | |
1351 | // declaring beginLoading. | |
1352 | 150 | cacheKey.setLocale(targetLocale); |
1353 | 150 | ResourceBundle bundle = findBundleInCache(cacheKey, control); |
1354 | 150 | if (isValidBundle(bundle)) { |
1355 | 70 | expiredBundle = bundle.expired; |
1356 | 70 | if (!expiredBundle) { |
1357 | // If its parent is the one asked for by the candidate | |
1358 | // locales (the runtime lookup path), we can take the cached | |
1359 | // one. (If it's not identical, then we'd have to check the | |
1360 | // parent's parents to be consistent with what's been | |
1361 | // requested.) | |
1362 | 70 | if (bundle.parent == parent) { |
1363 | 70 | return bundle; |
1364 | } | |
1365 | // Otherwise, remove the cached one since we can't keep | |
1366 | // the same bundles having different parents. | |
1367 | 0 | BundleReference bundleRef = cacheList.get(cacheKey); |
1368 | 0 | if (bundleRef != null && bundleRef.get() == bundle) { |
1369 | 0 | cacheList.remove(cacheKey, bundleRef); |
1370 | } | |
1371 | } | |
1372 | } | |
1373 | ||
1374 | 80 | if (bundle != NONEXISTENT_BUNDLE) { |
1375 | 40 | CacheKey constKey = (CacheKey) cacheKey.clone(); |
1376 | ||
1377 | try { | |
1378 | // Try declaring loading. If beginLoading() returns true, | |
1379 | // then we can proceed. Otherwise, we need to take a look | |
1380 | // at the cache again to see if someone else has loaded | |
1381 | // the bundle and put it in the cache while we've been | |
1382 | // waiting for other loading work to complete. | |
1383 | 40 | while (!beginLoading(constKey)) { |
1384 | 0 | bundle = findBundleInCache(cacheKey, control); |
1385 | 0 | if (bundle == null) { |
1386 | 0 | continue; |
1387 | } | |
1388 | 0 | if (bundle == NONEXISTENT_BUNDLE) { |
1389 | // If the bundle is NONEXISTENT_BUNDLE, the bundle doesn't exist. | |
1390 | 0 | return parent; |
1391 | } | |
1392 | 0 | expiredBundle = bundle.expired; |
1393 | 0 | if (!expiredBundle) { |
1394 | 0 | if (bundle.parent == parent) { |
1395 | 0 | return bundle; |
1396 | } | |
1397 | 0 | BundleReference bundleRef = cacheList.get(cacheKey); |
1398 | 0 | if (bundleRef != null && bundleRef.get() == bundle) { |
1399 | 0 | cacheList.remove(cacheKey, bundleRef); |
1400 | } | |
1401 | 0 | } |
1402 | } | |
1403 | ||
1404 | try { | |
1405 | 40 | bundle = loadBundle(cacheKey, formats, control, expiredBundle); |
1406 | 40 | if (bundle != null) { |
1407 | 26 | if (bundle.parent == null) { |
1408 | 26 | bundle.setParent(parent); |
1409 | } | |
1410 | 26 | bundle.locale = targetLocale; |
1411 | 26 | bundle = putBundleInCache(cacheKey, bundle, control); |
1412 | 26 | return bundle; |
1413 | } | |
1414 | ||
1415 | // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle | |
1416 | // instance for the locale. | |
1417 | 14 | putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control); |
1418 | } finally { | |
1419 | 40 | endLoading(constKey); |
1420 | 14 | } |
1421 | } finally { | |
1422 | 40 | if (constKey.getCause() instanceof InterruptedException) { |
1423 | 0 | Thread.currentThread().interrupt(); |
1424 | } | |
1425 | } | |
1426 | } | |
1427 | 54 | assert underConstruction.get(cacheKey) != Thread.currentThread(); |
1428 | 54 | return parent; |
1429 | } | |
1430 | ||
1431 | private static final ResourceBundle loadBundle(CacheKey cacheKey, | |
1432 | List<String> formats, | |
1433 | Control control, | |
1434 | boolean reload) { | |
1435 | 40 | assert underConstruction.get(cacheKey) == Thread.currentThread(); |
1436 | ||
1437 | // Here we actually load the bundle in the order of formats | |
1438 | // specified by the getFormats() value. | |
1439 | 40 | Locale targetLocale = cacheKey.getLocale(); |
1440 | ||
1441 | 40 | ResourceBundle bundle = null; |
1442 | 40 | int size = formats.size(); |
1443 | 54 | for (int i = 0; i < size; i++) { |
1444 | 40 | String format = formats.get(i); |
1445 | try { | |
1446 | 40 | bundle = control.newBundle(cacheKey.getName(), targetLocale, format, |
1447 | cacheKey.getLoader(), reload); | |
1448 | 0 | } catch (LinkageError error) { |
1449 | // We need to handle the LinkageError case due to | |
1450 | // inconsistent case-sensitivity in ClassLoader. | |
1451 | // See 6572242 for details. | |
1452 | 0 | cacheKey.setCause(error); |
1453 | 0 | } catch (Exception cause) { |
1454 | 0 | cacheKey.setCause(cause); |
1455 | 40 | } |
1456 | 40 | if (bundle != null) { |
1457 | // Set the format in the cache key so that it can be | |
1458 | // used when calling needsReload later. | |
1459 | 26 | cacheKey.setFormat(format); |
1460 | 26 | bundle.name = cacheKey.getName(); |
1461 | 26 | bundle.locale = targetLocale; |
1462 | // Bundle provider might reuse instances. So we should make | |
1463 | // sure to clear the expired flag here. | |
1464 | 26 | bundle.expired = false; |
1465 | 26 | break; |
1466 | } | |
1467 | } | |
1468 | 40 | assert underConstruction.get(cacheKey) == Thread.currentThread(); |
1469 | ||
1470 | 40 | return bundle; |
1471 | } | |
1472 | ||
1473 | private static final boolean isValidBundle(ResourceBundle bundle) { | |
1474 | 255 | return bundle != null && bundle != NONEXISTENT_BUNDLE; |
1475 | } | |
1476 | ||
1477 | /** | |
1478 | * Determines whether any of resource bundles in the parent chain, | |
1479 | * including the leaf, have expired. | |
1480 | */ | |
1481 | private static final boolean hasValidParentChain(ResourceBundle bundle) { | |
1482 | 1 | long now = System.currentTimeMillis(); |
1483 | 3 | while (bundle != null) { |
1484 | 2 | if (bundle.expired) { |
1485 | 0 | return false; |
1486 | } | |
1487 | 2 | CacheKey key = bundle.cacheKey; |
1488 | 2 | if (key != null) { |
1489 | 2 | long expirationTime = key.expirationTime; |
1490 | 2 | if (expirationTime >= 0 && expirationTime <= now) { |
1491 | 0 | return false; |
1492 | } | |
1493 | } | |
1494 | 2 | bundle = bundle.parent; |
1495 | 2 | } |
1496 | 1 | return true; |
1497 | } | |
1498 | ||
1499 | /** | |
1500 | * Declares the beginning of actual resource bundle loading. This method | |
1501 | * returns true if the declaration is successful and the current thread has | |
1502 | * been put in underConstruction. If someone else has already begun | |
1503 | * loading, this method waits until that loading work is complete and | |
1504 | * returns false. | |
1505 | */ | |
1506 | private static final boolean beginLoading(CacheKey constKey) { | |
1507 | 40 | Thread me = Thread.currentThread(); |
1508 | Thread worker; | |
1509 | // We need to declare by putting the current Thread (me) to | |
1510 | // underConstruction that we are working on loading the specified | |
1511 | // resource bundle. If we are already working the loading, it means | |
1512 | // that the resource loading requires a recursive call. In that case, | |
1513 | // we have to proceed. (4300693) | |
1514 | 40 | if (((worker = underConstruction.putIfAbsent(constKey, me)) == null) |
1515 | || worker == me) { | |
1516 | 40 | return true; |
1517 | } | |
1518 | ||
1519 | // If someone else is working on the loading, wait until | |
1520 | // the Thread finishes the bundle loading. | |
1521 | 0 | synchronized (worker) { |
1522 | 0 | while (underConstruction.get(constKey) == worker) { |
1523 | try { | |
1524 | 0 | worker.wait(); |
1525 | 0 | } catch (InterruptedException e) { |
1526 | // record the interruption | |
1527 | 0 | constKey.setCause(e); |
1528 | 0 | } |
1529 | } | |
1530 | 0 | } |
1531 | 0 | return false; |
1532 | } | |
1533 | ||
1534 | /** | |
1535 | * Declares the end of the bundle loading. This method calls notifyAll | |
1536 | * for those who are waiting for this completion. | |
1537 | */ | |
1538 | private static final void endLoading(CacheKey constKey) { | |
1539 | // Remove this Thread from the underConstruction map and wake up | |
1540 | // those who have been waiting for me to complete this bundle | |
1541 | // loading. | |
1542 | 40 | Thread me = Thread.currentThread(); |
1543 | 40 | assert (underConstruction.get(constKey) == me); |
1544 | 40 | underConstruction.remove(constKey); |
1545 | 40 | synchronized (me) { |
1546 | 40 | me.notifyAll(); |
1547 | 40 | } |
1548 | 40 | } |
1549 | ||
1550 | /** | |
1551 | * Throw a MissingResourceException with proper message | |
1552 | */ | |
1553 | private static final void throwMissingResourceException(String baseName, | |
1554 | Locale locale, | |
1555 | Throwable cause) { | |
1556 | // If the cause is a MissingResourceException, avoid creating | |
1557 | // a long chain. (6355009) | |
1558 | 0 | if (cause instanceof MissingResourceException) { |
1559 | 0 | cause = null; |
1560 | } | |
1561 | 0 | throw new MissingResourceException("Can't find bundle for base name " |
1562 | + baseName + ", locale " + locale, | |
1563 | baseName + "_" + locale, // className | |
1564 | "", // key | |
1565 | cause); | |
1566 | } | |
1567 | ||
1568 | /** | |
1569 | * Finds a bundle in the cache. Any expired bundles are marked as | |
1570 | * `expired' and removed from the cache upon return. | |
1571 | * | |
1572 | * @param cacheKey the key to look up the cache | |
1573 | * @param control the Control to be used for the expiration control | |
1574 | * @return the cached bundle, or null if the bundle is not found in the | |
1575 | * cache or its parent has expired. <code>bundle.expire</code> is true | |
1576 | * upon return if the bundle in the cache has expired. | |
1577 | */ | |
1578 | private static final ResourceBundle findBundleInCache(CacheKey cacheKey, | |
1579 | Control control) { | |
1580 | 150 | BundleReference bundleRef = cacheList.get(cacheKey); |
1581 | 150 | if (bundleRef == null) { |
1582 | 40 | return null; |
1583 | } | |
1584 | 110 | ResourceBundle bundle = bundleRef.get(); |
1585 | 110 | if (bundle == null) { |
1586 | 0 | return null; |
1587 | } | |
1588 | 110 | ResourceBundle p = bundle.parent; |
1589 | 110 | assert p != NONEXISTENT_BUNDLE; |
1590 | // If the parent has expired, then this one must also expire. We | |
1591 | // check only the immediate parent because the actual loading is | |
1592 | // done from the root (base) to leaf (child) and the purpose of | |
1593 | // checking is to propagate expiration towards the leaf. For | |
1594 | // example, if the requested locale is ja_JP_JP and there are | |
1595 | // bundles for all of the candidates in the cache, we have a list, | |
1596 | // | |
1597 | // base <- ja <- ja_JP <- ja_JP_JP | |
1598 | // | |
1599 | // If ja has expired, then it will reload ja and the list becomes a | |
1600 | // tree. | |
1601 | // | |
1602 | // base <- ja (new) | |
1603 | // " <- ja (expired) <- ja_JP <- ja_JP_JP | |
1604 | // | |
1605 | // When looking up ja_JP in the cache, it finds ja_JP in the cache | |
1606 | // which references to the expired ja. Then, ja_JP is marked as | |
1607 | // expired and removed from the cache. This will be propagated to | |
1608 | // ja_JP_JP. | |
1609 | // | |
1610 | // Now, it's possible, for example, that while loading new ja_JP, | |
1611 | // someone else has started loading the same bundle and finds the | |
1612 | // base bundle has expired. Then, what we get from the first | |
1613 | // getBundle call includes the expired base bundle. However, if | |
1614 | // someone else didn't start its loading, we wouldn't know if the | |
1615 | // base bundle has expired at the end of the loading process. The | |
1616 | // expiration control doesn't guarantee that the returned bundle and | |
1617 | // its parents haven't expired. | |
1618 | // | |
1619 | // We could check the entire parent chain to see if there's any in | |
1620 | // the chain that has expired. But this process may never end. An | |
1621 | // extreme case would be that getTimeToLive returns 0 and | |
1622 | // needsReload always returns true. | |
1623 | 110 | if (p != null && p.expired) { |
1624 | 0 | assert bundle != NONEXISTENT_BUNDLE; |
1625 | 0 | bundle.expired = true; |
1626 | 0 | bundle.cacheKey = null; |
1627 | 0 | cacheList.remove(cacheKey, bundleRef); |
1628 | 0 | bundle = null; |
1629 | } else { | |
1630 | 110 | CacheKey key = bundleRef.getCacheKey(); |
1631 | 110 | long expirationTime = key.expirationTime; |
1632 | 110 | if (!bundle.expired && expirationTime >= 0 && |
1633 | expirationTime <= System.currentTimeMillis()) { | |
1634 | // its TTL period has expired. | |
1635 | 0 | if (bundle != NONEXISTENT_BUNDLE) { |
1636 | // Synchronize here to call needsReload to avoid | |
1637 | // redundant concurrent calls for the same bundle. | |
1638 | 0 | synchronized (bundle) { |
1639 | 0 | expirationTime = key.expirationTime; |
1640 | 0 | if (!bundle.expired && expirationTime >= 0 && |
1641 | expirationTime <= System.currentTimeMillis()) { | |
1642 | try { | |
1643 | 0 | bundle.expired = control.needsReload(key.getName(), |
1644 | key.getLocale(), | |
1645 | key.getFormat(), | |
1646 | key.getLoader(), | |
1647 | bundle, | |
1648 | key.loadTime); | |
1649 | 0 | } catch (Exception e) { |
1650 | 0 | cacheKey.setCause(e); |
1651 | 0 | } |
1652 | 0 | if (bundle.expired) { |
1653 | // If the bundle needs to be reloaded, then | |
1654 | // remove the bundle from the cache, but | |
1655 | // return the bundle with the expired flag | |
1656 | // on. | |
1657 | 0 | bundle.cacheKey = null; |
1658 | 0 | cacheList.remove(cacheKey, bundleRef); |
1659 | } else { | |
1660 | // Update the expiration control info. and reuse | |
1661 | // the same bundle instance | |
1662 | 0 | setExpirationTime(key, control); |
1663 | } | |
1664 | } | |
1665 | 0 | } |
1666 | } else { | |
1667 | // We just remove NONEXISTENT_BUNDLE from the cache. | |
1668 | 0 | cacheList.remove(cacheKey, bundleRef); |
1669 | 0 | bundle = null; |
1670 | } | |
1671 | } | |
1672 | } | |
1673 | 110 | return bundle; |
1674 | } | |
1675 | ||
1676 | /** | |
1677 | * Put a new bundle in the cache. | |
1678 | * | |
1679 | * @param cacheKey the key for the resource bundle | |
1680 | * @param bundle the resource bundle to be put in the cache | |
1681 | * @return the ResourceBundle for the cacheKey; if someone has put | |
1682 | * the bundle before this call, the one found in the cache is | |
1683 | * returned. | |
1684 | */ | |
1685 | private static final ResourceBundle putBundleInCache(CacheKey cacheKey, | |
1686 | ResourceBundle bundle, | |
1687 | Control control) { | |
1688 | 40 | setExpirationTime(cacheKey, control); |
1689 | 40 | if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) { |
1690 | 40 | CacheKey key = (CacheKey) cacheKey.clone(); |
1691 | 40 | BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key); |
1692 | 40 | bundle.cacheKey = key; |
1693 | ||
1694 | // Put the bundle in the cache if it's not been in the cache. | |
1695 | 40 | BundleReference result = cacheList.putIfAbsent(key, bundleRef); |
1696 | ||
1697 | // If someone else has put the same bundle in the cache before | |
1698 | // us and it has not expired, we should use the one in the cache. | |
1699 | 40 | if (result != null) { |
1700 | 0 | ResourceBundle rb = result.get(); |
1701 | 0 | if (rb != null && !rb.expired) { |
1702 | // Clear the back link to the cache key | |
1703 | 0 | bundle.cacheKey = null; |
1704 | 0 | bundle = rb; |
1705 | // Clear the reference in the BundleReference so that | |
1706 | // it won't be enqueued. | |
1707 | 0 | bundleRef.clear(); |
1708 | } else { | |
1709 | // Replace the invalid (garbage collected or expired) | |
1710 | // instance with the valid one. | |
1711 | 0 | cacheList.put(key, bundleRef); |
1712 | } | |
1713 | } | |
1714 | } | |
1715 | 40 | return bundle; |
1716 | } | |
1717 | ||
1718 | private static final void setExpirationTime(CacheKey cacheKey, Control control) { | |
1719 | 40 | long ttl = control.getTimeToLive(cacheKey.getName(), |
1720 | cacheKey.getLocale()); | |
1721 | 40 | if (ttl >= 0) { |
1722 | // If any expiration time is specified, set the time to be | |
1723 | // expired in the cache. | |
1724 | 0 | long now = System.currentTimeMillis(); |
1725 | 0 | cacheKey.loadTime = now; |
1726 | 0 | cacheKey.expirationTime = now + ttl; |
1727 | 0 | } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) { |
1728 | 40 | cacheKey.expirationTime = ttl; |
1729 | } else { | |
1730 | 0 | throw new IllegalArgumentException("Invalid Control: TTL=" + ttl); |
1731 | } | |
1732 | 40 | } |
1733 | ||
1734 | /** | |
1735 | * Removes all resource bundles from the cache that have been loaded | |
1736 | * using the caller's class loader. | |
1737 | * | |
1738 | * @since 1.6 | |
1739 | * @see ResourceBundle.Control#getTimeToLive(String,Locale) | |
1740 | */ | |
1741 | public static final void clearCache() { | |
1742 | 0 | clearCache(getLoader()); |
1743 | 0 | } |
1744 | ||
1745 | /** | |
1746 | * Removes all resource bundles from the cache that have been loaded | |
1747 | * using the given class loader. | |
1748 | * | |
1749 | * @param loader the class loader | |
1750 | * @exception NullPointerException if <code>loader</code> is null | |
1751 | * @since 1.6 | |
1752 | * @see ResourceBundle.Control#getTimeToLive(String,Locale) | |
1753 | */ | |
1754 | public static final void clearCache(ClassLoader loader) { | |
1755 | 0 | if (loader == null) { |
1756 | 0 | throw new NullPointerException(); |
1757 | } | |
1758 | 0 | Set<CacheKey> set = cacheList.keySet(); |
1759 | 0 | for (CacheKey key : set) { |
1760 | 0 | if (key.getLoader() == loader) { |
1761 | 0 | set.remove(key); |
1762 | } | |
1763 | } | |
1764 | 0 | } |
1765 | ||
1766 | /** | |
1767 | * Gets an object for the given key from this resource bundle. | |
1768 | * Returns null if this resource bundle does not contain an | |
1769 | * object for the given key. | |
1770 | * | |
1771 | * @param key the key for the desired object | |
1772 | * @exception NullPointerException if <code>key</code> is <code>null</code> | |
1773 | * @return the object for the given key, or null | |
1774 | */ | |
1775 | protected abstract Object handleGetObject(String key); | |
1776 | ||
1777 | /** | |
1778 | * Returns an enumeration of the keys. | |
1779 | * | |
1780 | * @return an <code>Enumeration</code> of the keys contained in | |
1781 | * this <code>ResourceBundle</code> and its parent bundles. | |
1782 | */ | |
1783 | public abstract Enumeration<String> getKeys(); | |
1784 | ||
1785 | /** | |
1786 | * Determines whether the given <code>key</code> is contained in | |
1787 | * this <code>ResourceBundle</code> or its parent bundles. | |
1788 | * | |
1789 | * @param key | |
1790 | * the resource <code>key</code> | |
1791 | * @return <code>true</code> if the given <code>key</code> is | |
1792 | * contained in this <code>ResourceBundle</code> or its | |
1793 | * parent bundles; <code>false</code> otherwise. | |
1794 | * @exception NullPointerException | |
1795 | * if <code>key</code> is <code>null</code> | |
1796 | * @since 1.6 | |
1797 | */ | |
1798 | public boolean containsKey(String key) { | |
1799 | 0 | if (key == null) { |
1800 | 0 | throw new NullPointerException(); |
1801 | } | |
1802 | 0 | for (ResourceBundle rb = this; rb != null; rb = rb.parent) { |
1803 | 0 | if (rb.handleKeySet().contains(key)) { |
1804 | 0 | return true; |
1805 | } | |
1806 | } | |
1807 | 0 | return false; |
1808 | } | |
1809 | ||
1810 | /** | |
1811 | * Returns a <code>Set</code> of all keys contained in this | |
1812 | * <code>ResourceBundle</code> and its parent bundles. | |
1813 | * | |
1814 | * @return a <code>Set</code> of all keys contained in this | |
1815 | * <code>ResourceBundle</code> and its parent bundles. | |
1816 | * @since 1.6 | |
1817 | */ | |
1818 | public Set<String> keySet() { | |
1819 | 8 | Set<String> keys = new HashSet<String>(); |
1820 | 20 | for (ResourceBundle rb = this; rb != null; rb = rb.parent) { |
1821 | 12 | keys.addAll(rb.handleKeySet()); |
1822 | } | |
1823 | 8 | return keys; |
1824 | } | |
1825 | ||
1826 | /** | |
1827 | * Returns a <code>Set</code> of the keys contained <em>only</em> | |
1828 | * in this <code>ResourceBundle</code>. | |
1829 | * | |
1830 | * <p>The default implementation returns a <code>Set</code> of the | |
1831 | * keys returned by the {@link #getKeys() getKeys} method except | |
1832 | * for the ones for which the {@link #handleGetObject(String) | |
1833 | * handleGetObject} method returns <code>null</code>. Once the | |
1834 | * <code>Set</code> has been created, the value is kept in this | |
1835 | * <code>ResourceBundle</code> in order to avoid producing the | |
1836 | * same <code>Set</code> in the next calls. Override this method | |
1837 | * in subclass implementations for faster handling. | |
1838 | * | |
1839 | * @return a <code>Set</code> of the keys contained only in this | |
1840 | * <code>ResourceBundle</code> | |
1841 | * @since 1.6 | |
1842 | */ | |
1843 | protected Set<String> handleKeySet() { | |
1844 | 0 | if (keySet == null) { |
1845 | 0 | synchronized (this) { |
1846 | 0 | if (keySet == null) { |
1847 | 0 | Set<String> keys = new HashSet<String>(); |
1848 | 0 | Enumeration<String> enumKeys = getKeys(); |
1849 | 0 | while (enumKeys.hasMoreElements()) { |
1850 | 0 | String key = enumKeys.nextElement(); |
1851 | 0 | if (handleGetObject(key) != null) { |
1852 | 0 | keys.add(key); |
1853 | } | |
1854 | 0 | } |
1855 | 0 | keySet = keys; |
1856 | } | |
1857 | 0 | } |
1858 | } | |
1859 | 0 | return keySet; |
1860 | } | |
1861 | ||
1862 | ||
1863 | ||
1864 | /** | |
1865 | * <code>ResourceBundle.Control</code> defines a set of callback methods | |
1866 | * that are invoked by the {@link ResourceBundle#getBundle(String, | |
1867 | * Locale, ClassLoader, Control) ResourceBundle.getBundle} factory | |
1868 | * methods during the bundle loading process. In other words, a | |
1869 | * <code>ResourceBundle.Control</code> collaborates with the factory | |
1870 | * methods for loading resource bundles. The default implementation of | |
1871 | * the callback methods provides the information necessary for the | |
1872 | * factory methods to perform the <a | |
1873 | * href="./ResourceBundle.html#default_behavior">default behavior</a>. | |
1874 | * | |
1875 | * <p>In addition to the callback methods, the {@link | |
1876 | * #toBundleName(String, Locale) toBundleName} and {@link | |
1877 | * #toResourceName(String, String) toResourceName} methods are defined | |
1878 | * primarily for convenience in implementing the callback | |
1879 | * methods. However, the <code>toBundleName</code> method could be | |
1880 | * overridden to provide different conventions in the organization and | |
1881 | * packaging of localized resources. The <code>toResourceName</code> | |
1882 | * method is <code>final</code> to avoid use of wrong resource and class | |
1883 | * name separators. | |
1884 | * | |
1885 | * <p>Two factory methods, {@link #getControl(List)} and {@link | |
1886 | * #getNoFallbackControl(List)}, provide | |
1887 | * <code>ResourceBundle.Control</code> instances that implement common | |
1888 | * variations of the default bundle loading process. | |
1889 | * | |
1890 | * <p>The formats returned by the {@link Control#getFormats(String) | |
1891 | * getFormats} method and candidate locales returned by the {@link | |
1892 | * ResourceBundle.Control#getCandidateLocales(String, Locale) | |
1893 | * getCandidateLocales} method must be consistent in all | |
1894 | * <code>ResourceBundle.getBundle</code> invocations for the same base | |
1895 | * bundle. Otherwise, the <code>ResourceBundle.getBundle</code> methods | |
1896 | * may return unintended bundles. For example, if only | |
1897 | * <code>"java.class"</code> is returned by the <code>getFormats</code> | |
1898 | * method for the first call to <code>ResourceBundle.getBundle</code> | |
1899 | * and only <code>"java.properties"</code> for the second call, then the | |
1900 | * second call will return the class-based one that has been cached | |
1901 | * during the first call. | |
1902 | * | |
1903 | * <p>A <code>ResourceBundle.Control</code> instance must be thread-safe | |
1904 | * if it's simultaneously used by multiple threads. | |
1905 | * <code>ResourceBundle.getBundle</code> does not synchronize to call | |
1906 | * the <code>ResourceBundle.Control</code> methods. The default | |
1907 | * implementations of the methods are thread-safe. | |
1908 | * | |
1909 | * <p>Applications can specify <code>ResourceBundle.Control</code> | |
1910 | * instances returned by the <code>getControl</code> factory methods or | |
1911 | * created from a subclass of <code>ResourceBundle.Control</code> to | |
1912 | * customize the bundle loading process. The following are examples of | |
1913 | * changing the default bundle loading process. | |
1914 | * | |
1915 | * <p><b>Example 1</b> | |
1916 | * | |
1917 | * <p>The following code lets <code>ResourceBundle.getBundle</code> look | |
1918 | * up only properties-based resources. | |
1919 | * | |
1920 | * <pre> | |
1921 | * import java.util.*; | |
1922 | * import static java.util.ResourceBundle.Control.*; | |
1923 | * ... | |
1924 | * ResourceBundle bundle = | |
1925 | * ResourceBundle.getBundle("MyResources", new Locale("fr", "CH"), | |
1926 | * ResourceBundle.Control.getControl(FORMAT_PROPERTIES)); | |
1927 | * </pre> | |
1928 | * | |
1929 | * Given the resource bundles in the <a | |
1930 | * href="./ResourceBundle.html#default_behavior_example">example</a> in | |
1931 | * the <code>ResourceBundle.getBundle</code> description, this | |
1932 | * <code>ResourceBundle.getBundle</code> call loads | |
1933 | * <code>MyResources_fr_CH.properties</code> whose parent is | |
1934 | * <code>MyResources_fr.properties</code> whose parent is | |
1935 | * <code>MyResources.properties</code>. (<code>MyResources_fr_CH.properties</code> | |
1936 | * is not hidden, but <code>MyResources_fr_CH.class</code> is.) | |
1937 | * | |
1938 | * <p><b>Example 2</b> | |
1939 | * | |
1940 | * <p>The following is an example of loading XML-based bundles | |
1941 | * using {@link Properties#loadFromXML(java.io.InputStream) | |
1942 | * Properties.loadFromXML}. | |
1943 | * | |
1944 | * <pre> | |
1945 | * ResourceBundle rb = ResourceBundle.getBundle("Messages", | |
1946 | * new ResourceBundle.Control() { | |
1947 | * public List<String> getFormats(String baseName) { | |
1948 | * if (baseName == null) | |
1949 | * throw new NullPointerException(); | |
1950 | * return Arrays.asList("xml"); | |
1951 | * } | |
1952 | * public ResourceBundle newBundle(String baseName, | |
1953 | * Locale locale, | |
1954 | * String format, | |
1955 | * ClassLoader loader, | |
1956 | * boolean reload) | |
1957 | * throws IllegalAccessException, | |
1958 | * InstantiationException, | |
1959 | * IOException { | |
1960 | * if (baseName == null || locale == null | |
1961 | * || format == null || loader == null) | |
1962 | * throw new NullPointerException(); | |
1963 | * ResourceBundle bundle = null; | |
1964 | * if (format.equals("xml")) { | |
1965 | * String bundleName = toBundleName(baseName, locale); | |
1966 | * String resourceName = toResourceName(bundleName, format); | |
1967 | * InputStream stream = null; | |
1968 | * if (reload) { | |
1969 | * URL url = loader.getResource(resourceName); | |
1970 | * if (url != null) { | |
1971 | * URLConnection connection = url.openConnection(); | |
1972 | * if (connection != null) { | |
1973 | * // Disable caches to get fresh data for | |
1974 | * // reloading. | |
1975 | * connection.setUseCaches(false); | |
1976 | * stream = connection.getInputStream(); | |
1977 | * } | |
1978 | * } | |
1979 | * } else { | |
1980 | * stream = loader.getResourceAsStream(resourceName); | |
1981 | * } | |
1982 | * if (stream != null) { | |
1983 | * BufferedInputStream bis = new BufferedInputStream(stream); | |
1984 | * bundle = new XMLResourceBundle(bis); | |
1985 | * bis.close(); | |
1986 | * } | |
1987 | * } | |
1988 | * return bundle; | |
1989 | * } | |
1990 | * }); | |
1991 | * | |
1992 | * ... | |
1993 | * | |
1994 | * private static class XMLResourceBundle extends ResourceBundle { | |
1995 | * private Properties props; | |
1996 | * XMLResourceBundle(InputStream stream) throws IOException { | |
1997 | * props = new Properties(); | |
1998 | * props.loadFromXML(stream); | |
1999 | * } | |
2000 | * protected Object handleGetObject(String key) { | |
2001 | * return props.getProperty(key); | |
2002 | * } | |
2003 | * public Enumeration<String> getKeys() { | |
2004 | * ... | |
2005 | * } | |
2006 | * } | |
2007 | * </pre> | |
2008 | * | |
2009 | * @since 1.6 | |
2010 | */ | |
2011 | 52 | public static class Control { |
2012 | /** | |
2013 | * The default format <code>List</code>, which contains the strings | |
2014 | * <code>"java.class"</code> and <code>"java.properties"</code>, in | |
2015 | * this order. This <code>List</code> is {@linkplain | |
2016 | * Collections#unmodifiableList(List) unmodifiable}. | |
2017 | * | |
2018 | * @see #getFormats(String) | |
2019 | */ | |
2020 | 8 | public static final List<String> FORMAT_DEFAULT |
2021 | = Collections.unmodifiableList(Arrays.asList("java.class", | |
2022 | "java.properties")); | |
2023 | ||
2024 | /** | |
2025 | * The class-only format <code>List</code> containing | |
2026 | * <code>"java.class"</code>. This <code>List</code> is {@linkplain | |
2027 | * Collections#unmodifiableList(List) unmodifiable}. | |
2028 | * | |
2029 | * @see #getFormats(String) | |
2030 | */ | |
2031 | 8 | public static final List<String> FORMAT_CLASS |
2032 | = Collections.unmodifiableList(Arrays.asList("java.class")); | |
2033 | ||
2034 | /** | |
2035 | * The properties-only format <code>List</code> containing | |
2036 | * <code>"java.properties"</code>. This <code>List</code> is | |
2037 | * {@linkplain Collections#unmodifiableList(List) unmodifiable}. | |
2038 | * | |
2039 | * @see #getFormats(String) | |
2040 | */ | |
2041 | 8 | public static final List<String> FORMAT_PROPERTIES |
2042 | = Collections.unmodifiableList(Arrays.asList("java.properties")); | |
2043 | ||
2044 | /** | |
2045 | * The time-to-live constant for not caching loaded resource bundle | |
2046 | * instances. | |
2047 | * | |
2048 | * @see #getTimeToLive(String, Locale) | |
2049 | */ | |
2050 | public static final long TTL_DONT_CACHE = -1; | |
2051 | ||
2052 | /** | |
2053 | * The time-to-live constant for disabling the expiration control | |
2054 | * for loaded resource bundle instances in the cache. | |
2055 | * | |
2056 | * @see #getTimeToLive(String, Locale) | |
2057 | */ | |
2058 | public static final long TTL_NO_EXPIRATION_CONTROL = -2; | |
2059 | ||
2060 | 8 | private static final Control INSTANCE = new Control(); |
2061 | ||
2062 | /** | |
2063 | * Sole constructor. (For invocation by subclass constructors, | |
2064 | * typically implicit.) | |
2065 | */ | |
2066 | 16 | protected Control() { |
2067 | 16 | } |
2068 | ||
2069 | /** | |
2070 | * Returns a <code>ResourceBundle.Control</code> in which the {@link | |
2071 | * #getFormats(String) getFormats} method returns the specified | |
2072 | * <code>formats</code>. The <code>formats</code> must be equal to | |
2073 | * one of {@link Control#FORMAT_PROPERTIES}, {@link | |
2074 | * Control#FORMAT_CLASS} or {@link | |
2075 | * Control#FORMAT_DEFAULT}. <code>ResourceBundle.Control</code> | |
2076 | * instances returned by this method are singletons and thread-safe. | |
2077 | * | |
2078 | * <p>Specifying {@link Control#FORMAT_DEFAULT} is equivalent to | |
2079 | * instantiating the <code>ResourceBundle.Control</code> class, | |
2080 | * except that this method returns a singleton. | |
2081 | * | |
2082 | * @param formats | |
2083 | * the formats to be returned by the | |
2084 | * <code>ResourceBundle.Control.getFormats</code> method | |
2085 | * @return a <code>ResourceBundle.Control</code> supporting the | |
2086 | * specified <code>formats</code> | |
2087 | * @exception NullPointerException | |
2088 | * if <code>formats</code> is <code>null</code> | |
2089 | * @exception IllegalArgumentException | |
2090 | * if <code>formats</code> is unknown | |
2091 | */ | |
2092 | public static final Control getControl(List<String> formats) { | |
2093 | 0 | if (formats.equals(Control.FORMAT_PROPERTIES)) { |
2094 | 0 | return SingleFormatControl.PROPERTIES_ONLY; |
2095 | } | |
2096 | 0 | if (formats.equals(Control.FORMAT_CLASS)) { |
2097 | 0 | return SingleFormatControl.CLASS_ONLY; |
2098 | } | |
2099 | 0 | if (formats.equals(Control.FORMAT_DEFAULT)) { |
2100 | 0 | return Control.INSTANCE; |
2101 | } | |
2102 | 0 | throw new IllegalArgumentException(); |
2103 | } | |
2104 | ||
2105 | /** | |
2106 | * Returns a <code>ResourceBundle.Control</code> in which the {@link | |
2107 | * #getFormats(String) getFormats} method returns the specified | |
2108 | * <code>formats</code> and the {@link | |
2109 | * Control#getFallbackLocale(String, Locale) getFallbackLocale} | |
2110 | * method returns <code>null</code>. The <code>formats</code> must | |
2111 | * be equal to one of {@link Control#FORMAT_PROPERTIES}, {@link | |
2112 | * Control#FORMAT_CLASS} or {@link Control#FORMAT_DEFAULT}. | |
2113 | * <code>ResourceBundle.Control</code> instances returned by this | |
2114 | * method are singletons and thread-safe. | |
2115 | * | |
2116 | * @param formats | |
2117 | * the formats to be returned by the | |
2118 | * <code>ResourceBundle.Control.getFormats</code> method | |
2119 | * @return a <code>ResourceBundle.Control</code> supporting the | |
2120 | * specified <code>formats</code> with no fallback | |
2121 | * <code>Locale</code> support | |
2122 | * @exception NullPointerException | |
2123 | * if <code>formats</code> is <code>null</code> | |
2124 | * @exception IllegalArgumentException | |
2125 | * if <code>formats</code> is unknown | |
2126 | */ | |
2127 | public static final Control getNoFallbackControl(List<String> formats) { | |
2128 | 0 | if (formats.equals(Control.FORMAT_DEFAULT)) { |
2129 | 0 | return NoFallbackControl.NO_FALLBACK; |
2130 | } | |
2131 | 0 | if (formats.equals(Control.FORMAT_PROPERTIES)) { |
2132 | 0 | return NoFallbackControl.PROPERTIES_ONLY_NO_FALLBACK; |
2133 | } | |
2134 | 0 | if (formats.equals(Control.FORMAT_CLASS)) { |
2135 | 0 | return NoFallbackControl.CLASS_ONLY_NO_FALLBACK; |
2136 | } | |
2137 | 0 | throw new IllegalArgumentException(); |
2138 | } | |
2139 | ||
2140 | /** | |
2141 | * Returns a <code>List</code> of <code>String</code>s containing | |
2142 | * formats to be used to load resource bundles for the given | |
2143 | * <code>baseName</code>. The <code>ResourceBundle.getBundle</code> | |
2144 | * factory method tries to load resource bundles with formats in the | |
2145 | * order specified by the list. The list returned by this method | |
2146 | * must have at least one <code>String</code>. The predefined | |
2147 | * formats are <code>"java.class"</code> for class-based resource | |
2148 | * bundles and <code>"java.properties"</code> for {@linkplain | |
2149 | * PropertyResourceBundle properties-based} ones. Strings starting | |
2150 | * with <code>"java."</code> are reserved for future extensions and | |
2151 | * must not be used by application-defined formats. | |
2152 | * | |
2153 | * <p>It is not a requirement to return an immutable (unmodifiable) | |
2154 | * <code>List</code>. However, the returned <code>List</code> must | |
2155 | * not be mutated after it has been returned by | |
2156 | * <code>getFormats</code>. | |
2157 | * | |
2158 | * <p>The default implementation returns {@link #FORMAT_DEFAULT} so | |
2159 | * that the <code>ResourceBundle.getBundle</code> factory method | |
2160 | * looks up first class-based resource bundles, then | |
2161 | * properties-based ones. | |
2162 | * | |
2163 | * @param baseName | |
2164 | * the base name of the resource bundle, a fully qualified class | |
2165 | * name | |
2166 | * @return a <code>List</code> of <code>String</code>s containing | |
2167 | * formats for loading resource bundles. | |
2168 | * @exception NullPointerException | |
2169 | * if <code>baseName</code> is null | |
2170 | * @see #FORMAT_DEFAULT | |
2171 | * @see #FORMAT_CLASS | |
2172 | * @see #FORMAT_PROPERTIES | |
2173 | */ | |
2174 | public List<String> getFormats(String baseName) { | |
2175 | 0 | if (baseName == null) { |
2176 | 0 | throw new NullPointerException(); |
2177 | } | |
2178 | 0 | return FORMAT_DEFAULT; |
2179 | } | |
2180 | ||
2181 | /** | |
2182 | * Returns a <code>List</code> of <code>Locale</code>s as candidate | |
2183 | * locales for <code>baseName</code> and <code>locale</code>. This | |
2184 | * method is called by the <code>ResourceBundle.getBundle</code> | |
2185 | * factory method each time the factory method tries finding a | |
2186 | * resource bundle for a target <code>Locale</code>. | |
2187 | * | |
2188 | * <p>The sequence of the candidate locales also corresponds to the | |
2189 | * runtime resource lookup path (also known as the <I>parent | |
2190 | * chain</I>), if the corresponding resource bundles for the | |
2191 | * candidate locales exist and their parents are not defined by | |
2192 | * loaded resource bundles themselves. The last element of the list | |
2193 | * must be a {@linkplain Locale#ROOT root locale} if it is desired to | |
2194 | * have the base bundle as the terminal of the parent chain. | |
2195 | * | |
2196 | * <p>If the given locale is equal to <code>Locale.ROOT</code> (the | |
2197 | * root locale), a <code>List</code> containing only the root | |
2198 | * <code>Locale</code> must be returned. In this case, the | |
2199 | * <code>ResourceBundle.getBundle</code> factory method loads only | |
2200 | * the base bundle as the resulting resource bundle. | |
2201 | * | |
2202 | * <p>It is not a requirement to return an immutable | |
2203 | * (unmodifiable) <code>List</code>. However, the returned | |
2204 | * <code>List</code> must not be mutated after it has been | |
2205 | * returned by <code>getCandidateLocales</code>. | |
2206 | * | |
2207 | * <p>The default implementation returns a <code>List</code> containing | |
2208 | * <code>Locale</code>s in the following sequence: | |
2209 | * <pre> | |
2210 | * Locale(language, country, variant) | |
2211 | * Locale(language, country) | |
2212 | * Locale(language) | |
2213 | * Locale.ROOT | |
2214 | * </pre> | |
2215 | * where <code>language</code>, <code>country</code> and | |
2216 | * <code>variant</code> are the language, country and variant values | |
2217 | * of the given <code>locale</code>, respectively. Locales where the | |
2218 | * final component values are empty strings are omitted. | |
2219 | * | |
2220 | * <p>The default implementation uses an {@link ArrayList} that | |
2221 | * overriding implementations may modify before returning it to the | |
2222 | * caller. However, a subclass must not modify it after it has | |
2223 | * been returned by <code>getCandidateLocales</code>. | |
2224 | * | |
2225 | * <p>For example, if the given <code>baseName</code> is "Messages" | |
2226 | * and the given <code>locale</code> is | |
2227 | * <code>Locale("ja", "", "XX")</code>, then a | |
2228 | * <code>List</code> of <code>Locale</code>s: | |
2229 | * <pre> | |
2230 | * Locale("ja", "", "XX") | |
2231 | * Locale("ja") | |
2232 | * Locale.ROOT | |
2233 | * </pre> | |
2234 | * is returned. And if the resource bundles for the "ja" and | |
2235 | * "" <code>Locale</code>s are found, then the runtime resource | |
2236 | * lookup path (parent chain) is: | |
2237 | * <pre> | |
2238 | * Messages_ja -> Messages | |
2239 | * </pre> | |
2240 | * | |
2241 | * @param baseName | |
2242 | * the base name of the resource bundle, a fully | |
2243 | * qualified class name | |
2244 | * @param locale | |
2245 | * the locale for which a resource bundle is desired | |
2246 | * @return a <code>List</code> of candidate | |
2247 | * <code>Locale</code>s for the given <code>locale</code> | |
2248 | * @exception NullPointerException | |
2249 | * if <code>baseName</code> or <code>locale</code> is | |
2250 | * <code>null</code> | |
2251 | */ | |
2252 | public List<Locale> getCandidateLocales(String baseName, Locale locale) { | |
2253 | 52 | if (baseName == null) { |
2254 | 0 | throw new NullPointerException(); |
2255 | } | |
2256 | 52 | String language = locale.getLanguage(); |
2257 | 52 | String country = locale.getCountry(); |
2258 | 52 | String variant = locale.getVariant(); |
2259 | ||
2260 | 52 | List<Locale> locales = new ArrayList<Locale>(4); |
2261 | 52 | if (variant.length() > 0) { |
2262 | 0 | locales.add(locale); |
2263 | } | |
2264 | 52 | if (country.length() > 0) { |
2265 | 46 | locales.add((locales.size() == 0) ? |
2266 | locale : new Locale(language, country, "")); | |
2267 | } | |
2268 | 52 | if (language.length() > 0) { |
2269 | 52 | locales.add((locales.size() == 0) ? |
2270 | locale : new Locale(language, "", "")); | |
2271 | } | |
2272 | 52 | locales.add(ROOTLOCALE); |
2273 | 52 | return locales; |
2274 | } | |
2275 | ||
2276 | /** | |
2277 | * Returns a <code>Locale</code> to be used as a fallback locale for | |
2278 | * further resource bundle searches by the | |
2279 | * <code>ResourceBundle.getBundle</code> factory method. This method | |
2280 | * is called from the factory method every time when no resulting | |
2281 | * resource bundle has been found for <code>baseName</code> and | |
2282 | * <code>locale</code>, where locale is either the parameter for | |
2283 | * <code>ResourceBundle.getBundle</code> or the previous fallback | |
2284 | * locale returned by this method. | |
2285 | * | |
2286 | * <p>The method returns <code>null</code> if no further fallback | |
2287 | * search is desired. | |
2288 | * | |
2289 | * <p>The default implementation returns the {@linkplain | |
2290 | * Locale#getDefault() default <code>Locale</code>} if the given | |
2291 | * <code>locale</code> isn't the default one. Otherwise, | |
2292 | * <code>null</code> is returned. | |
2293 | * | |
2294 | * @param baseName | |
2295 | * the base name of the resource bundle, a fully | |
2296 | * qualified class name for which | |
2297 | * <code>ResourceBundle.getBundle</code> has been | |
2298 | * unable to find any resource bundles (except for the | |
2299 | * base bundle) | |
2300 | * @param locale | |
2301 | * the <code>Locale</code> for which | |
2302 | * <code>ResourceBundle.getBundle</code> has been | |
2303 | * unable to find any resource bundles (except for the | |
2304 | * base bundle) | |
2305 | * @return a <code>Locale</code> for the fallback search, | |
2306 | * or <code>null</code> if no further fallback search | |
2307 | * is desired. | |
2308 | * @exception NullPointerException | |
2309 | * if <code>baseName</code> or <code>locale</code> | |
2310 | * is <code>null</code> | |
2311 | */ | |
2312 | public Locale getFallbackLocale(String baseName, Locale locale) { | |
2313 | 0 | if (baseName == null) { |
2314 | 0 | throw new NullPointerException(); |
2315 | } | |
2316 | 0 | Locale defaultLocale = Locale.getDefault(); |
2317 | 0 | return locale.equals(defaultLocale) ? null : defaultLocale; |
2318 | } | |
2319 | ||
2320 | /** | |
2321 | * Instantiates a resource bundle for the given bundle name of the | |
2322 | * given format and locale, using the given class loader if | |
2323 | * necessary. This method returns <code>null</code> if there is no | |
2324 | * resource bundle available for the given parameters. If a resource | |
2325 | * bundle can't be instantiated due to an unexpected error, the | |
2326 | * error must be reported by throwing an <code>Error</code> or | |
2327 | * <code>Exception</code> rather than simply returning | |
2328 | * <code>null</code>. | |
2329 | * | |
2330 | * <p>If the <code>reload</code> flag is <code>true</code>, it | |
2331 | * indicates that this method is being called because the previously | |
2332 | * loaded resource bundle has expired. | |
2333 | * | |
2334 | * <p>The default implementation instantiates a | |
2335 | * <code>ResourceBundle</code> as follows. | |
2336 | * | |
2337 | * <ul> | |
2338 | * | |
2339 | * <li>The bundle name is obtained by calling {@link | |
2340 | * #toBundleName(String, Locale) toBundleName(baseName, | |
2341 | * locale)}.</li> | |
2342 | * | |
2343 | * <li>If <code>format</code> is <code>"java.class"</code>, the | |
2344 | * {@link Class} specified by the bundle name is loaded by calling | |
2345 | * {@link ClassLoader#loadClass(String)}. Then, a | |
2346 | * <code>ResourceBundle</code> is instantiated by calling {@link | |
2347 | * Class#newInstance()}. Note that the <code>reload</code> flag is | |
2348 | * ignored for loading class-based resource bundles in this default | |
2349 | * implementation.</li> | |
2350 | * | |
2351 | * <li>If <code>format</code> is <code>"java.properties"</code>, | |
2352 | * {@link #toResourceName(String, String) toResourceName(bundlename, | |
2353 | * "properties")} is called to get the resource name. | |
2354 | * If <code>reload</code> is <code>true</code>, {@link | |
2355 | * ClassLoader#getResource(String) load.getResource} is called | |
2356 | * to get a {@link URL} for creating a {@link | |
2357 | * URLConnection}. This <code>URLConnection</code> is used to | |
2358 | * {@linkplain URLConnection#setUseCaches(boolean) disable the | |
2359 | * caches} of the underlying resource loading layers, | |
2360 | * and to {@linkplain URLConnection#getInputStream() get an | |
2361 | * <code>InputStream</code>}. | |
2362 | * Otherwise, {@link ClassLoader#getResourceAsStream(String) | |
2363 | * loader.getResourceAsStream} is called to get an {@link | |
2364 | * InputStream}. Then, a {@link | |
2365 | * PropertyResourceBundle} is constructed with the | |
2366 | * <code>InputStream</code>.</li> | |
2367 | * | |
2368 | * <li>If <code>format</code> is neither <code>"java.class"</code> | |
2369 | * nor <code>"java.properties"</code>, an | |
2370 | * <code>IllegalArgumentException</code> is thrown.</li> | |
2371 | * | |
2372 | * </ul> | |
2373 | * | |
2374 | * @param baseName | |
2375 | * the base bundle name of the resource bundle, a fully | |
2376 | * qualified class name | |
2377 | * @param locale | |
2378 | * the locale for which the resource bundle should be | |
2379 | * instantiated | |
2380 | * @param format | |
2381 | * the resource bundle format to be loaded | |
2382 | * @param loader | |
2383 | * the <code>ClassLoader</code> to use to load the bundle | |
2384 | * @param reload | |
2385 | * the flag to indicate bundle reloading; <code>true</code> | |
2386 | * if reloading an expired resource bundle, | |
2387 | * <code>false</code> otherwise | |
2388 | * @return the resource bundle instance, | |
2389 | * or <code>null</code> if none could be found. | |
2390 | * @exception NullPointerException | |
2391 | * if <code>bundleName</code>, <code>locale</code>, | |
2392 | * <code>format</code>, or <code>loader</code> is | |
2393 | * <code>null</code>, or if <code>null</code> is returned by | |
2394 | * {@link #toBundleName(String, Locale) toBundleName} | |
2395 | * @exception IllegalArgumentException | |
2396 | * if <code>format</code> is unknown, or if the resource | |
2397 | * found for the given parameters contains malformed data. | |
2398 | * @exception ClassCastException | |
2399 | * if the loaded class cannot be cast to <code>ResourceBundle</code> | |
2400 | * @exception IllegalAccessException | |
2401 | * if the class or its nullary constructor is not | |
2402 | * accessible. | |
2403 | * @exception InstantiationException | |
2404 | * if the instantiation of a class fails for some other | |
2405 | * reason. | |
2406 | * @exception ExceptionInInitializerError | |
2407 | * if the initialization provoked by this method fails. | |
2408 | * @exception SecurityException | |
2409 | * If a security manager is present and creation of new | |
2410 | * instances is denied. See {@link Class#newInstance()} | |
2411 | * for details. | |
2412 | * @exception IOException | |
2413 | * if an error occurred when reading resources using | |
2414 | * any I/O operations | |
2415 | */ | |
2416 | public ResourceBundle newBundle(String baseName, Locale locale, String format, | |
2417 | ClassLoader loader, boolean reload) | |
2418 | throws IllegalAccessException, InstantiationException, IOException { | |
2419 | 0 | String bundleName = toBundleName(baseName, locale); |
2420 | 0 | ResourceBundle bundle = null; |
2421 | 0 | if (format.equals("java.class")) { |
2422 | try { | |
2423 | 0 | Class<? extends ResourceBundle> bundleClass |
2424 | = (Class<? extends ResourceBundle>)loader.loadClass(bundleName); | |
2425 | ||
2426 | // If the class isn't a ResourceBundle subclass, throw a | |
2427 | // ClassCastException. | |
2428 | 0 | if (ResourceBundle.class.isAssignableFrom(bundleClass)) { |
2429 | 0 | bundle = bundleClass.newInstance(); |
2430 | } else { | |
2431 | 0 | throw new ClassCastException(bundleClass.getName() |
2432 | + " cannot be cast to ResourceBundle"); | |
2433 | } | |
2434 | 0 | } catch (ClassNotFoundException e) { |
2435 | 0 | } |
2436 | 0 | } else if (format.equals("java.properties")) { |
2437 | 0 | final String resourceName = toResourceName(bundleName, "properties"); |
2438 | 0 | final ClassLoader classLoader = loader; |
2439 | 0 | final boolean reloadFlag = reload; |
2440 | 0 | InputStream stream = null; |
2441 | try { | |
2442 | 0 | stream = AccessController.doPrivileged( |
2443 | 0 | new PrivilegedExceptionAction<InputStream>() { |
2444 | public InputStream run() throws IOException { | |
2445 | 0 | InputStream is = null; |
2446 | 0 | if (reloadFlag) { |
2447 | 0 | URL url = classLoader.getResource(resourceName); |
2448 | 0 | if (url != null) { |
2449 | 0 | URLConnection connection = url.openConnection(); |
2450 | 0 | if (connection != null) { |
2451 | // Disable caches to get fresh data for | |
2452 | // reloading. | |
2453 | 0 | connection.setUseCaches(false); |
2454 | 0 | is = connection.getInputStream(); |
2455 | } | |
2456 | } | |
2457 | 0 | } else { |
2458 | 0 | is = classLoader.getResourceAsStream(resourceName); |
2459 | } | |
2460 | 0 | return is; |
2461 | } | |
2462 | }); | |
2463 | 0 | } catch (PrivilegedActionException e) { |
2464 | 0 | throw (IOException) e.getException(); |
2465 | 0 | } |
2466 | 0 | if (stream != null) { |
2467 | try { | |
2468 | // bundle = new PropertyResourceBundle(stream); | |
2469 | } finally { | |
2470 | 0 | stream.close(); |
2471 | 0 | } |
2472 | } | |
2473 | 0 | } else { |
2474 | 0 | throw new IllegalArgumentException("unknown format: " + format); |
2475 | } | |
2476 | 0 | return bundle; |
2477 | } | |
2478 | ||
2479 | /** | |
2480 | * Returns the time-to-live (TTL) value for resource bundles that | |
2481 | * are loaded under this | |
2482 | * <code>ResourceBundle.Control</code>. Positive time-to-live values | |
2483 | * specify the number of milliseconds a bundle can remain in the | |
2484 | * cache without being validated against the source data from which | |
2485 | * it was constructed. The value 0 indicates that a bundle must be | |
2486 | * validated each time it is retrieved from the cache. {@link | |
2487 | * #TTL_DONT_CACHE} specifies that loaded resource bundles are not | |
2488 | * put in the cache. {@link #TTL_NO_EXPIRATION_CONTROL} specifies | |
2489 | * that loaded resource bundles are put in the cache with no | |
2490 | * expiration control. | |
2491 | * | |
2492 | * <p>The expiration affects only the bundle loading process by the | |
2493 | * <code>ResourceBundle.getBundle</code> factory method. That is, | |
2494 | * if the factory method finds a resource bundle in the cache that | |
2495 | * has expired, the factory method calls the {@link | |
2496 | * #needsReload(String, Locale, String, ClassLoader, ResourceBundle, | |
2497 | * long) needsReload} method to determine whether the resource | |
2498 | * bundle needs to be reloaded. If <code>needsReload</code> returns | |
2499 | * <code>true</code>, the cached resource bundle instance is removed | |
2500 | * from the cache. Otherwise, the instance stays in the cache, | |
2501 | * updated with the new TTL value returned by this method. | |
2502 | * | |
2503 | * <p>All cached resource bundles are subject to removal from the | |
2504 | * cache due to memory constraints of the runtime environment. | |
2505 | * Returning a large positive value doesn't mean to lock loaded | |
2506 | * resource bundles in the cache. | |
2507 | * | |
2508 | * <p>The default implementation returns {@link #TTL_NO_EXPIRATION_CONTROL}. | |
2509 | * | |
2510 | * @param baseName | |
2511 | * the base name of the resource bundle for which the | |
2512 | * expiration value is specified. | |
2513 | * @param locale | |
2514 | * the locale of the resource bundle for which the | |
2515 | * expiration value is specified. | |
2516 | * @return the time (0 or a positive millisecond offset from the | |
2517 | * cached time) to get loaded bundles expired in the cache, | |
2518 | * {@link #TTL_NO_EXPIRATION_CONTROL} to disable the | |
2519 | * expiration control, or {@link #TTL_DONT_CACHE} to disable | |
2520 | * caching. | |
2521 | * @exception NullPointerException | |
2522 | * if <code>baseName</code> or <code>locale</code> is | |
2523 | * <code>null</code> | |
2524 | */ | |
2525 | public long getTimeToLive(String baseName, Locale locale) { | |
2526 | 40 | if (baseName == null || locale == null) { |
2527 | 0 | throw new NullPointerException(); |
2528 | } | |
2529 | 40 | return TTL_NO_EXPIRATION_CONTROL; |
2530 | } | |
2531 | ||
2532 | /** | |
2533 | * Determines if the expired <code>bundle</code> in the cache needs | |
2534 | * to be reloaded based on the loading time given by | |
2535 | * <code>loadTime</code> or some other criteria. The method returns | |
2536 | * <code>true</code> if reloading is required; <code>false</code> | |
2537 | * otherwise. <code>loadTime</code> is a millisecond offset since | |
2538 | * the <a href="Calendar.html#Epoch"> <code>Calendar</code> | |
2539 | * Epoch</a>. | |
2540 | * | |
2541 | * The calling <code>ResourceBundle.getBundle</code> factory method | |
2542 | * calls this method on the <code>ResourceBundle.Control</code> | |
2543 | * instance used for its current invocation, not on the instance | |
2544 | * used in the invocation that originally loaded the resource | |
2545 | * bundle. | |
2546 | * | |
2547 | * <p>The default implementation compares <code>loadTime</code> and | |
2548 | * the last modified time of the source data of the resource | |
2549 | * bundle. If it's determined that the source data has been modified | |
2550 | * since <code>loadTime</code>, <code>true</code> is | |
2551 | * returned. Otherwise, <code>false</code> is returned. This | |
2552 | * implementation assumes that the given <code>format</code> is the | |
2553 | * same string as its file suffix if it's not one of the default | |
2554 | * formats, <code>"java.class"</code> or | |
2555 | * <code>"java.properties"</code>. | |
2556 | * | |
2557 | * @param baseName | |
2558 | * the base bundle name of the resource bundle, a | |
2559 | * fully qualified class name | |
2560 | * @param locale | |
2561 | * the locale for which the resource bundle | |
2562 | * should be instantiated | |
2563 | * @param format | |
2564 | * the resource bundle format to be loaded | |
2565 | * @param loader | |
2566 | * the <code>ClassLoader</code> to use to load the bundle | |
2567 | * @param bundle | |
2568 | * the resource bundle instance that has been expired | |
2569 | * in the cache | |
2570 | * @param loadTime | |
2571 | * the time when <code>bundle</code> was loaded and put | |
2572 | * in the cache | |
2573 | * @return <code>true</code> if the expired bundle needs to be | |
2574 | * reloaded; <code>false</code> otherwise. | |
2575 | * @exception NullPointerException | |
2576 | * if <code>baseName</code>, <code>locale</code>, | |
2577 | * <code>format</code>, <code>loader</code>, or | |
2578 | * <code>bundle</code> is <code>null</code> | |
2579 | */ | |
2580 | public boolean needsReload(String baseName, Locale locale, | |
2581 | String format, ClassLoader loader, | |
2582 | ResourceBundle bundle, long loadTime) { | |
2583 | 0 | if (bundle == null) { |
2584 | 0 | throw new NullPointerException(); |
2585 | } | |
2586 | 0 | if (format.equals("java.class") || format.equals("java.properties")) { |
2587 | 0 | format = format.substring(5); |
2588 | } | |
2589 | 0 | boolean result = false; |
2590 | try { | |
2591 | 0 | String resourceName = toResourceName(toBundleName(baseName, locale), format); |
2592 | 0 | URL url = loader.getResource(resourceName); |
2593 | 0 | if (url != null) { |
2594 | 0 | long lastModified = 0; |
2595 | 0 | URLConnection connection = url.openConnection(); |
2596 | 0 | if (connection != null) { |
2597 | // disable caches to get the correct data | |
2598 | 0 | connection.setUseCaches(false); |
2599 | 0 | if (connection instanceof JarURLConnection) { |
2600 | 0 | JarEntry ent = ((JarURLConnection)connection).getJarEntry(); |
2601 | 0 | if (ent != null) { |
2602 | 0 | lastModified = ent.getTime(); |
2603 | 0 | if (lastModified == -1) { |
2604 | 0 | lastModified = 0; |
2605 | } | |
2606 | } | |
2607 | 0 | } else { |
2608 | 0 | lastModified = connection.getLastModified(); |
2609 | } | |
2610 | } | |
2611 | 0 | result = lastModified >= loadTime; |
2612 | } | |
2613 | 0 | } catch (NullPointerException npe) { |
2614 | 0 | throw npe; |
2615 | 0 | } catch (Exception e) { |
2616 | // ignore other exceptions | |
2617 | 0 | } |
2618 | 0 | return result; |
2619 | } | |
2620 | ||
2621 | /** | |
2622 | * Converts the given <code>baseName</code> and <code>locale</code> | |
2623 | * to the bundle name. This method is called from the default | |
2624 | * implementation of the {@link #newBundle(String, Locale, String, | |
2625 | * ClassLoader, boolean) newBundle} and {@link #needsReload(String, | |
2626 | * Locale, String, ClassLoader, ResourceBundle, long) needsReload} | |
2627 | * methods. | |
2628 | * | |
2629 | * <p>This implementation returns the following value: | |
2630 | * <pre> | |
2631 | * baseName + "_" + language + "_" + country + "_" + variant | |
2632 | * </pre> | |
2633 | * where <code>language</code>, <code>country</code> and | |
2634 | * <code>variant</code> are the language, country and variant values | |
2635 | * of <code>locale</code>, respectively. Final component values that | |
2636 | * are empty Strings are omitted along with the preceding '_'. If | |
2637 | * all of the values are empty strings, then <code>baseName</code> | |
2638 | * is returned. | |
2639 | * | |
2640 | * <p>For example, if <code>baseName</code> is | |
2641 | * <code>"baseName"</code> and <code>locale</code> is | |
2642 | * <code>Locale("ja", "", "XX")</code>, then | |
2643 | * <code>"baseName_ja_ _XX"</code> is returned. If the given | |
2644 | * locale is <code>Locale("en")</code>, then | |
2645 | * <code>"baseName_en"</code> is returned. | |
2646 | * | |
2647 | * <p>Overriding this method allows applications to use different | |
2648 | * conventions in the organization and packaging of localized | |
2649 | * resources. | |
2650 | * | |
2651 | * @param baseName | |
2652 | * the base name of the resource bundle, a fully | |
2653 | * qualified class name | |
2654 | * @param locale | |
2655 | * the locale for which a resource bundle should be | |
2656 | * loaded | |
2657 | * @return the bundle name for the resource bundle | |
2658 | * @exception NullPointerException | |
2659 | * if <code>baseName</code> or <code>locale</code> | |
2660 | * is <code>null</code> | |
2661 | */ | |
2662 | public String toBundleName(String baseName, Locale locale) { | |
2663 | 27 | if (locale == ROOTLOCALE) { |
2664 | 0 | return baseName; |
2665 | } | |
2666 | ||
2667 | 27 | String language = locale.getLanguage(); |
2668 | 27 | String country = locale.getCountry(); |
2669 | 27 | String variant = locale.getVariant(); |
2670 | ||
2671 | // it's safe to use '==' to compare strings here, as they are intern'ed. | |
2672 | 27 | if (language == "" && country == "" && variant == "") { |
2673 | 0 | return baseName; |
2674 | } | |
2675 | ||
2676 | 27 | StringBuilder sb = new StringBuilder(baseName); |
2677 | 27 | sb.append('_'); |
2678 | 27 | if (variant != "") { |
2679 | 0 | sb.append(language).append('_').append(country).append('_').append(variant); |
2680 | 27 | } else if (country != "") { |
2681 | 12 | sb.append(language).append('_').append(country); |
2682 | } else { | |
2683 | 15 | sb.append(language); |
2684 | } | |
2685 | 27 | return sb.toString(); |
2686 | ||
2687 | } | |
2688 | ||
2689 | /** | |
2690 | * Converts the given <code>bundleName</code> to the form required | |
2691 | * by the {@link ClassLoader#getResource ClassLoader.getResource} | |
2692 | * method by replacing all occurrences of <code>'.'</code> in | |
2693 | * <code>bundleName</code> with <code>'/'</code> and appending a | |
2694 | * <code>'.'</code> and the given file <code>suffix</code>. For | |
2695 | * example, if <code>bundleName</code> is | |
2696 | * <code>"foo.bar.MyResources_ja_JP"</code> and <code>suffix</code> | |
2697 | * is <code>"properties"</code>, then | |
2698 | * <code>"foo/bar/MyResources_ja_JP.properties"</code> is returned. | |
2699 | * | |
2700 | * @param bundleName | |
2701 | * the bundle name | |
2702 | * @param suffix | |
2703 | * the file type suffix | |
2704 | * @return the converted resource name | |
2705 | * @exception NullPointerException | |
2706 | * if <code>bundleName</code> or <code>suffix</code> | |
2707 | * is <code>null</code> | |
2708 | */ | |
2709 | public final String toResourceName(String bundleName, String suffix) { | |
2710 | 27 | StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length()); |
2711 | 27 | sb.append(bundleName.replace('.', '/')).append('.').append(suffix); |
2712 | 27 | return sb.toString(); |
2713 | } | |
2714 | } | |
2715 | ||
2716 | 0 | private static class SingleFormatControl extends Control { |
2717 | 0 | private static final Control PROPERTIES_ONLY |
2718 | = new SingleFormatControl(FORMAT_PROPERTIES); | |
2719 | ||
2720 | 0 | private static final Control CLASS_ONLY |
2721 | = new SingleFormatControl(FORMAT_CLASS); | |
2722 | ||
2723 | private final List<String> formats; | |
2724 | ||
2725 | 0 | protected SingleFormatControl(List<String> formats) { |
2726 | 0 | this.formats = formats; |
2727 | 0 | } |
2728 | ||
2729 | @Override | |
2730 | public List<String> getFormats(String baseName) { | |
2731 | 0 | if (baseName == null) { |
2732 | 0 | throw new NullPointerException(); |
2733 | } | |
2734 | 0 | return formats; |
2735 | } | |
2736 | } | |
2737 | ||
2738 | 0 | private static final class NoFallbackControl extends SingleFormatControl { |
2739 | 0 | private static final Control NO_FALLBACK |
2740 | = new NoFallbackControl(FORMAT_DEFAULT); | |
2741 | ||
2742 | 0 | private static final Control PROPERTIES_ONLY_NO_FALLBACK |
2743 | = new NoFallbackControl(FORMAT_PROPERTIES); | |
2744 | ||
2745 | 0 | private static final Control CLASS_ONLY_NO_FALLBACK |
2746 | = new NoFallbackControl(FORMAT_CLASS); | |
2747 | ||
2748 | protected NoFallbackControl(List<String> formats) { | |
2749 | 0 | super(formats); |
2750 | 0 | } |
2751 | ||
2752 | @Override | |
2753 | public Locale getFallbackLocale(String baseName, Locale locale) { | |
2754 | 0 | if (baseName == null || locale == null) { |
2755 | 0 | throw new NullPointerException(); |
2756 | } | |
2757 | 0 | return null; |
2758 | } | |
2759 | } | |
2760 | } |