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