2 Copyright 2010 Sun Microsystems, Inc.
3 All rights reserved. Use is subject to license terms.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of the License.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 package com.mysql.clusterj.core.util;
22 import java.text.MessageFormat;
23 import java.security.AccessController;
24 import java.security.PrivilegedAction;
26 import com.mysql.clusterj.ClusterJFatalInternalException;
28 /** Helper class for constructing messages from bundles. The intended usage
29 * of this class is to construct a new instance bound to a bundle, as in
31 * <code>I18NHelper local =
32 * I18NHelper.getInstance("com.mysql.clusterj.core.Bundle");</code>
34 * This call uses the class loader that loaded the I18NHelper class to find
35 * the specified Bundle. The class provides two overloaded getInstance
36 * methods allowing to specify a different class loader:
37 * {@link #getInstance(Class cls)} looks for a bundle
38 * called "Bundle.properties" located in the package of the specified class
39 * object and {@link #getInstance(String bundleName,ClassLoader loader)}
40 * uses the specified class loader to find the bundle.
42 * Subsequently, instance methods can be used to format message strings
43 * using the text from the bundle, as in
45 * <code>throw new JDOFatalInternalException (local.message("ERR_NoMetadata",
46 * cls.getName()));</code>
48 public class I18NHelper {
51 private static Logger logger = LoggerFactoryService.getFactory()
52 .getInstance(I18NHelper.class);
54 /** Bundles that have already been loaded
56 private static Hashtable<String, ResourceBundle> bundles = new Hashtable<String, ResourceBundle>();
58 /** Helper instances that have already been created
60 private static Hashtable<String, I18NHelper> helpers = new Hashtable<String, I18NHelper>();
62 /** The default locale for this VM.
64 private static Locale locale = Locale.getDefault();
66 /** The name of the bundle used by this instance of the helper.
68 private final String bundleName;
70 /** The bundle used by this instance of the helper.
72 private ResourceBundle bundle = null;
74 /** Throwable if ResourceBundle couldn't be loaded
76 private Throwable failure = null;
78 /** The unqualified standard name of a bundle. */
79 private static final String bundleSuffix = ".Bundle"; // NOI18N
82 private I18NHelper() {
83 this.bundleName = null;
86 /** Constructor for an instance bound to a bundle.
87 * @param bundleName the name of the resource bundle
88 * @param loader the class loader from which to load the resource
91 private I18NHelper (String bundleName, ClassLoader loader) {
92 this.bundleName = bundleName;
94 bundle = loadBundle (bundleName, bundleName, loader);
101 /** An instance bound to a bundle. This method uses the current class
102 * loader to find the bundle.
103 * @param bundleName the name of the bundle
104 * @return the helper instance bound to the bundle
106 public static I18NHelper getInstance (String bundleName) {
107 return getInstance (bundleName, I18NHelper.class.getClassLoader());
110 /** An instance bound to a bundle. This method figures out the bundle name
111 * for the class object's package and uses the class' class loader to
112 * find the bundle. Note, the specified class object must not be
114 * @param cls the class object from which to load the resource bundle
115 * @return the helper instance bound to the bundle
117 public static I18NHelper getInstance (final Class<?> cls) {
118 ClassLoader classLoader = AccessController.doPrivileged (
119 new PrivilegedAction<ClassLoader> () {
120 public ClassLoader run () {
121 return cls.getClassLoader();
125 String bundle = getPackageName (cls.getName()) + bundleSuffix;
126 return getInstance (bundle, classLoader);
129 /** An instance bound to a bundle. This method uses the specified class
130 * loader to find the bundle. Note, the specified class loader must not
131 * be <code>null</code>.
132 * @param bundleName the name of the bundle
133 * @param loader the class loader from which to load the resource
135 * @return the helper instance bound to the bundle
137 public static I18NHelper getInstance (String bundleName,
138 ClassLoader loader) {
139 I18NHelper helper = helpers.get (bundleName);
140 if (helper != null) {
143 helper = new I18NHelper(bundleName, loader);
144 helpers.put (bundleName, helper);
145 // if two threads simultaneously create the same helper, return the first
146 // one to be put into the Hashtable. The other will be garbage collected.
147 return helpers.get (bundleName);
150 /** Message formatter
151 * @param messageKey the message key
152 * @return the resolved message text
154 public String message (String messageKey) {
155 assertBundle (messageKey);
156 return getMessage (bundle, messageKey);
159 /** Message formatter
160 * @param messageKey the message key
161 * @param arg1 the first argument
162 * @return the resolved message text
164 public String message (String messageKey, Object arg1) {
165 assertBundle (messageKey);
166 return getMessage (bundle, messageKey, arg1);
169 /** Message formatter
170 * @param messageKey the message key
171 * @param arg1 the first argument
172 * @param arg2 the second argument
173 * @return the resolved message text
175 public String message (String messageKey, Object arg1, Object arg2) {
176 assertBundle (messageKey);
177 return getMessage (bundle, messageKey, arg1, arg2);
180 /** Message formatter
181 * @param messageKey the message key
182 * @param args the array of arguments
183 * @return the resolved message text
185 public String message (String messageKey, Object... args) {
186 assertBundle (messageKey);
187 return getMessage (bundle, messageKey, args);
190 /** Message formatter
191 * @param messageKey the message key
192 * @param arg the argument
193 * @return the resolved message text
195 public String message (String messageKey, int arg) {
196 assertBundle (messageKey);
197 return getMessage(bundle, messageKey, arg);
200 /** Message formatter
201 * @param messageKey the message key
202 * @param arg the argument
203 * @return the resolved message text
205 public String message (String messageKey, boolean arg) {
206 assertBundle (messageKey);
207 return getMessage(bundle, messageKey, arg);
210 /** Returns the resource bundle used by this I18NHelper.
211 * @return the associated resource bundle
213 public ResourceBundle getResourceBundle () {
218 //========= Internal helper methods ==========
221 * Load ResourceBundle by bundle name
222 * @param bundleName the name of the bundle
223 * @param loader the class loader from which to load the resource bundle
224 * @return the ResourceBundle
226 final private static ResourceBundle loadBundle(
227 String original, String bundleName, ClassLoader loader) {
228 ResourceBundle messages = bundles.get(bundleName);
230 if (messages == null) //not found as loaded - add
233 if (loader != null) {
234 messages = ResourceBundle.getBundle(bundleName, locale, loader);
236 // the library was loaded by the boostrap class loader
237 messages = ResourceBundle.getBundle(bundleName, locale,
238 getSystemClassLoaderPrivileged());
240 bundles.put(bundleName, messages);
241 } catch (java.util.MissingResourceException ex) {
242 // recursively try to find the Bundle in the next higher package
243 String superBundleName = removeDirectoryName(bundleName);
244 if (superBundleName == null) {
245 throw new ClusterJFatalInternalException(
246 "Missing resource bundle " + original);
248 messages = loadBundle(original, superBundleName, loader);
254 /** Assert resources available
255 * @throws JDOFatalInternalException if the resource bundle could not
256 * be loaded during construction.
258 private void assertBundle () {
260 throw new ClusterJFatalInternalException (
261 "No resources could be found for bundle:\"" +
262 bundleName + "\" ", failure);
265 /** Assert resources available
266 * @param key the message key
267 * @throws JDOFatalInternalException if the resource bundle could not
268 * be loaded during construction.
270 private void assertBundle (String key) {
272 throw new ClusterJFatalInternalException (
273 "No resources could be found for bundle: " + bundleName
274 + " to annotate error message key:\""
275 + key + "\"", failure);
279 * Returns message as <code>String</code>
280 * @param messages the resource bundle
281 * @param messageKey the message key
282 * @return the resolved message text
284 final private static String getMessage(ResourceBundle messages, String messageKey)
286 return messages.getString(messageKey);
290 * Formats message by adding array of arguments
291 * @param messages the resource bundle
292 * @param messageKey the message key
293 * @param msgArgs an array of arguments to substitute into the message
294 * @return the resolved message text
296 final private static String getMessage(ResourceBundle messages,
297 String messageKey, Object[] msgArgs)
299 for (int i=0; i<msgArgs.length; i++) {
300 if (msgArgs[i] == null) msgArgs[i] = ""; // NOI18N
302 MessageFormat formatter = new MessageFormat(messages.getString(messageKey));
303 return formatter.format(msgArgs);
307 * Formats message by adding an <code>Object</code> argument.
308 * @param messages the resource bundle
309 * @param messageKey the message key
310 * @param arg the argument
311 * @return the resolved message text
313 final private static String getMessage(ResourceBundle messages,
314 String messageKey, Object arg)
316 Object []args = {arg};
317 return getMessage(messages, messageKey, args);
321 * Formats message by adding two <code>Object</code> arguments.
322 * @param messages the resource bundle
323 * @param messageKey the message key
324 * @param arg1 the first argument
325 * @param arg2 the second argument
326 * @return the resolved message text
328 final private static String getMessage(ResourceBundle messages,
329 String messageKey, Object arg1, Object arg2)
331 Object []args = {arg1, arg2};
332 return getMessage(messages, messageKey, args);
336 * Formats message by adding an <code>int</code> as an argument.
337 * @param messages the resource bundle
338 * @param messageKey the message key
339 * @param arg the argument
340 * @return the resolved message text
342 final private static String getMessage(ResourceBundle messages,
343 String messageKey, int arg)
345 Object []args = {new Integer(arg)};
346 return getMessage(messages, messageKey, args);
350 * Formats message by adding a <code>boolean</code> as an argument.
351 * @param messages the resource bundle
352 * @param messageKey the message key
353 * @param arg the argument
354 * @return the resolved message text
356 final private static String getMessage(ResourceBundle messages,
357 String messageKey, boolean arg)
359 Object []args = {String.valueOf(arg)};
360 return getMessage(messages, messageKey, args);
364 * Returns the package portion of the specified class.
365 * @param className the name of the class from which to extract the
367 * @return package portion of the specified class
369 final private static String getPackageName(final String className)
371 final int index = className.lastIndexOf('.');
372 return ((index != -1) ? className.substring(0, index) : ""); // NOI18N
375 /** Return the bundle name of the super package. For example,
376 * if the bundleName is com.mysql.cluster.util.deeper.Bundle,
377 * return com.mysql.cluster.util.Bundle.
378 * @param bundleName the bundle name
379 * @return the bundle name of the super package
381 private static String removeDirectoryName(String bundleName) {
383 int lastDot = bundleName.lastIndexOf(".");
384 String packageName = bundleName.substring(0, lastDot);
385 String suffix = bundleName.substring(lastDot);
386 int index = packageName.lastIndexOf(".");
390 String superPackageName = packageName.substring(0, index);
391 result = superPackageName + suffix;
393 if (logger.isDebugEnabled()) {
394 logger.debug("bundleName is: " + bundleName +
395 "; superPackageName is: " + superPackageName +
396 "; suffix is: " + suffix +
397 "; packageName is: " + packageName +
398 "; returning: " + result);
404 * Get the system class loader. This must be done in a doPrivileged
405 * block because of security.
407 private static ClassLoader getSystemClassLoaderPrivileged() {
408 return AccessController.doPrivileged (
409 new PrivilegedAction<ClassLoader> () {
410 public ClassLoader run () {
411 return ClassLoader.getSystemClassLoader();