2
* $Header: /home/cvs/jakarta-struts/src/share/org/apache/struts/util/PropertyMessageResources.java,v 1.12 2004/03/14 06:23:51 sraeburn Exp $
4
* $Date: 2004/03/14 06:23:51 $
6
* Copyright 1999-2004 The Apache Software Foundation.
8
* Licensed under the Apache License, Version 2.0 (the "License");
9
* you may not use this file except in compliance with the License.
10
* You may obtain a copy of the License at
12
* http://www.apache.org/licenses/LICENSE-2.0
14
* Unless required by applicable law or agreed to in writing, software
15
* distributed under the License is distributed on an "AS IS" BASIS,
16
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
* See the License for the specific language governing permissions and
18
* limitations under the License.
22
package org.apache.struts.util;
24
import java.io.IOException;
25
import java.io.InputStream;
26
import java.util.HashMap;
27
import java.util.Iterator;
28
import java.util.Locale;
29
import java.util.Properties;
31
import org.apache.commons.logging.Log;
32
import org.apache.commons.logging.LogFactory;
35
* Concrete subclass of <code>MessageResources</code> that reads message keys
36
* and corresponding strings from named property resources in the same manner
37
* that <code>java.util.PropertyResourceBundle</code> does. The
38
* <code>base</code> property defines the base property resource name, and
41
* <strong>IMPLEMENTATION NOTE</strong> - This class trades memory for
42
* speed by caching all messages located via generalizing the Locale under
43
* the original locale as well.
44
* This results in specific messages being stored in the message cache
45
* more than once, but improves response time on subsequent requests for
46
* the same locale + key combination.
48
* @version $Revision: 1.12 $ $Date: 2004/03/14 06:23:51 $
50
public class PropertyMessageResources extends MessageResources {
53
// ----------------------------------------------------------- Constructors
57
* Construct a new PropertyMessageResources according to the
58
* specified parameters.
60
* @param factory The MessageResourcesFactory that created us
61
* @param config The configuration parameter for this MessageResources
63
public PropertyMessageResources(MessageResourcesFactory factory,
66
super(factory, config);
67
log.trace("Initializing, config='" + config + "'");
73
* Construct a new PropertyMessageResources according to the
74
* specified parameters.
76
* @param factory The MessageResourcesFactory that created us
77
* @param config The configuration parameter for this MessageResources
78
* @param returnNull The returnNull property we should initialize with
80
public PropertyMessageResources(MessageResourcesFactory factory,
81
String config, boolean returnNull) {
83
super(factory, config, returnNull);
84
log.trace("Initializing, config='" + config +
85
"', returnNull=" + returnNull);
90
// ------------------------------------------------------------- Properties
94
* The set of locale keys for which we have already loaded messages, keyed
95
* by the value calculated in <code>localeKey()</code>.
97
protected HashMap locales = new HashMap();
101
* The <code>Log</code> instance for this class.
103
protected static final Log log =
104
LogFactory.getLog(PropertyMessageResources.class);
108
* The cache of messages we have accumulated over time, keyed by the
109
* value calculated in <code>messageKey()</code>.
111
protected HashMap messages = new HashMap();
114
// --------------------------------------------------------- Public Methods
118
* Returns a text message for the specified key, for the default Locale.
119
* A null string result will be returned by this method if no relevant
120
* message resource is found for this key or Locale, if the
121
* <code>returnNull</code> property is set. Otherwise, an appropriate
122
* error message will be returned.
124
* This method must be implemented by a concrete subclass.
126
* @param locale The requested message Locale, or <code>null</code>
127
* for the system default Locale
128
* @param key The message key to look up
129
* @return text message for the specified key and locale
131
public String getMessage(Locale locale, String key) {
133
if (log.isDebugEnabled()) {
134
log.debug("getMessage(" + locale + "," + key + ")");
137
// Initialize variables we will require
138
String localeKey = localeKey(locale);
139
String originalKey = messageKey(localeKey, key);
140
String messageKey = null;
141
String message = null;
143
boolean addIt = false; // Add if not found under the original key
145
// Loop from specific to general Locales looking for this message
148
// Load this Locale's messages if we have not done so yet
149
loadLocale(localeKey);
151
// Check if we have this key for the current locale key
152
messageKey = messageKey(localeKey, key);
153
synchronized (messages) {
154
message = (String) messages.get(messageKey);
155
if (message != null) {
157
messages.put(originalKey, message);
163
// Strip trailing modifiers to try a more general locale key
165
underscore = localeKey.lastIndexOf("_");
166
if (underscore < 0) {
169
localeKey = localeKey.substring(0, underscore);
173
// Try the default locale if the current locale is different
174
if (!defaultLocale.equals(locale)) {
175
localeKey = localeKey(defaultLocale);
176
messageKey = messageKey(localeKey, key);
177
loadLocale(localeKey);
178
synchronized (messages) {
179
message = (String) messages.get(messageKey);
180
if (message != null) {
181
messages.put(originalKey, message);
187
// As a last resort, try the default Locale
189
messageKey = messageKey(localeKey, key);
190
loadLocale(localeKey);
191
synchronized (messages) {
192
message = (String) messages.get(messageKey);
193
if (message != null) {
194
messages.put(originalKey, message);
199
// Return an appropriate error indication
203
return ("???" + messageKey(locale, key) + "???");
209
// ------------------------------------------------------ Protected Methods
213
* Load the messages associated with the specified Locale key. For this
214
* implementation, the <code>config</code> property should contain a fully
215
* qualified package and resource name, separated by periods, of a series
216
* of property resources to be loaded from the class loader that created
217
* this PropertyMessageResources instance. This is exactly the same name
218
* format you would use when utilizing the
219
* <code>java.util.PropertyResourceBundle</code> class.
221
* @param localeKey Locale key for the messages to be retrieved
223
protected synchronized void loadLocale(String localeKey) {
225
if (log.isTraceEnabled()) {
226
log.trace("loadLocale(" + localeKey + ")");
229
// Have we already attempted to load messages for this locale?
230
if (locales.get(localeKey) != null) {
234
locales.put(localeKey, localeKey);
236
// Set up to load the property resource for this locale key, if we can
237
String name = config.replace('.', '/');
238
if (localeKey.length() > 0) {
239
name += "_" + localeKey;
242
name += ".properties";
243
InputStream is = null;
244
Properties props = new Properties();
246
// Load the specified property resource
247
if (log.isTraceEnabled()) {
248
log.trace(" Loading resource '" + name + "'");
251
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
252
if (classLoader == null) {
253
classLoader = this.getClass().getClassLoader();
256
is = classLoader.getResourceAsStream(name);
261
} catch (IOException e) {
262
log.error("loadLocale()", e);
266
} catch (IOException e) {
267
log.error("loadLocale()", e);
272
if (log.isTraceEnabled()) {
273
log.trace(" Loading resource completed");
276
// Copy the corresponding values into our cache
277
if (props.size() < 1) {
281
synchronized (messages) {
282
Iterator names = props.keySet().iterator();
283
while (names.hasNext()) {
284
String key = (String) names.next();
285
if (log.isTraceEnabled()) {
286
log.trace(" Saving message key '" + messageKey(localeKey, key));
288
messages.put(messageKey(localeKey, key), props.getProperty(key));