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