1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package net.sf.navigator.util;
22
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Locale;
28 import java.util.Properties;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32
33 /**
34 * Concrete subclass of <code>MessageResources</code> that reads message keys
35 * and corresponding strings from named property resources in the same manner
36 * that <code>java.util.PropertyResourceBundle</code> does. The
37 * <code>base</code> property defines the base property resource name, and
38 * must be specified.
39 * <p>
40 * <strong>IMPLEMENTATION NOTE</strong> - This class trades memory for
41 * speed by caching all messages located via generalizing the Locale under
42 * the original locale as well.
43 * This results in specific messages being stored in the message cache
44 * more than once, but improves response time on subsequent requests for
45 * the same locale + key combination.
46 *
47 * @version $Revision: 1.2 $ $Date: 2006/07/09 08:08:10 $
48 */
49 public class PropertyMessageResources extends MessageResources {
50
51
52
53
54
55 /**
56 * Construct a new PropertyMessageResources according to the
57 * specified parameters.
58 *
59 * @param factory The MessageResourcesFactory that created us
60 * @param config The configuration parameter for this MessageResources
61 */
62 public PropertyMessageResources(MessageResourcesFactory factory,
63 String config) {
64
65 super(factory, config);
66 log.trace("Initializing, config='" + config + "'");
67
68 }
69
70
71 /**
72 * Construct a new PropertyMessageResources according to the
73 * specified parameters.
74 *
75 * @param factory The MessageResourcesFactory that created us
76 * @param config The configuration parameter for this MessageResources
77 * @param returnNull The returnNull property we should initialize with
78 */
79 public PropertyMessageResources(MessageResourcesFactory factory,
80 String config, boolean returnNull) {
81
82 super(factory, config, returnNull);
83 log.trace("Initializing, config='" + config +
84 "', returnNull=" + returnNull);
85
86 }
87
88
89
90
91
92 /**
93 * The set of locale keys for which we have already loaded messages, keyed
94 * by the value calculated in <code>localeKey()</code>.
95 */
96 protected HashMap locales = new HashMap();
97
98
99 /**
100 * The <code>Log</code> instance for this class.
101 */
102 protected static final Log log =
103 LogFactory.getLog(PropertyMessageResources.class);
104
105
106 /**
107 * The cache of messages we have accumulated over time, keyed by the
108 * value calculated in <code>messageKey()</code>.
109 */
110 protected HashMap messages = new HashMap();
111
112
113
114
115
116 /**
117 * Returns a text message for the specified key, for the default Locale.
118 * A null string result will be returned by this method if no relevant
119 * message resource is found for this key or Locale, if the
120 * <code>returnNull</code> property is set. Otherwise, an appropriate
121 * error message will be returned.
122 * <p>
123 * This method must be implemented by a concrete subclass.
124 *
125 * @param locale The requested message Locale, or <code>null</code>
126 * for the system default Locale
127 * @param key The message key to look up
128 * @return text message for the specified key and locale
129 */
130 public String getMessage(Locale locale, String key) {
131
132 if (log.isDebugEnabled()) {
133 log.debug("getMessage(" + locale + "," + key + ")");
134 }
135
136
137 String localeKey = localeKey(locale);
138 String originalKey = messageKey(localeKey, key);
139 String messageKey = null;
140 String message = null;
141 int underscore = 0;
142 boolean addIt = false;
143
144
145 while (true) {
146
147
148 loadLocale(localeKey);
149
150
151 messageKey = messageKey(localeKey, key);
152 synchronized (messages) {
153 message = (String) messages.get(messageKey);
154 if (message != null) {
155 if (addIt) {
156 messages.put(originalKey, message);
157 }
158 return (message);
159 }
160 }
161
162
163 addIt = true;
164 underscore = localeKey.lastIndexOf("_");
165 if (underscore < 0) {
166 break;
167 }
168 localeKey = localeKey.substring(0, underscore);
169
170 }
171
172
173 if (!defaultLocale.equals(locale)) {
174 localeKey = localeKey(defaultLocale);
175 messageKey = messageKey(localeKey, key);
176 loadLocale(localeKey);
177 synchronized (messages) {
178 message = (String) messages.get(messageKey);
179 if (message != null) {
180 messages.put(originalKey, message);
181 return (message);
182 }
183 }
184 }
185
186
187 localeKey = "";
188 messageKey = messageKey(localeKey, key);
189 loadLocale(localeKey);
190 synchronized (messages) {
191 message = (String) messages.get(messageKey);
192 if (message != null) {
193 messages.put(originalKey, message);
194 return (message);
195 }
196 }
197
198
199 if (returnNull) {
200 return (null);
201 } else {
202 return ("???" + messageKey(locale, key) + "???");
203 }
204
205 }
206
207
208
209
210
211 /**
212 * Load the messages associated with the specified Locale key. For this
213 * implementation, the <code>config</code> property should contain a fully
214 * qualified package and resource name, separated by periods, of a series
215 * of property resources to be loaded from the class loader that created
216 * this PropertyMessageResources instance. This is exactly the same name
217 * format you would use when utilizing the
218 * <code>java.util.PropertyResourceBundle</code> class.
219 *
220 * @param localeKey Locale key for the messages to be retrieved
221 */
222 protected synchronized void loadLocale(String localeKey) {
223
224 if (log.isTraceEnabled()) {
225 log.trace("loadLocale(" + localeKey + ")");
226 }
227
228
229 if (locales.get(localeKey) != null) {
230 return;
231 }
232
233 locales.put(localeKey, localeKey);
234
235
236 String name = config.replace('.', '/');
237 if (localeKey.length() > 0) {
238 name += "_" + localeKey;
239 }
240
241 name += ".properties";
242 InputStream is = null;
243 Properties props = new Properties();
244
245
246 if (log.isTraceEnabled()) {
247 log.trace(" Loading resource '" + name + "'");
248 }
249
250 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
251 if (classLoader == null) {
252 classLoader = this.getClass().getClassLoader();
253 }
254
255 is = classLoader.getResourceAsStream(name);
256 if (is != null) {
257 try {
258 props.load(is);
259
260 } catch (IOException e) {
261 log.error("loadLocale()", e);
262 } finally {
263 try {
264 is.close();
265 } catch (IOException e) {
266 log.error("loadLocale()", e);
267 }
268 }
269 }
270
271 if (log.isTraceEnabled()) {
272 log.trace(" Loading resource completed");
273 }
274
275
276 if (props.size() < 1) {
277 return;
278 }
279
280 synchronized (messages) {
281 Iterator names = props.keySet().iterator();
282 while (names.hasNext()) {
283 String key = (String) names.next();
284 if (log.isTraceEnabled()) {
285 log.trace(" Saving message key '" + messageKey(localeKey, key));
286 }
287 messages.put(messageKey(localeKey, key), props.getProperty(key));
288 }
289 }
290
291 }
292
293
294 }