View Javadoc

1   /*
2    * $Header: /cvsroot/struts-menu/navigator/src/java/net/sf/navigator/util/PropertyMessageResources.java,v 1.2 2006/07/09 08:08:10 mraible Exp $
3    * $Revision: 1.2 $
4    * $Date: 2006/07/09 08:08:10 $
5    *
6    * Copyright 1999-2004 The Apache Software Foundation.
7    * 
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
11   * 
12   *      http://www.apache.org/licenses/LICENSE-2.0
13   * 
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.
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      // ----------------------------------------------------------- Constructors
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      // ------------------------------------------------------------- Properties
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     // --------------------------------------------------------- Public Methods
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         // Initialize variables we will require
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;  // Add if not found under the original key
143 
144         // Loop from specific to general Locales looking for this message
145         while (true) {
146 
147             // Load this Locale's messages if we have not done so yet
148             loadLocale(localeKey);
149 
150             // Check if we have this key for the current locale key
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             // Strip trailing modifiers to try a more general locale key
163             addIt = true;
164             underscore = localeKey.lastIndexOf("_");
165             if (underscore < 0) {
166                 break;
167             }
168             localeKey = localeKey.substring(0, underscore);
169 
170         }
171 
172         // Try the default locale if the current locale is different
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         // As a last resort, try the default Locale
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         // Return an appropriate error indication
199         if (returnNull) {
200             return (null);
201         } else {
202             return ("???" + messageKey(locale, key) + "???");
203         }
204 
205     }
206 
207 
208     // ------------------------------------------------------ Protected Methods
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         // Have we already attempted to load messages for this locale?
229         if (locales.get(localeKey) != null) {
230             return;
231         }
232         
233         locales.put(localeKey, localeKey);
234 
235         // Set up to load the property resource for this locale key, if we can
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         // Load the specified property resource
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         // Copy the corresponding values into our cache
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 }