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