001: /* PropertyBundle.java
002:
003: {{IS_NOTE
004:
005: Purpose:
006: Description:
007: History:
008: 90/12/06 20:09:36, Create, Tom M. Yeh.
009: }}IS_NOTE
010:
011: Copyright (C) 2001 Potix Corporation. All Rights Reserved.
012:
013: {{IS_RIGHT
014: This program is distributed under GPL Version 2.0 in the hope that
015: it will be useful, but WITHOUT ANY WARRANTY.
016: }}IS_RIGHT
017: */
018: package org.zkoss.util.resource;
019:
020: import java.util.Locale;
021: import java.util.Map;
022: import java.util.HashMap;
023: import java.io.InputStream;
024: import java.lang.reflect.Method;
025:
026: import org.zkoss.lang.D;
027: import org.zkoss.lang.Classes;
028: import org.zkoss.util.CacheMap;
029: import org.zkoss.util.Cache;
030: import org.zkoss.util.Maps;
031: import org.zkoss.util.logging.Log;
032:
033: /**
034: * The property bundle.
035: *
036: * <p>It is similar to java.util.ResourceBundle, but they differ as follows.
037: *
038: * <ul>
039: * <li>It uses {@link Maps#load(Map, InputStream)} to load the properties.
040: * Thus, It is capable to handle UTF-16 and UTF-8 (but not ISO-8859-1).</li>
041: * <li>The locator could be any object as long as it implements
042: * <code>InputStream getResourceAsStream(String)</code>.</li>
043: * <li>It supports only property files.</li>
044: * <li>The getBundle method returns null if the resource not found,
045: * while ResourceBundle throws MissingResourceException.</li>
046: * </ul>
047: *
048: * <p>Instances of PropertyBundle are cached, so the performance is good.
049: * However, it implies the property file mapped by the giving
050: * class loader, name and locale is immutable. In other words, if you
051: * update the content of a property file, it might not be reflected to
052: * getString unless it is cleared out of the cache.
053: *
054: * <p>Thread safe.
055: *
056: * @author tomyeh
057: */
058: public class PropertyBundle {
059: private static final Log log = Log.lookup(PropertyBundle.class);
060:
061: /** The cache to hold bundles (Key, PropertyBundle). */
062: private static final Cache _cache;
063: static {
064: _cache = new CacheMap();
065: _cache.setMaxSize(100);
066: }
067:
068: /** The map of properties. */
069: private final Map _map;
070: /** The locale of the boundle. */
071: private final Locale _locale;
072:
073: /** The key used to look up the cache. */
074: private static class Key {
075: private String baseName;
076: private Locale locale;
077: private Locator locator;
078: private boolean caseInsensitive;
079:
080: private Key(String baseName, Locale locale, Locator locator,
081: boolean caseInsensitive) {
082: this .baseName = baseName;
083: this .locale = locale;
084: this .locator = locator;
085: this .caseInsensitive = caseInsensitive;
086: }
087:
088: //-- Object --//
089: public int hashCode() {
090: return baseName.hashCode() ^ locale.hashCode()
091: ^ locator.hashCode();
092: }
093:
094: public boolean equals(Object o) {
095: if (!(o instanceof Key))
096: return false;
097: Key k = (Key) o;
098: return k.baseName.equals(baseName)
099: && k.locale.equals(locale)
100: && k.locator.equals(locator)
101: && k.caseInsensitive == caseInsensitive;
102: }
103: }
104:
105: /**
106: * Gets a resource bundle using the specified
107: * base name, locale, and locator.
108: *
109: * @param locator the locator. {@link Locators#getDefault}.
110: * @param caseInsensitive whether the key used to access the map
111: * is case-insensitive. If true, all keys are converted to lower cases.
112: * @return the bundle; null if not found
113: */
114: public static final PropertyBundle getBundle(String baseName,
115: Locale locale, Locator locator, boolean caseInsensitive) {
116: //We don't lock the whole method, so it is possible that
117: //more than one thread are loading the same property file.
118: //However, it is OK since any result is correct.
119: //On the other hand, it is more likely that two or more
120: //threads are asking different property files at the same
121: //time, so we avoid the big lock.
122:
123: final Key key = new Key(baseName, locale, locator,
124: caseInsensitive);
125: synchronized (_cache) {
126: PropertyBundle bundle = (PropertyBundle) _cache.get(key);
127: if (bundle != null)
128: return bundle;
129: }
130:
131: final PropertyBundle bundle = new PropertyBundle(baseName,
132: locale, locator, caseInsensitive);
133: if (bundle._map == null) //failed
134: return null;
135:
136: synchronized (_cache) {
137: _cache.put(key, bundle);
138: }
139: return bundle;
140: }
141:
142: /**
143: * Gets a resource bundle using the specified
144: * base name, locale, and locator.
145: */
146: public static final PropertyBundle getBundle(String baseName,
147: Locale locale, Locator locator) {
148: return getBundle(baseName, locale, locator, false);
149: }
150:
151: /**
152: * Gets a resource bundle using the specified
153: * base name, locale, and the default locator, {@link Locators#getDefault}.
154: *
155: * @param caseInsensitive whether the key used to access the map
156: * is case-insensitive. If true, all keys are converted to lower cases.
157: * @return the bundle; null if not found
158: */
159: public static final PropertyBundle getBundle(String baseName,
160: Locale locale, boolean caseInsensitive) {
161: return getBundle(baseName, locale, Locators.getDefault(),
162: caseInsensitive);
163: }
164:
165: /**
166: * Gets a resource bundle using the specified
167: * base name, locale, and the default locator, {@link Locators#getDefault}.
168: */
169: public static final PropertyBundle getBundle(String baseName,
170: Locale locale) {
171: return getBundle(baseName, locale, false);
172: }
173:
174: /**
175: * Constructor.
176: *
177: * @param caseInsensitive whether the key used to access the map
178: * is case-insensitive. If true, all keys are converted to lower cases.
179: */
180: protected PropertyBundle(String baseName, Locale locale,
181: Locator locator, boolean caseInsensitive) {
182: try {
183: final Locators.StreamLocation loc = Locators
184: .locateAsStream(baseName + ".properties", locale,
185: locator);
186: if (loc != null) {
187: _map = new HashMap(37);
188: Maps.load(_map, loc.stream, caseInsensitive);
189: _locale = loc.locale;
190: } else {
191: _map = null; //we use _map to denote failure
192: _locale = null;
193: }
194: } catch (RuntimeException ex) {
195: throw ex;
196: } catch (Exception ex) {
197: throw (IllegalArgumentException) (new IllegalArgumentException()
198: .initCause(ex));
199: }
200: }
201:
202: /** Returns the property for the given key from this resource bundle
203: * or one of its parents.
204: */
205: public final String getProperty(String key) {
206: //It is OK not to sync because _map is immutable
207: return (String) _map.get(key);
208: }
209:
210: /** Returns a map of all properties, (String key , String value).
211: */
212: public final Map getProperties() {
213: return _map;
214: }
215:
216: /** Returns the locale of the bundle.
217: * Note: it is value might not be the same as the locale being passed
218: * to the constructor, because the contructor will do some fallback.
219: */
220: public final Locale getLocale() {
221: return _locale;
222: }
223: }
|