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 }