View Javadoc

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