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