001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: Messages.java 468654 2006-10-28 07:09:23Z minchau $
020     */
021    package org.apache.xml.serializer.utils;
022    
023    import java.util.ListResourceBundle;
024    import java.util.Locale;
025    import java.util.MissingResourceException;
026    import java.util.ResourceBundle;
027    
028    /**
029     * A utility class for issuing error messages.
030     * 
031     * A user of this class normally would create a singleton 
032     * instance of this class, passing the name
033     * of the message class on the constructor. For example:
034     * <CODE>
035     * static Messages x = new Messages("org.package.MyMessages");
036     * </CODE>
037     * Later the message is typically generated this way if there are no 
038     * substitution arguments:
039     * <CODE>
040     * String msg = x.createMessage(org.package.MyMessages.KEY_ONE, null); 
041     * </CODE>
042     * If there are arguments substitutions then something like this:
043     * <CODE>
044     * String filename = ...;
045     * String directory = ...;
046     * String msg = x.createMessage(org.package.MyMessages.KEY_TWO, 
047     *   new Object[] {filename, directory) ); 
048     * </CODE>
049     *  
050     * The constructor of an instance of this class must be given
051     * the class name of a class that extends java.util.ListResourceBundle 
052     * ("org.package.MyMessages" in the example above).  
053     * The name should not have any language suffix 
054     * which will be added automatically by this utility class.
055     * 
056     * The message class ("org.package.MyMessages")
057     * must define the abstract method getContents() that is
058     * declared in its base class, for example:
059     * <CODE>
060     * public Object[][] getContents() {return contents;}
061     * </CODE>
062     * 
063     * It is suggested that the message class expose its
064     * message keys like this:
065     * <CODE>
066     *   public static final String KEY_ONE = "KEY1";
067     *   public static final String KEY_TWO = "KEY2";
068     *   . . . 
069     * </CODE>
070     * and used through their names (KEY_ONE ...) rather than
071     * their values ("KEY1" ...).
072     * 
073     * The field contents (returned by getContents()
074     * should be initialized something like this:
075     * <CODE>
076     * public static final Object[][] contents = {
077     * { KEY_ONE, "Something has gone wrong!" },
078     * { KEY_TWO, "The file ''{0}'' does not exist in directory ''{1}''." },
079     * . . .
080     * { KEY_N, "Message N" }  }
081     * </CODE>
082     * 
083     * Where that section of code with the KEY to Message mappings
084     * (where the message classes 'contents' field is initialized)
085     * can have the Message strings translated in an alternate language
086     * in a errorResourceClass with a language suffix.
087     * 
088     * More sophisticated use of this class would be to pass null
089     * when contructing it, but then call loadResourceBundle()
090     * before creating any messages.
091     * 
092     * This class is not a public API, it is only public because it is 
093     * used in org.apache.xml.serializer.
094     *
095     *  @xsl.usage internal
096     */
097    public final class Messages
098    {
099        /** The local object to use.  */
100        private final Locale m_locale = Locale.getDefault();
101    
102        /** The language specific resource object for messages.  */
103        private ListResourceBundle m_resourceBundle;
104    
105        /** The class name of the error message string table with no language suffix. */
106        private String m_resourceBundleName;
107    
108    
109    
110        /**
111         * Constructor.
112         * @param resourceBundle the class name of the ListResourceBundle
113         * that the instance of this class is associated with and will use when
114         * creating messages.
115         * The class name is without a language suffix. If the value passed
116         * is null then loadResourceBundle(errorResourceClass) needs to be called
117         * explicitly before any messages are created.
118         * 
119         * @xsl.usage internal
120         */
121        Messages(String resourceBundle)
122        {
123    
124            m_resourceBundleName = resourceBundle;
125        }
126        
127        /*
128         * Set the Locale object to use. If this method is not called the
129         * default locale is used. This method needs to be called before
130         * loadResourceBundle().
131         * 
132         * @param locale non-null reference to Locale object.
133         * @xsl.usage internal
134         */
135    //    public void setLocale(Locale locale)
136    //    {
137    //        m_locale = locale;
138    //    }
139    
140        /**
141         * Get the Locale object that is being used.
142         * 
143         * @return non-null reference to Locale object.
144         * @xsl.usage internal
145         */
146        private Locale getLocale()
147        {
148            return m_locale;
149        }
150    
151        /**
152         * Get the ListResourceBundle being used by this Messages instance which was
153         * previously set by a call to loadResourceBundle(className)
154         * @xsl.usage internal
155         */
156        private ListResourceBundle getResourceBundle()
157        {
158            return m_resourceBundle;
159        }
160    
161        /**
162         * Creates a message from the specified key and replacement
163         * arguments, localized to the given locale.
164         *
165         * @param msgKey  The key for the message text.
166         * @param args    The arguments to be used as replacement text
167         * in the message created.
168         *
169         * @return The formatted message string.
170         * @xsl.usage internal
171         */
172        public final String createMessage(String msgKey, Object args[])
173        {
174            if (m_resourceBundle == null)
175                m_resourceBundle = loadResourceBundle(m_resourceBundleName);
176    
177            if (m_resourceBundle != null)
178            {
179                return createMsg(m_resourceBundle, msgKey, args);
180            }
181            else
182                return "Could not load the resource bundles: "+ m_resourceBundleName;
183        }
184    
185        /**
186         * Creates a message from the specified key and replacement
187         * arguments, localized to the given locale.
188         *
189         * @param errorCode The key for the message text.
190         *
191         * @param fResourceBundle The resource bundle to use.
192         * @param msgKey  The message key to use.
193         * @param args      The arguments to be used as replacement text
194         *                  in the message created.
195         *
196         * @return The formatted message string.
197         * @xsl.usage internal
198         */
199        private final String createMsg(
200            ListResourceBundle fResourceBundle,
201            String msgKey,
202            Object args[]) //throws Exception
203        {
204    
205            String fmsg = null;
206            boolean throwex = false;
207            String msg = null;
208    
209            if (msgKey != null)
210                msg = fResourceBundle.getString(msgKey);
211            else
212                msgKey = "";
213    
214            if (msg == null)
215            {
216                throwex = true;
217                /* The message is not in the bundle . . . this is bad,
218                 * so try to get the message that the message is not in the bundle
219                 */
220                try
221                {
222    
223                    msg =
224                        java.text.MessageFormat.format(
225                            MsgKey.BAD_MSGKEY,
226                            new Object[] { msgKey, m_resourceBundleName });
227                }
228                catch (Exception e)
229                {
230                    /* even the message that the message is not in the bundle is
231                     * not there ... this is really bad
232                     */
233                    msg =
234                        "The message key '"
235                            + msgKey
236                            + "' is not in the message class '"
237                            + m_resourceBundleName+"'";
238                }
239            }
240            else if (args != null)
241            {
242                try
243                {
244                    // Do this to keep format from crying.
245                    // This is better than making a bunch of conditional
246                    // code all over the place.
247                    int n = args.length;
248    
249                    for (int i = 0; i < n; i++)
250                    {
251                        if (null == args[i])
252                            args[i] = "";
253                    }
254    
255                    fmsg = java.text.MessageFormat.format(msg, args);
256                    // if we get past the line above we have create the message ... hurray!
257                }
258                catch (Exception e)
259                {
260                    throwex = true;
261                    try
262                    {
263                        // Get the message that the format failed.
264                        fmsg =
265                            java.text.MessageFormat.format(
266                                MsgKey.BAD_MSGFORMAT,
267                                new Object[] { msgKey, m_resourceBundleName });
268                        fmsg += " " + msg;
269                    }
270                    catch (Exception formatfailed)
271                    {
272                        // We couldn't even get the message that the format of
273                        // the message failed ... so fall back to English.
274                        fmsg =
275                            "The format of message '"
276                                + msgKey
277                                + "' in message class '"
278                                + m_resourceBundleName
279                                + "' failed.";
280                    }
281                }
282            }
283            else
284                fmsg = msg;
285    
286            if (throwex)
287            {
288                throw new RuntimeException(fmsg);
289            }
290    
291            return fmsg;
292        }
293    
294        /**
295         * Return a named ResourceBundle for a particular locale.  This method mimics the behavior
296         * of ResourceBundle.getBundle().
297         * 
298         * @param className the name of the class that implements ListResourceBundle,
299         * without language suffix.
300         * @return the ResourceBundle
301         * @throws MissingResourceException
302         * @xsl.usage internal
303         */
304        private ListResourceBundle loadResourceBundle(String resourceBundle)
305            throws MissingResourceException
306        {
307            m_resourceBundleName = resourceBundle;
308            Locale locale = getLocale();
309    
310            ListResourceBundle lrb;
311    
312            try
313            {
314    
315                ResourceBundle rb =
316                    ResourceBundle.getBundle(m_resourceBundleName, locale);
317                lrb = (ListResourceBundle) rb;
318            }
319            catch (MissingResourceException e)
320            {
321                try // try to fall back to en_US if we can't load
322                    {
323    
324                    // Since we can't find the localized property file,
325                    // fall back to en_US.
326                    lrb =
327                        (ListResourceBundle) ResourceBundle.getBundle(
328                            m_resourceBundleName,
329                            new Locale("en", "US"));
330                }
331                catch (MissingResourceException e2)
332                {
333    
334                    // Now we are really in trouble.
335                    // very bad, definitely very bad...not going to get very far
336                    throw new MissingResourceException(
337                        "Could not load any resource bundles." + m_resourceBundleName,
338                        m_resourceBundleName,
339                        "");
340                }
341            }
342            m_resourceBundle = lrb;
343            return lrb;
344        }
345    
346        /**
347         * Return the resource file suffic for the indicated locale
348         * For most locales, this will be based the language code.  However
349         * for Chinese, we do distinguish between Taiwan and PRC
350         *
351         * @param locale the locale
352         * @return an String suffix which can be appended to a resource name
353         * @xsl.usage internal
354         */
355        private static String getResourceSuffix(Locale locale)
356        {
357    
358            String suffix = "_" + locale.getLanguage();
359            String country = locale.getCountry();
360    
361            if (country.equals("TW"))
362                suffix += "_" + country;
363    
364            return suffix;
365        }
366    }