001: /*
002: *******************************************************************************
003: * Copyright (C) 2004-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007:
008: package com.ibm.icu.util;
009:
010: import java.lang.ref.SoftReference;
011: import java.util.HashMap;
012: import java.util.Locale;
013: import java.util.Map;
014: import java.util.MissingResourceException;
015: import java.util.ResourceBundle;
016:
017: import com.ibm.icu.impl.ICUResourceBundle;
018: import com.ibm.icu.impl.ICUResourceBundleReader;
019: import com.ibm.icu.impl.ResourceBundleWrapper;
020: import com.ibm.icu.util.ULocale;
021:
022: /**
023: * A class representing a collection of resource information pertaining to a given
024: * locale. A resource bundle provides a way of accessing locale- specfic information in
025: * a data file. You create a resource bundle that manages the resources for a given
026: * locale and then ask it for individual resources.
027: * <P>
028: * In ResourceBundle class, an object is created
029: * and the sub items are fetched using getString, getObject methods.
030: * In UResourceBundle,each individual element of a resource is a resource by itself.
031: *
032: * <P>
033: * Resource bundles in ICU are currently defined using text files which conform to the following
034: * <a href="http://dev.icu-project.org/cgi-bin/viewcvs.cgi/icuhtml/design/bnf_rb.txt">BNF definition</a>.
035: * More on resource bundle concepts and syntax can be found in the
036: * <a href="http://icu.sourceforge.net/userguide/ResourceManagement.html">Users Guide</a>.
037: * <P>
038: *
039: * The packaging of ICU *.res files can be of two types
040: * ICU4C:
041: * <pre>
042: * root.res
043: * |
044: * --------
045: * | |
046: * fr.res en.res
047: * |
048: * --------
049: * | |
050: * fr_CA.res fr_FR.res
051: * </pre>
052: * JAVA/JDK:
053: * <pre>
054: * LocaleElements.res
055: * |
056: * -------------------
057: * | |
058: * LocaleElements_fr.res LocaleElements_en.res
059: * |
060: * ---------------------------
061: * | |
062: * LocaleElements_fr_CA.res LocaleElements_fr_FR.res
063: * </pre>
064: * Depending on the organization of your resources, the syntax to getBundleInstance will change.
065: * To open ICU style organization use:
066: * <pre>
067: * UResourceBundle bundle = UResourceBundle.getBundleInstance("com/ibm/icu/impl/data/icudt30b", "en_US");
068: * </pre>
069: * To open Java/JDK style organization use:
070: * <pre>
071: * UResourceBundle bundle = UResourceBundle.getBundleInstance("com.ibm.icu.impl.data.LocaleElements", "en_US");
072: * </pre>
073: * @stable ICU 3.0
074: * @author ram
075: */
076: public abstract class UResourceBundle extends ResourceBundle {
077:
078: /**
079: * Creates a resource bundle using the specified base name and locale.
080: * ICU_DATA_CLASS is used as the default root.
081: * @param baseName the base name of the resource bundle, a fully qualified class name
082: * @param localeName the locale for which a resource bundle is desired
083: * @exception MissingResourceException
084: * if no resource bundle for the specified base name can be found
085: * @return a resource bundle for the given base name and locale
086: * @stable ICU 3.0
087: */
088: public static UResourceBundle getBundleInstance(String baseName,
089: String localeName) {
090: return getBundleInstance(baseName, localeName,
091: ICUResourceBundle.ICU_DATA_CLASS_LOADER, false);
092: }
093:
094: /**
095: * Creates a resource bundle using the specified base name, locale, and class root.
096: *
097: * @param baseName the base name of the resource bundle, a fully qualified class name
098: * @param localeName the locale for which a resource bundle is desired
099: * @param root the class object from which to load the resource bundle
100: * @exception MissingResourceException
101: * if no resource bundle for the specified base name can be found
102: * @return a resource bundle for the given base name and locale
103: * @stable ICU 3.0
104: */
105: public static UResourceBundle getBundleInstance(String baseName,
106: String localeName, ClassLoader root) {
107: return getBundleInstance(baseName, localeName, root, false);
108: }
109:
110: /**
111: * Creates a resource bundle using the specified base name, locale, and class root.
112: *
113: * @param baseName the base name of the resource bundle, a fully qualified class name
114: * @param localeName the locale for which a resource bundle is desired
115: * @param root the class object from which to load the resource bundle
116: * @param disableFallback Option to disable locale inheritence.
117: * If true the fallback chain will not be built.
118: * @exception MissingResourceException
119: * if no resource bundle for the specified base name can be found
120: * @return a resource bundle for the given base name and locale
121: * @stable ICU 3.0
122: *
123: */
124: protected static UResourceBundle getBundleInstance(String baseName,
125: String localeName, ClassLoader root, boolean disableFallback) {
126: return instantiateBundle(baseName, localeName, root,
127: disableFallback);
128: }
129:
130: /**
131: * Sole constructor. (For invocation by subclass constructors, typically
132: * implicit.) This is public for compatibility with Java, whose compiler
133: * will generate public default constructors for an abstract class.
134: * @stable ICU 3.0
135: */
136: public UResourceBundle() {
137: }
138:
139: /**
140: * Creates a UResourceBundle for the locale specified, from which users can extract resources by using
141: * their corresponding keys.
142: * @param locale specifies the locale for which we want to open the resource.
143: * If null the bundle for default locale is opened.
144: * @return a resource bundle for the given locale
145: * @stable ICU 3.0
146: */
147: public static UResourceBundle getBundleInstance(ULocale locale) {
148: if (locale == null) {
149: locale = ULocale.getDefault();
150: }
151: return getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,
152: locale.toString(),
153: ICUResourceBundle.ICU_DATA_CLASS_LOADER);
154: }
155:
156: /**
157: * Creates a UResourceBundle for the default locale and specified base name,
158: * from which users can extract resources by using their corresponding keys.
159: * @param baseName specifies the locale for which we want to open the resource.
160: * If null the bundle for default locale is opened.
161: * @return a resource bundle for the given base name and default locale
162: * @stable ICU 3.0
163: */
164: public static UResourceBundle getBundleInstance(String baseName) {
165: return getBundleInstance(baseName, ULocale.getDefault()
166: .toString(), ICUResourceBundle.ICU_DATA_CLASS_LOADER);
167: }
168:
169: /**
170: * Creates a UResourceBundle for the specified locale and specified base name,
171: * from which users can extract resources by using their corresponding keys.
172: * @param baseName specifies the locale for which we want to open the resource.
173: * If null the bundle for default locale is opened.
174: * @param locale specifies the locale for which we want to open the resource.
175: * If null the bundle for default locale is opened.
176: * @return a resource bundle for the given base name and locale
177: * @stable ICU 3.0
178: */
179:
180: public static UResourceBundle getBundleInstance(String baseName,
181: Locale locale) {
182: return getBundleInstance(baseName, ULocale.forLocale(locale));
183: }
184:
185: /**
186: * Creates a UResourceBundle, from which users can extract resources by using
187: * their corresponding keys.
188: * @param baseName string containing the name of the data package.
189: * If null the default ICU package name is used.
190: * @param locale specifies the locale for which we want to open the resource.
191: * If null the bundle for default locale is opened.
192: * @return a resource bundle for the given base name and locale
193: * @stable ICU 3.0
194: */
195: public static UResourceBundle getBundleInstance(String baseName,
196: ULocale locale) {
197: return getBundleInstance(baseName, locale.toString(),
198: ICUResourceBundle.ICU_DATA_CLASS_LOADER);
199: }
200:
201: /**
202: * Creates a UResourceBundle for the specified locale and specified base name,
203: * from which users can extract resources by using their corresponding keys.
204: * @param baseName specifies the locale for which we want to open the resource.
205: * If null the bundle for default locale is opened.
206: * @param locale specifies the locale for which we want to open the resource.
207: * If null the bundle for default locale is opened.
208: * @param loader the loader to use
209: * @return a resource bundle for the given base name and locale
210: * @internal revisit for ICU 3.6
211: * @deprecated This API is ICU internal only.
212: */
213: public static UResourceBundle getBundleInstance(String baseName,
214: Locale locale, ClassLoader loader) {
215: return getBundleInstance(baseName, ULocale.forLocale(locale),
216: loader);
217: }
218:
219: /**
220: * Creates a UResourceBundle, from which users can extract resources by using
221: * their corresponding keys.
222: * @param baseName string containing the name of the data package.
223: * If null the default ICU package name is used.
224: * @param locale specifies the locale for which we want to open the resource.
225: * If null the bundle for default locale is opened.
226: * @param loader the loader to use
227: * @return a resource bundle for the given base name and locale
228: * @internal revisit for ICU 3.6
229: * @deprecated This API is ICU internal only.
230: */
231: public static UResourceBundle getBundleInstance(String baseName,
232: ULocale locale, ClassLoader loader) {
233: return getBundleInstance(baseName, locale.toString(), loader);
234: }
235:
236: /**
237: * Returns the RFC 3066 conformant locale id of this resource bundle.
238: * This method can be used after a call to getBundleInstance() to
239: * determine whether the resource bundle returned really
240: * corresponds to the requested locale or is a fallback.
241: *
242: * @return the locale of this resource bundle
243: * @stable ICU 3.0
244: */
245: public abstract ULocale getULocale();
246:
247: /**
248: * Gets the localeID
249: * @return The string representation of the localeID
250: * @stable ICU 3.0
251: */
252: protected abstract String getLocaleID();
253:
254: /**
255: * Gets the base name of the resource bundle
256: * @return The string representation of the base name
257: * @stable ICU 3.0
258: */
259: protected abstract String getBaseName();
260:
261: /**
262: * Gets the parent bundle
263: * @return The parent bundle
264: * @stable ICU 3.0
265: */
266: protected abstract UResourceBundle getParent();
267:
268: /**
269: * Get the locale of this bundle
270: * @return the locale of this resource bundle
271: * @stable ICU 3.0
272: */
273: public Locale getLocale() {
274: return getULocale().toLocale();
275: }
276:
277: // Cache for ResourceBundle instantiation
278: private static SoftReference BUNDLE_CACHE;
279:
280: private static void addToCache(ResourceCacheKey key,
281: UResourceBundle b) {
282: Map m = null;
283: if (BUNDLE_CACHE != null) {
284: m = (Map) BUNDLE_CACHE.get();
285: }
286: if (m == null) {
287: m = new HashMap();
288: BUNDLE_CACHE = new SoftReference(m);
289: }
290: m.put(key, b);
291: }
292:
293: /**
294: * @internal revisit for ICU 3.6
295: * @deprecated This API is ICU internal only.
296: */
297: protected static synchronized void addToCache(ClassLoader cl,
298: String fullName, ULocale defaultLocale, UResourceBundle b) {
299: cacheKey.setKeyValues(cl, fullName, defaultLocale);
300: addToCache((ResourceCacheKey) cacheKey.clone(), b);
301: }
302:
303: /**
304: * @internal revisit for ICU 3.6
305: * @deprecated This API is ICU internal only.
306: */
307: protected static synchronized UResourceBundle loadFromCache(
308: ClassLoader cl, String fullName, ULocale defaultLocale) {
309: cacheKey.setKeyValues(cl, fullName, defaultLocale);
310: return loadFromCache(cacheKey);
311: }
312:
313: private static UResourceBundle loadFromCache(ResourceCacheKey key) {
314: if (BUNDLE_CACHE != null) {
315: Map m = (Map) BUNDLE_CACHE.get();
316: if (m != null) {
317: return (UResourceBundle) m.get(key);
318: }
319: }
320: return null;
321: }
322:
323: /**
324: * Key used for cached resource bundles. The key checks
325: * the resource name, the class root, and the default
326: * locale to determine if the resource is a match to the
327: * requested one. The root may be null, but the
328: * searchName and the default locale must have a non-null value.
329: * Note that the default locale may change over time, and
330: * lookup should always be based on the current default
331: * locale (if at all).
332: */
333: private static final class ResourceCacheKey implements Cloneable {
334: private SoftReference loaderRef;
335: private String searchName;
336: private ULocale defaultLocale;
337: private int hashCodeCache;
338:
339: ///CLOVER:OFF
340: public boolean equals(Object other) {
341: if (this == other) {
342: return true;
343: }
344: try {
345: final ResourceCacheKey otherEntry = (ResourceCacheKey) other;
346: //quick check to see if they are not equal
347: if (hashCodeCache != otherEntry.hashCodeCache) {
348: return false;
349: }
350: //are the names the same?
351: if (!searchName.equals(otherEntry.searchName)) {
352: return false;
353: }
354: // are the default locales the same?
355: if (defaultLocale == null) {
356: if (otherEntry.defaultLocale != null) {
357: return false;
358: }
359: } else {
360: if (!defaultLocale.equals(otherEntry.defaultLocale)) {
361: return false;
362: }
363: }
364: //are refs (both non-null) or (both null)?
365: if (loaderRef == null) {
366: return otherEntry.loaderRef == null;
367: } else {
368: return (otherEntry.loaderRef != null)
369: && (loaderRef.get() == otherEntry.loaderRef
370: .get());
371: }
372: } catch (NullPointerException e) {
373: return false;
374: } catch (ClassCastException e) {
375: return false;
376: }
377: }
378:
379: public int hashCode() {
380: return hashCodeCache;
381: }
382:
383: public Object clone() {
384: try {
385: return super .clone();
386: } catch (CloneNotSupportedException e) {
387: //this should never happen
388: throw new IllegalStateException();
389: }
390: }
391:
392: ///CLOVER:ON
393: private void setKeyValues(ClassLoader root, String searchName,
394: ULocale defaultLocale) {
395: this .searchName = searchName;
396: hashCodeCache = searchName.hashCode();
397: this .defaultLocale = defaultLocale;
398: if (defaultLocale != null) {
399: hashCodeCache ^= defaultLocale.hashCode();
400: }
401: if (root == null) {
402: this .loaderRef = null;
403: } else {
404: loaderRef = new SoftReference(root);
405: hashCodeCache ^= root.hashCode();
406: }
407: }
408:
409: ///CLOVER:OFF
410: private void clear() {
411: setKeyValues(null, "", null);
412: }
413: ///CLOVER:ON
414: }
415:
416: private static final ResourceCacheKey cacheKey = new ResourceCacheKey();
417:
418: private static final int ROOT_MISSING = 0;
419: private static final int ROOT_ICU = 1;
420: private static final int ROOT_JAVA = 2;
421:
422: private static SoftReference ROOT_CACHE;
423:
424: private static int getRootType(String baseName, ClassLoader root) {
425: Map m = null;
426: Integer rootType;
427:
428: if (ROOT_CACHE != null) {
429: m = (Map) ROOT_CACHE.get();
430: }
431:
432: if (m == null) {
433: m = new HashMap();
434: ROOT_CACHE = new SoftReference(m);
435: }
436:
437: rootType = (Integer) m.get(baseName);
438:
439: if (rootType == null) {
440: String rootLocale = (baseName.indexOf('.') == -1) ? "root"
441: : "";
442: int rt = ROOT_MISSING;
443: UResourceBundle b = null;
444: try {
445: b = ICUResourceBundle.getBundleInstance(baseName,
446: rootLocale, root, true);
447: rt = ROOT_ICU;
448: } catch (MissingResourceException ex) {
449: try {
450: b = ResourceBundleWrapper.getBundleInstance(
451: baseName, rootLocale, root, true);
452: rt = ROOT_JAVA;
453: } catch (MissingResourceException e) {
454: //throw away the exception
455: }
456: }
457:
458: rootType = new Integer(rt);
459: m.put(baseName, rootType);
460: }
461:
462: return rootType.intValue();
463: }
464:
465: private static void setRootType(String baseName, int rootType) {
466: Integer rt = new Integer(rootType);
467: Map m = null;
468:
469: if (ROOT_CACHE != null) {
470: m = (Map) ROOT_CACHE.get();
471: } else {
472: m = new HashMap();
473: ROOT_CACHE = new SoftReference(m);
474: }
475:
476: m.put(baseName, rt);
477: }
478:
479: /**
480: * Loads a new resource bundle for the give base name, locale and class loader.
481: * Optionally will disable loading of fallback bundles.
482: * @param baseName the base name of the resource bundle, a fully qualified class name
483: * @param localeName the locale for which a resource bundle is desired
484: * @param root the class object from which to load the resource bundle
485: * @param disableFallback disables loading of fallback lookup chain
486: * @exception MissingResourceException
487: * if no resource bundle for the specified base name can be found
488: * @return a resource bundle for the given base name and locale
489: * @stable ICU 3.0
490: */
491: protected static synchronized UResourceBundle instantiateBundle(
492: String baseName, String localeName, ClassLoader root,
493: boolean disableFallback) {
494: UResourceBundle b = null;
495: int rootType = getRootType(baseName, root);
496:
497: ULocale defaultLocale = ULocale.getDefault();
498:
499: switch (rootType) {
500: case ROOT_ICU:
501: if (disableFallback) {
502: String fullName = ICUResourceBundleReader.getFullName(
503: baseName, localeName);
504:
505: cacheKey.setKeyValues(root, fullName, defaultLocale);
506: b = loadFromCache(cacheKey);
507:
508: if (b == null) {
509: b = ICUResourceBundle.getBundleInstance(baseName,
510: localeName, root, disableFallback);
511: cacheKey
512: .setKeyValues(root, fullName, defaultLocale);
513: addToCache(cacheKey, b);
514: }
515: } else {
516: b = ICUResourceBundle.getBundleInstance(baseName,
517: localeName, root, disableFallback);
518: }
519:
520: return b;
521:
522: case ROOT_JAVA:
523: return ResourceBundleWrapper.getBundleInstance(baseName,
524: localeName, root, disableFallback);
525:
526: default:
527: try {
528: b = ICUResourceBundle.getBundleInstance(baseName,
529: localeName, root, disableFallback);
530: setRootType(baseName, ROOT_ICU);
531: } catch (MissingResourceException ex) {
532: b = ResourceBundleWrapper.getBundleInstance(baseName,
533: localeName, root, disableFallback);
534: setRootType(baseName, ROOT_JAVA);
535: }
536: return b;
537: }
538: }
539:
540: /**
541: * @internal
542: * @deprecated This API is ICU internal only.
543: */
544: protected abstract void setLoadingStatus(int newStatus);
545: }
|