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 */ 21 package org.apache.xml.serializer.utils; 22 23 import java.util.ListResourceBundle; 24 import java.util.Locale; 25 import java.util.MissingResourceException; 26 import 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 */ 97 public 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 */ Messages(String resourceBundle)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 */ getLocale()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 */ getResourceBundle()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 */ createMessage(String msgKey, Object args[])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 */ createMsg( ListResourceBundle fResourceBundle, String msgKey, Object args[])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 */ loadResourceBundle(String resourceBundle)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 */ getResourceSuffix(Locale locale)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