View Javadoc

1   /*
2    * $Id: MessageResources.java 421119 2006-07-12 04:49:11Z wsmoak $
3    *
4    * Copyright 1999-2005 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  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.Serializable;
24  
25  import java.text.MessageFormat;
26  
27  import java.util.HashMap;
28  import java.util.Locale;
29  
30  /***
31   * General purpose abstract class that describes an API for retrieving
32   * Locale-sensitive messages from underlying resource locations of an
33   * unspecified design, and optionally utilizing the <code>MessageFormat</code>
34   * class to produce internationalized messages with parametric replacement.
35   * <p> Calls to <code>getMessage()</code> variants without a
36   * <code>Locale</code> argument are presumed to be requesting a message string
37   * in the default <code>Locale</code> for this JVM. <p> Calls to
38   * <code>getMessage()</code> with an unknown key, or an unknown
39   * <code>Locale</code> will return <code>null</code> if the
40   * <code>returnNull</code> property is set to <code>true</code>.  Otherwise, a
41   * suitable error message will be returned instead. <p> <strong>IMPLEMENTATION
42   * NOTE</strong> - Classes that extend this class must be Serializable so that
43   * instances may be used in distributable application server environments.
44   *
45   * @version $Rev: 421119 $ $Date: 2005-08-29 23:57:50 -0400 (Mon, 29 Aug 2005)
46   *          $
47   */
48  public abstract class MessageResources implements Serializable {
49      // ------------------------------------------------------------- Properties
50  
51      /***
52       * Commons Logging instance.
53       */
54      protected static Log log = LogFactory.getLog(MessageResources.class);
55  
56      // --------------------------------------------------------- Static Methods
57  
58      /***
59       * The default MessageResourcesFactory used to create MessageResources
60       * instances.
61       */
62      protected static MessageResourcesFactory defaultFactory = null;
63  
64      /***
65       * The configuration parameter used to initialize this MessageResources.
66       */
67      protected String config = null;
68  
69      /***
70       * The default Locale for our environment.
71       */
72      protected Locale defaultLocale = Locale.getDefault();
73  
74      /***
75       * The <code>MessageResourcesFactory</code> that created this instance.
76       */
77      protected MessageResourcesFactory factory = null;
78  
79      /***
80       * The set of previously created MessageFormat objects, keyed by the key
81       * computed in <code>messageKey()</code>.
82       */
83      protected HashMap formats = new HashMap();
84  
85      /***
86       * Indicate is a <code>null</code> is returned instead of an error message
87       * string when an unknown Locale or key is requested.
88       */
89      protected boolean returnNull = false;
90  
91      /***
92       * Indicates whether 'escape processing' should be performed on the error
93       * message string.
94       */
95      private boolean escape = true;
96  
97      // ----------------------------------------------------------- Constructors
98  
99      /***
100      * Construct a new MessageResources according to the specified
101      * parameters.
102      *
103      * @param factory The MessageResourcesFactory that created us
104      * @param config  The configuration parameter for this MessageResources
105      */
106     public MessageResources(MessageResourcesFactory factory, String config) {
107         this(factory, config, false);
108     }
109 
110     /***
111      * Construct a new MessageResources according to the specified
112      * parameters.
113      *
114      * @param factory    The MessageResourcesFactory that created us
115      * @param config     The configuration parameter for this
116      *                   MessageResources
117      * @param returnNull The returnNull property we should initialize with
118      */
119     public MessageResources(MessageResourcesFactory factory, String config,
120         boolean returnNull) {
121         super();
122         this.factory = factory;
123         this.config = config;
124         this.returnNull = returnNull;
125     }
126 
127     /***
128      * The configuration parameter used to initialize this MessageResources.
129      *
130      * @return parameter used to initialize this MessageResources
131      */
132     public String getConfig() {
133         return (this.config);
134     }
135 
136     /***
137      * The <code>MessageResourcesFactory</code> that created this instance.
138      *
139      * @return <code>MessageResourcesFactory</code> that created instance
140      */
141     public MessageResourcesFactory getFactory() {
142         return (this.factory);
143     }
144 
145     /***
146      * Indicates that a <code>null</code> is returned instead of an error
147      * message string if an unknown Locale or key is requested.
148      *
149      * @return true if null is returned if unknown key or locale is requested
150      */
151     public boolean getReturnNull() {
152         return (this.returnNull);
153     }
154 
155     /***
156      * Indicates that a <code>null</code> is returned instead of an error
157      * message string if an unknown Locale or key is requested.
158      *
159      * @param returnNull true Indicates that a <code>null</code> is returned
160      *                   if an unknown Locale or key is requested.
161      */
162     public void setReturnNull(boolean returnNull) {
163         this.returnNull = returnNull;
164     }
165 
166     /***
167      * Indicates whether 'escape processing' should be performed on the error
168      * message string.
169      *
170      * @since Struts 1.2.8
171      */
172     public boolean isEscape() {
173         return escape;
174     }
175 
176     /***
177      * Set whether 'escape processing' should be performed on the error
178      * message string.
179      *
180      * @since Struts 1.2.8
181      */
182     public void setEscape(boolean escape) {
183         this.escape = escape;
184     }
185 
186     // --------------------------------------------------------- Public Methods
187 
188     /***
189      * Returns a text message for the specified key, for the default Locale.
190      *
191      * @param key The message key to look up
192      */
193     public String getMessage(String key) {
194         return this.getMessage((Locale) null, key, null);
195     }
196 
197     /***
198      * Returns a text message after parametric replacement of the specified
199      * parameter placeholders.
200      *
201      * @param key  The message key to look up
202      * @param args An array of replacement parameters for placeholders
203      */
204     public String getMessage(String key, Object[] args) {
205         return this.getMessage((Locale) null, key, args);
206     }
207 
208     /***
209      * Returns a text message after parametric replacement of the specified
210      * parameter placeholders.
211      *
212      * @param key  The message key to look up
213      * @param arg0 The replacement for placeholder {0} in the message
214      */
215     public String getMessage(String key, Object arg0) {
216         return this.getMessage((Locale) null, key, arg0);
217     }
218 
219     /***
220      * Returns a text message after parametric replacement of the specified
221      * parameter placeholders.
222      *
223      * @param key  The message key to look up
224      * @param arg0 The replacement for placeholder {0} in the message
225      * @param arg1 The replacement for placeholder {1} in the message
226      */
227     public String getMessage(String key, Object arg0, Object arg1) {
228         return this.getMessage((Locale) null, key, arg0, arg1);
229     }
230 
231     /***
232      * Returns a text message after parametric replacement of the specified
233      * parameter placeholders.
234      *
235      * @param key  The message key to look up
236      * @param arg0 The replacement for placeholder {0} in the message
237      * @param arg1 The replacement for placeholder {1} in the message
238      * @param arg2 The replacement for placeholder {2} in the message
239      */
240     public String getMessage(String key, Object arg0, Object arg1, Object arg2) {
241         return this.getMessage((Locale) null, key, arg0, arg1, arg2);
242     }
243 
244     /***
245      * Returns a text message after parametric replacement of the specified
246      * parameter placeholders.
247      *
248      * @param key  The message key to look up
249      * @param arg0 The replacement for placeholder {0} in the message
250      * @param arg1 The replacement for placeholder {1} in the message
251      * @param arg2 The replacement for placeholder {2} in the message
252      * @param arg3 The replacement for placeholder {3} in the message
253      */
254     public String getMessage(String key, Object arg0, Object arg1, Object arg2,
255         Object arg3) {
256         return this.getMessage((Locale) null, key, arg0, arg1, arg2, arg3);
257     }
258 
259     /***
260      * Returns a text message for the specified key, for the default Locale. A
261      * null string result will be returned by this method if no relevant
262      * message resource is found for this key or Locale, if the
263      * <code>returnNull</code> property is set.  Otherwise, an appropriate
264      * error message will be returned. <p> This method must be implemented by
265      * a concrete subclass.
266      *
267      * @param locale The requested message Locale, or <code>null</code> for
268      *               the system default Locale
269      * @param key    The message key to look up
270      */
271     public abstract String getMessage(Locale locale, String key);
272 
273     /***
274      * Returns a text message after parametric replacement of the specified
275      * parameter placeholders.  A null string result will be returned by this
276      * method if no resource bundle has been configured.
277      *
278      * @param locale The requested message Locale, or <code>null</code> for
279      *               the system default Locale
280      * @param key    The message key to look up
281      * @param args   An array of replacement parameters for placeholders
282      */
283     public String getMessage(Locale locale, String key, Object[] args) {
284         // Cache MessageFormat instances as they are accessed
285         if (locale == null) {
286             locale = defaultLocale;
287         }
288 
289         MessageFormat format = null;
290         String formatKey = messageKey(locale, key);
291 
292         synchronized (formats) {
293             format = (MessageFormat) formats.get(formatKey);
294 
295             if (format == null) {
296                 String formatString = getMessage(locale, key);
297 
298                 if (formatString == null) {
299                     return returnNull ? null : ("???" + formatKey + "???");
300                 }
301 
302                 format = new MessageFormat(escape(formatString));
303                 format.setLocale(locale);
304                 formats.put(formatKey, format);
305             }
306         }
307 
308         return format.format(args);
309     }
310 
311     /***
312      * Returns a text message after parametric replacement of the specified
313      * parameter placeholders.  A null string result will never be returned by
314      * this method.
315      *
316      * @param locale The requested message Locale, or <code>null</code> for
317      *               the system default Locale
318      * @param key    The message key to look up
319      * @param arg0   The replacement for placeholder {0} in the message
320      */
321     public String getMessage(Locale locale, String key, Object arg0) {
322         return this.getMessage(locale, key, new Object[] { arg0 });
323     }
324 
325     /***
326      * Returns a text message after parametric replacement of the specified
327      * parameter placeholders.  A null string result will never be returned by
328      * this method.
329      *
330      * @param locale The requested message Locale, or <code>null</code> for
331      *               the system default Locale
332      * @param key    The message key to look up
333      * @param arg0   The replacement for placeholder {0} in the message
334      * @param arg1   The replacement for placeholder {1} in the message
335      */
336     public String getMessage(Locale locale, String key, Object arg0, Object arg1) {
337         return this.getMessage(locale, key, new Object[] { arg0, arg1 });
338     }
339 
340     /***
341      * Returns a text message after parametric replacement of the specified
342      * parameter placeholders.  A null string result will never be returned by
343      * this method.
344      *
345      * @param locale The requested message Locale, or <code>null</code> for
346      *               the system default Locale
347      * @param key    The message key to look up
348      * @param arg0   The replacement for placeholder {0} in the message
349      * @param arg1   The replacement for placeholder {1} in the message
350      * @param arg2   The replacement for placeholder {2} in the message
351      */
352     public String getMessage(Locale locale, String key, Object arg0,
353         Object arg1, Object arg2) {
354         return this.getMessage(locale, key, new Object[] { arg0, arg1, arg2 });
355     }
356 
357     /***
358      * Returns a text message after parametric replacement of the specified
359      * parameter placeholders.  A null string result will never be returned by
360      * this method.
361      *
362      * @param locale The requested message Locale, or <code>null</code> for
363      *               the system default Locale
364      * @param key    The message key to look up
365      * @param arg0   The replacement for placeholder {0} in the message
366      * @param arg1   The replacement for placeholder {1} in the message
367      * @param arg2   The replacement for placeholder {2} in the message
368      * @param arg3   The replacement for placeholder {3} in the message
369      */
370     public String getMessage(Locale locale, String key, Object arg0,
371         Object arg1, Object arg2, Object arg3) {
372         return this.getMessage(locale, key,
373             new Object[] { arg0, arg1, arg2, arg3 });
374     }
375 
376     /***
377      * Return <code>true</code> if there is a defined message for the
378      * specified key in the system default locale.
379      *
380      * @param key The message key to look up
381      */
382     public boolean isPresent(String key) {
383         return this.isPresent(null, key);
384     }
385 
386     /***
387      * Return <code>true</code> if there is a defined message for the
388      * specified key in the specified Locale.
389      *
390      * @param locale The requested message Locale, or <code>null</code> for
391      *               the system default Locale
392      * @param key    The message key to look up
393      */
394     public boolean isPresent(Locale locale, String key) {
395         String message = getMessage(locale, key);
396 
397         if (message == null) {
398             return false;
399         } else if (message.startsWith("???") && message.endsWith("???")) {
400             return false; // FIXME - Only valid for default implementation
401         } else {
402             return true;
403         }
404     }
405 
406     // ------------------------------------------------------ Protected Methods
407 
408     /***
409      * Escape any single quote characters that are included in the specified
410      * message string.
411      *
412      * @param string The string to be escaped
413      */
414     protected String escape(String string) {
415         if (!isEscape()) {
416             return string;
417         }
418 
419         if ((string == null) || (string.indexOf('\'') < 0)) {
420             return string;
421         }
422 
423         int n = string.length();
424         StringBuffer sb = new StringBuffer(n);
425 
426         for (int i = 0; i < n; i++) {
427             char ch = string.charAt(i);
428 
429             if (ch == '\'') {
430                 sb.append('\'');
431             }
432 
433             sb.append(ch);
434         }
435 
436         return sb.toString();
437     }
438 
439     /***
440      * Compute and return a key to be used in caching information by a Locale.
441      * <strong>NOTE</strong> - The locale key for the default Locale in our
442      * environment is a zero length String.
443      *
444      * @param locale The locale for which a key is desired
445      */
446     protected String localeKey(Locale locale) {
447         return (locale == null) ? "" : locale.toString();
448     }
449 
450     /***
451      * Compute and return a key to be used in caching information by Locale
452      * and message key.
453      *
454      * @param locale The Locale for which this format key is calculated
455      * @param key    The message key for which this format key is calculated
456      */
457     protected String messageKey(Locale locale, String key) {
458         return (localeKey(locale) + "." + key);
459     }
460 
461     /***
462      * Compute and return a key to be used in caching information by locale
463      * key and message key.
464      *
465      * @param localeKey The locale key for which this cache key is calculated
466      * @param key       The message key for which this cache key is
467      *                  calculated
468      */
469     protected String messageKey(String localeKey, String key) {
470         return (localeKey + "." + key);
471     }
472 
473     /***
474      * Create and return an instance of <code>MessageResources</code> for the
475      * created by the default <code>MessageResourcesFactory</code>.
476      *
477      * @param config Configuration parameter for this message bundle.
478      */
479     public synchronized static MessageResources getMessageResources(
480         String config) {
481         if (defaultFactory == null) {
482             defaultFactory = MessageResourcesFactory.createFactory();
483         }
484 
485         return defaultFactory.createResources(config);
486     }
487 
488     /***
489      * Log a message to the Writer that has been configured for our use.
490      *
491      * @param message The message to be logged
492      */
493     public void log(String message) {
494         log.debug(message);
495     }
496 
497     /***
498      * Log a message and exception to the Writer that has been configured for
499      * our use.
500      *
501      * @param message   The message to be logged
502      * @param throwable The exception to be logged
503      */
504     public void log(String message, Throwable throwable) {
505         log.debug(message, throwable);
506     }
507 }