1 /* 2 Copyright 2010 Sun Microsystems, Inc. 3 All rights reserved. Use is subject to license terms. 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License, version 2.0, 7 as published by the Free Software Foundation. 8 9 This program is also distributed with certain software (including 10 but not limited to OpenSSL) that is licensed under separate terms, 11 as designated in a particular file or component or in included license 12 documentation. The authors of MySQL hereby grant you an additional 13 permission to link the program and your derivative works with the 14 separately licensed software that they have included with MySQL. 15 16 This program is distributed in the hope that it will be useful, 17 but WITHOUT ANY WARRANTY; without even the implied warranty of 18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 GNU General Public License, version 2.0, for more details. 20 21 You should have received a copy of the GNU General Public License 22 along with this program; if not, write to the Free Software 23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 24 */ 25 26 package com.mysql.clusterj.core.util; 27 28 import java.util.*; 29 import java.text.MessageFormat; 30 import java.security.AccessController; 31 import java.security.PrivilegedAction; 32 33 import com.mysql.clusterj.ClusterJFatalInternalException; 34 35 /** Helper class for constructing messages from bundles. The intended usage 36 * of this class is to construct a new instance bound to a bundle, as in 37 * <P> 38 * <code>I18NHelper local = 39 * I18NHelper.getInstance("com.mysql.clusterj.core.Bundle");</code> 40 * <P> 41 * This call uses the class loader that loaded the I18NHelper class to find 42 * the specified Bundle. The class provides two overloaded getInstance 43 * methods allowing to specify a different class loader: 44 * {@link #getInstance(Class cls)} looks for a bundle 45 * called "Bundle.properties" located in the package of the specified class 46 * object and {@link #getInstance(String bundleName,ClassLoader loader)} 47 * uses the specified class loader to find the bundle. 48 * <P> 49 * Subsequently, instance methods can be used to format message strings 50 * using the text from the bundle, as in 51 * <P> 52 * <code>throw new JDOFatalInternalException (local.message("ERR_NoMetadata", 53 * cls.getName()));</code> 54 */ 55 public class I18NHelper { 56 57 /** The logger */ 58 private static Logger logger = LoggerFactoryService.getFactory() 59 .getInstance(I18NHelper.class); 60 61 /** Bundles that have already been loaded 62 */ 63 private static Hashtable<String, ResourceBundle> bundles = new Hashtable<String, ResourceBundle>(); 64 65 /** Helper instances that have already been created 66 */ 67 private static Hashtable<String, I18NHelper> helpers = new Hashtable<String, I18NHelper>(); 68 69 /** The default locale for this VM. 70 */ 71 private static Locale locale = Locale.getDefault(); 72 73 /** The name of the bundle used by this instance of the helper. 74 */ 75 private final String bundleName; 76 77 /** The bundle used by this instance of the helper. 78 */ 79 private ResourceBundle bundle = null; 80 81 /** Throwable if ResourceBundle couldn't be loaded 82 */ 83 private Throwable failure = null; 84 85 /** The unqualified standard name of a bundle. */ 86 private static final String bundleSuffix = ".Bundle"; // NOI18N 87 88 /** Constructor */ I18NHelper()89 private I18NHelper() { 90 this.bundleName = null; 91 } 92 93 /** Constructor for an instance bound to a bundle. 94 * @param bundleName the name of the resource bundle 95 * @param loader the class loader from which to load the resource 96 * bundle 97 */ I18NHelper(String bundleName, ClassLoader loader)98 private I18NHelper (String bundleName, ClassLoader loader) { 99 this.bundleName = bundleName; 100 try { 101 bundle = loadBundle (bundleName, bundleName, loader); 102 } 103 catch (Throwable e) { 104 failure = e; 105 } 106 } 107 108 /** An instance bound to a bundle. This method uses the current class 109 * loader to find the bundle. 110 * @param bundleName the name of the bundle 111 * @return the helper instance bound to the bundle 112 */ getInstance(String bundleName)113 public static I18NHelper getInstance (String bundleName) { 114 return getInstance (bundleName, I18NHelper.class.getClassLoader()); 115 } 116 117 /** An instance bound to a bundle. This method figures out the bundle name 118 * for the class object's package and uses the class' class loader to 119 * find the bundle. Note, the specified class object must not be 120 * <code>null</code>. 121 * @param cls the class object from which to load the resource bundle 122 * @return the helper instance bound to the bundle 123 */ getInstance(final Class<?> cls)124 public static I18NHelper getInstance (final Class<?> cls) { 125 ClassLoader classLoader = AccessController.doPrivileged ( 126 new PrivilegedAction<ClassLoader> () { 127 public ClassLoader run () { 128 return cls.getClassLoader(); 129 } 130 } 131 ); 132 String bundle = getPackageName (cls.getName()) + bundleSuffix; 133 return getInstance (bundle, classLoader); 134 } 135 136 /** An instance bound to a bundle. This method uses the specified class 137 * loader to find the bundle. Note, the specified class loader must not 138 * be <code>null</code>. 139 * @param bundleName the name of the bundle 140 * @param loader the class loader from which to load the resource 141 * bundle 142 * @return the helper instance bound to the bundle 143 */ getInstance(String bundleName, ClassLoader loader)144 public static I18NHelper getInstance (String bundleName, 145 ClassLoader loader) { 146 I18NHelper helper = helpers.get (bundleName); 147 if (helper != null) { 148 return helper; 149 } 150 helper = new I18NHelper(bundleName, loader); 151 helpers.put (bundleName, helper); 152 // if two threads simultaneously create the same helper, return the first 153 // one to be put into the Hashtable. The other will be garbage collected. 154 return helpers.get (bundleName); 155 } 156 157 /** Message formatter 158 * @param messageKey the message key 159 * @return the resolved message text 160 */ message(String messageKey)161 public String message (String messageKey) { 162 assertBundle (messageKey); 163 return getMessage (bundle, messageKey); 164 } 165 166 /** Message formatter 167 * @param messageKey the message key 168 * @param arg1 the first argument 169 * @return the resolved message text 170 */ message(String messageKey, Object arg1)171 public String message (String messageKey, Object arg1) { 172 assertBundle (messageKey); 173 return getMessage (bundle, messageKey, arg1); 174 } 175 176 /** Message formatter 177 * @param messageKey the message key 178 * @param arg1 the first argument 179 * @param arg2 the second argument 180 * @return the resolved message text 181 */ message(String messageKey, Object arg1, Object arg2)182 public String message (String messageKey, Object arg1, Object arg2) { 183 assertBundle (messageKey); 184 return getMessage (bundle, messageKey, arg1, arg2); 185 } 186 187 /** Message formatter 188 * @param messageKey the message key 189 * @param args the array of arguments 190 * @return the resolved message text 191 */ message(String messageKey, Object... args)192 public String message (String messageKey, Object... args) { 193 assertBundle (messageKey); 194 return getMessage (bundle, messageKey, args); 195 } 196 197 /** Message formatter 198 * @param messageKey the message key 199 * @param arg the argument 200 * @return the resolved message text 201 */ message(String messageKey, int arg)202 public String message (String messageKey, int arg) { 203 assertBundle (messageKey); 204 return getMessage(bundle, messageKey, arg); 205 } 206 207 /** Message formatter 208 * @param messageKey the message key 209 * @param arg the argument 210 * @return the resolved message text 211 */ message(String messageKey, boolean arg)212 public String message (String messageKey, boolean arg) { 213 assertBundle (messageKey); 214 return getMessage(bundle, messageKey, arg); 215 } 216 217 /** Returns the resource bundle used by this I18NHelper. 218 * @return the associated resource bundle 219 */ getResourceBundle()220 public ResourceBundle getResourceBundle () { 221 assertBundle (); 222 return bundle; 223 } 224 225 //========= Internal helper methods ========== 226 227 /** 228 * Load ResourceBundle by bundle name 229 * @param bundleName the name of the bundle 230 * @param loader the class loader from which to load the resource bundle 231 * @return the ResourceBundle 232 */ loadBundle( String original, String bundleName, ClassLoader loader)233 final private static ResourceBundle loadBundle( 234 String original, String bundleName, ClassLoader loader) { 235 ResourceBundle messages = bundles.get(bundleName); 236 237 if (messages == null) //not found as loaded - add 238 { 239 try { 240 if (loader != null) { 241 messages = ResourceBundle.getBundle(bundleName, locale, loader); 242 } else { 243 // the library was loaded by the boostrap class loader 244 messages = ResourceBundle.getBundle(bundleName, locale, 245 getSystemClassLoaderPrivileged()); 246 } 247 bundles.put(bundleName, messages); 248 } catch (java.util.MissingResourceException ex) { 249 // recursively try to find the Bundle in the next higher package 250 String superBundleName = removeDirectoryName(bundleName); 251 if (superBundleName == null) { 252 throw new ClusterJFatalInternalException( 253 "Missing resource bundle " + original); 254 } 255 messages = loadBundle(original, superBundleName, loader); 256 } 257 } 258 return messages; 259 } 260 261 /** Assert resources available 262 * @throws JDOFatalInternalException if the resource bundle could not 263 * be loaded during construction. 264 */ assertBundle()265 private void assertBundle () { 266 if (failure != null) 267 throw new ClusterJFatalInternalException ( 268 "No resources could be found for bundle:\"" + 269 bundleName + "\" ", failure); 270 } 271 272 /** Assert resources available 273 * @param key the message key 274 * @throws JDOFatalInternalException if the resource bundle could not 275 * be loaded during construction. 276 */ assertBundle(String key)277 private void assertBundle (String key) { 278 if (failure != null) 279 throw new ClusterJFatalInternalException ( 280 "No resources could be found for bundle: " + bundleName 281 + " to annotate error message key:\"" 282 + key + "\"", failure); 283 } 284 285 /** 286 * Returns message as <code>String</code> 287 * @param messages the resource bundle 288 * @param messageKey the message key 289 * @return the resolved message text 290 */ getMessage(ResourceBundle messages, String messageKey)291 final private static String getMessage(ResourceBundle messages, String messageKey) 292 { 293 return messages.getString(messageKey); 294 } 295 296 /** 297 * Formats message by adding array of arguments 298 * @param messages the resource bundle 299 * @param messageKey the message key 300 * @param msgArgs an array of arguments to substitute into the message 301 * @return the resolved message text 302 */ getMessage(ResourceBundle messages, String messageKey, Object[] msgArgs)303 final private static String getMessage(ResourceBundle messages, 304 String messageKey, Object[] msgArgs) 305 { 306 for (int i=0; i<msgArgs.length; i++) { 307 if (msgArgs[i] == null) msgArgs[i] = ""; // NOI18N 308 } 309 MessageFormat formatter = new MessageFormat(messages.getString(messageKey)); 310 return formatter.format(msgArgs); 311 } 312 313 /** 314 * Formats message by adding an <code>Object</code> argument. 315 * @param messages the resource bundle 316 * @param messageKey the message key 317 * @param arg the argument 318 * @return the resolved message text 319 */ getMessage(ResourceBundle messages, String messageKey, Object arg)320 final private static String getMessage(ResourceBundle messages, 321 String messageKey, Object arg) 322 { 323 Object []args = {arg}; 324 return getMessage(messages, messageKey, args); 325 } 326 327 /** 328 * Formats message by adding two <code>Object</code> arguments. 329 * @param messages the resource bundle 330 * @param messageKey the message key 331 * @param arg1 the first argument 332 * @param arg2 the second argument 333 * @return the resolved message text 334 */ getMessage(ResourceBundle messages, String messageKey, Object arg1, Object arg2)335 final private static String getMessage(ResourceBundle messages, 336 String messageKey, Object arg1, Object arg2) 337 { 338 Object []args = {arg1, arg2}; 339 return getMessage(messages, messageKey, args); 340 } 341 342 /** 343 * Formats message by adding an <code>int</code> as an argument. 344 * @param messages the resource bundle 345 * @param messageKey the message key 346 * @param arg the argument 347 * @return the resolved message text 348 */ getMessage(ResourceBundle messages, String messageKey, int arg)349 final private static String getMessage(ResourceBundle messages, 350 String messageKey, int arg) 351 { 352 Object []args = {new Integer(arg)}; 353 return getMessage(messages, messageKey, args); 354 } 355 356 /** 357 * Formats message by adding a <code>boolean</code> as an argument. 358 * @param messages the resource bundle 359 * @param messageKey the message key 360 * @param arg the argument 361 * @return the resolved message text 362 */ getMessage(ResourceBundle messages, String messageKey, boolean arg)363 final private static String getMessage(ResourceBundle messages, 364 String messageKey, boolean arg) 365 { 366 Object []args = {String.valueOf(arg)}; 367 return getMessage(messages, messageKey, args); 368 } 369 370 /** 371 * Returns the package portion of the specified class. 372 * @param className the name of the class from which to extract the 373 * package 374 * @return package portion of the specified class 375 */ getPackageName(final String className)376 final private static String getPackageName(final String className) 377 { 378 final int index = className.lastIndexOf('.'); 379 return ((index != -1) ? className.substring(0, index) : ""); // NOI18N 380 } 381 382 /** Return the bundle name of the super package. For example, 383 * if the bundleName is com.mysql.cluster.util.deeper.Bundle, 384 * return com.mysql.cluster.util.Bundle. 385 * @param bundleName the bundle name 386 * @return the bundle name of the super package 387 */ removeDirectoryName(String bundleName)388 private static String removeDirectoryName(String bundleName) { 389 String result; 390 int lastDot = bundleName.lastIndexOf("."); 391 String packageName = bundleName.substring(0, lastDot); 392 String suffix = bundleName.substring(lastDot); 393 int index = packageName.lastIndexOf("."); 394 if (index == -1) { 395 return null; 396 } 397 String superPackageName = packageName.substring(0, index); 398 result = superPackageName + suffix; 399 400 if (logger.isDebugEnabled()) { 401 logger.debug("bundleName is: " + bundleName + 402 "; superPackageName is: " + superPackageName + 403 "; suffix is: " + suffix + 404 "; packageName is: " + packageName + 405 "; returning: " + result); 406 } 407 return result; 408 } 409 410 /** 411 * Get the system class loader. This must be done in a doPrivileged 412 * block because of security. 413 */ getSystemClassLoaderPrivileged()414 private static ClassLoader getSystemClassLoaderPrivileged() { 415 return AccessController.doPrivileged ( 416 new PrivilegedAction<ClassLoader> () { 417 public ClassLoader run () { 418 return ClassLoader.getSystemClassLoader(); 419 } 420 } 421 ); 422 } 423 } 424