1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you 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/*
19 * $Id: Messages.java 468654 2006-10-28 07:09:23Z minchau $
20 */
21package org.apache.xml.serializer.utils;
22
23import java.util.ListResourceBundle;
24import java.util.Locale;
25import java.util.MissingResourceException;
26import java.util.ResourceBundle;
27
28/**
29 * A utility class for issuing error messages.
30 *
31 * A user of this class normally would create a singleton
32 * instance of this class, passing the name
33 * of the message class on the constructor. For example:
34 * <CODE>
35 * static Messages x = new Messages("org.package.MyMessages");
36 * </CODE>
37 * Later the message is typically generated this way if there are no
38 * substitution arguments:
39 * <CODE>
40 * String msg = x.createMessage(org.package.MyMessages.KEY_ONE, null);
41 * </CODE>
42 * If there are arguments substitutions then something like this:
43 * <CODE>
44 * String filename = ...;
45 * String directory = ...;
46 * String msg = x.createMessage(org.package.MyMessages.KEY_TWO,
47 *   new Object[] {filename, directory) );
48 * </CODE>
49 *
50 * The constructor of an instance of this class must be given
51 * the class name of a class that extends java.util.ListResourceBundle
52 * ("org.package.MyMessages" in the example above).
53 * The name should not have any language suffix
54 * which will be added automatically by this utility class.
55 *
56 * The message class ("org.package.MyMessages")
57 * must define the abstract method getContents() that is
58 * declared in its base class, for example:
59 * <CODE>
60 * public Object[][] getContents() {return contents;}
61 * </CODE>
62 *
63 * It is suggested that the message class expose its
64 * message keys like this:
65 * <CODE>
66 *   public static final String KEY_ONE = "KEY1";
67 *   public static final String KEY_TWO = "KEY2";
68 *   . . .
69 * </CODE>
70 * and used through their names (KEY_ONE ...) rather than
71 * their values ("KEY1" ...).
72 *
73 * The field contents (returned by getContents()
74 * should be initialized something like this:
75 * <CODE>
76 * public static final Object[][] contents = {
77 * { KEY_ONE, "Something has gone wrong!" },
78 * { KEY_TWO, "The file ''{0}'' does not exist in directory ''{1}''." },
79 * . . .
80 * { KEY_N, "Message N" }  }
81 * </CODE>
82 *
83 * Where that section of code with the KEY to Message mappings
84 * (where the message classes 'contents' field is initialized)
85 * can have the Message strings translated in an alternate language
86 * in a errorResourceClass with a language suffix.
87 *
88 * More sophisticated use of this class would be to pass null
89 * when contructing it, but then call loadResourceBundle()
90 * before creating any messages.
91 *
92 * This class is not a public API, it is only public because it is
93 * used in org.apache.xml.serializer.
94 *
95 *  @xsl.usage internal
96 */
97public final class Messages
98{
99    /** 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}
367