View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at 
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software 
11   * distributed under the License is distributed on an "AS IS" BASIS, 
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  
17  package javax.jdo.spi;
18  
19  import java.util.*;
20  import java.text.MessageFormat;
21  import java.security.AccessController;
22  import java.security.PrivilegedAction;
23  
24  import javax.jdo.JDOFatalInternalException;
25  
26  /*** Helper class for constructing messages from bundles.  The intended usage
27   * of this class is to construct a new instance bound to a bundle, as in
28   * <P>
29   * <code>I18NHelper msg = I18NHelper.getInstance("javax.jdo.Bundle");</code>
30   * <P>
31   * This call uses the class loader that loaded the I18NHelper class to find
32   * the specified Bundle. The class provides two overloaded getInstance
33   * methods allowing to specify a different class loader: 
34   * {@link #getInstance(Class cls)} looks for a bundle
35   * called "Bundle.properties" located in the package of the specified class 
36   * object and {@link #getInstance(String bundleName,ClassLoader loader)} 
37   * uses the specified class loader to find the bundle.
38   * <P>
39   * Subsequently, instance methods can be used to format message strings 
40   * using the text from the bundle, as in 
41   * <P>
42   * <code>throw new JDOFatalInternalException (msg.msg("ERR_NoMetadata", 
43   * cls.getName()));</code>
44   * @since 1.0.1
45   * @version 1.1
46   */        
47  public class I18NHelper {
48  
49      /*** Bundles that have already been loaded 
50       */
51      private static Hashtable    bundles = new Hashtable();
52      
53      /*** Helper instances that have already been created 
54       */
55      private static Hashtable    helpers = new Hashtable();
56      
57      /*** The default locale for this VM.
58       */
59      private static Locale       locale = Locale.getDefault();
60  
61      /*** The name of the bundle used by this instance of the helper.
62       */
63      private final String        bundleName;
64  
65      /*** The bundle used by this instance of the helper.
66       */
67      private ResourceBundle      bundle = null;
68  
69      /*** Throwable if ResourceBundle couldn't be loaded
70       */
71      private Throwable           failure = null;
72  
73      /*** The unqualified standard name of a bundle. */
74      private static final String bundleSuffix = ".Bundle";    // NOI18N
75  
76      /*** Constructor */
77      private I18NHelper() {
78          this.bundleName = null;
79      }
80  
81      /*** Constructor for an instance bound to a bundle.
82       * @param bundleName the name of the resource bundle
83       * @param loader the class loader from which to load the resource
84       * bundle
85       */
86      private I18NHelper (String bundleName, ClassLoader loader) {
87          this.bundleName = bundleName;
88          try {
89              bundle = loadBundle (bundleName, loader);
90          }
91          catch (Throwable e) {
92              failure = e;
93          }
94      }
95      
96      /*** An instance bound to a bundle. This method uses the current class 
97       * loader to find the bundle.
98       * @param bundleName the name of the bundle
99       * @return the helper instance bound to the bundle
100      */
101     public static I18NHelper getInstance (String bundleName) {
102         return getInstance (bundleName, I18NHelper.class.getClassLoader());
103     }
104 
105     /*** An instance bound to a bundle. This method figures out the bundle name
106      * for the class object's package and uses the class' class loader to
107      * find the bundle. Note, the specified class object must not be
108      * <code>null</code>.
109      * @param cls the class object from which to load the resource bundle
110      * @return the helper instance bound to the bundle
111      */
112     public static I18NHelper getInstance (final Class cls) {
113         ClassLoader classLoader = (ClassLoader) AccessController.doPrivileged (
114             new PrivilegedAction () {
115                 public Object run () {
116                     return cls.getClassLoader();
117                 }
118             }
119             );
120         String bundle = getPackageName (cls.getName()) + bundleSuffix;
121         return getInstance (bundle, classLoader);
122     }
123 
124     /*** An instance bound to a bundle. This method uses the specified class
125      * loader to find the bundle. Note, the specified class loader must not
126      * be <code>null</code>.
127      * @param bundleName the name of the bundle
128      * @param loader the class loader from which to load the resource
129      * bundle
130      * @return the helper instance bound to the bundle
131      */
132     public static I18NHelper getInstance (String bundleName, 
133                                           ClassLoader loader) {
134         I18NHelper helper = (I18NHelper) helpers.get (bundleName);
135         if (helper != null) {
136             return helper;
137         }
138         helper = new I18NHelper(bundleName, loader);
139         helpers.put (bundleName, helper);
140         // if two threads simultaneously create the same helper, return the first
141         // one to be put into the Hashtable.  The other will be garbage collected.
142         return (I18NHelper) helpers.get (bundleName);
143     }
144 
145     /*** Message formatter
146      * @param messageKey the message key
147      * @return the resolved message text
148      */
149     public String msg (String messageKey) {
150         assertBundle (messageKey);
151         return getMessage (bundle, messageKey);
152     }
153 
154     /*** Message formatter
155      * @param messageKey the message key
156      * @param arg1 the first argument
157      * @return the resolved message text
158      */
159     public String msg (String messageKey, Object arg1) {
160         assertBundle (messageKey);
161         return getMessage (bundle, messageKey, arg1);
162     }
163 
164     /*** Message formatter
165      * @param messageKey the message key
166      * @param arg1 the first argument
167      * @param arg2 the second argument
168      * @return the resolved message text
169      */
170     public String msg (String messageKey, Object arg1, Object arg2) {
171         assertBundle (messageKey);
172         return getMessage (bundle, messageKey, arg1, arg2);
173     }
174 
175     /*** Message formatter
176      * @param messageKey the message key
177      * @param arg1 the first argument
178      * @param arg2 the second argument
179      * @param arg3 the third argument
180      * @return the resolved message text
181      */
182     public String msg (String messageKey, Object arg1, Object arg2, Object arg3) {
183         assertBundle (messageKey);
184         return getMessage (bundle, messageKey, arg1, arg2, arg3);
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      */
192     public String msg (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      */
202     public String msg (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      */
212     public String msg (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      * @since 1.1
220      */
221     public ResourceBundle getResourceBundle () {
222         assertBundle ();
223         return bundle;
224     }
225     
226     //========= Internal helper methods ==========
227 
228     /***
229      * Load ResourceBundle by bundle name
230      * @param bundleName the name of the bundle
231      * @param loader the class loader from which to load the resource bundle
232      * @return  the ResourceBundle
233      */
234     final private static ResourceBundle loadBundle(
235         String bundleName, ClassLoader loader) {
236         ResourceBundle messages = (ResourceBundle)bundles.get(bundleName);
237 
238         if (messages == null) //not found as loaded - add
239         {
240             if (loader != null) {
241                 messages = ResourceBundle.getBundle(bundleName, locale, loader);
242             } else {
243                 // the JDO library is loaded by the boostrap class loader
244                 messages = ResourceBundle.getBundle(bundleName, locale,
245                         getSystemClassLoaderPrivileged());
246             }
247             bundles.put(bundleName, messages);
248         }
249         return messages;
250     }
251 
252     /*** Assert resources available
253      * @since 1.1
254      * @throws JDOFatalInternalException if the resource bundle could not
255      * be loaded during construction.
256      */
257     private void assertBundle () {
258         if (failure != null)
259             throw new JDOFatalInternalException (
260                 "No resources could be found for bundle:\"" + 
261                 bundle + "\" ", failure);
262     }
263     
264     /*** Assert resources available
265      * @param key the message key 
266      * @since 1.0.2
267      * @throws JDOFatalInternalException if the resource bundle could not
268      * be loaded during construction.
269      */
270     private void assertBundle (String key) {
271         if (failure != null)
272             throw new JDOFatalInternalException (
273                 "No resources could be found to annotate error message key:\"" + 
274                 key + "\"", failure);
275     }
276 
277     /***
278      * Returns message as <code>String</code>
279      * @param messages the resource bundle
280      * @param messageKey the message key
281      * @return the resolved message text
282      */
283     final private static String getMessage(ResourceBundle messages, String messageKey) 
284     {
285         return messages.getString(messageKey);
286     }
287 
288     /***
289      * Formats message by adding array of arguments
290      * @param messages the resource bundle
291      * @param messageKey the message key
292      * @param msgArgs an array of arguments to substitute into the message
293      * @return the resolved message text
294      */
295     final private static String getMessage(ResourceBundle messages, 
296             String messageKey, Object[] msgArgs) 
297     {
298         for (int i=0; i<msgArgs.length; i++) {
299             if (msgArgs[i] == null) msgArgs[i] = ""; // NOI18N
300         }
301         MessageFormat formatter = new MessageFormat(messages.getString(messageKey));
302         return formatter.format(msgArgs);
303     }
304     
305     /***
306      * Formats message by adding an <code>Object</code> argument.
307      * @param messages the resource bundle
308      * @param messageKey the message key
309      * @param arg the argument
310      * @return the resolved message text
311      */
312     final private static String getMessage(ResourceBundle messages, 
313             String messageKey, Object arg) 
314     {
315         Object []args = {arg};
316         return getMessage(messages, messageKey, args);
317     }
318     
319     /***
320      * Formats message by adding two <code>Object</code> arguments.
321      * @param messages the resource bundle
322      * @param messageKey the message key
323      * @param arg1 the first argument
324      * @param arg2 the second argument
325      * @return the resolved message text
326      */
327     final private static String getMessage(ResourceBundle messages, 
328             String messageKey, Object arg1, Object arg2) 
329     {
330         Object []args = {arg1, arg2};
331         return getMessage(messages, messageKey, args);
332     }
333     
334     /***
335      * Formats message by adding three <code>Object</code> arguments.
336      * @param messages the resource bundle
337      * @param messageKey the message key
338      * @param arg1 the first argument
339      * @param arg2 the second argument
340      * @param arg3 the third argument
341      * @return the resolved message text
342      */
343     final private static String getMessage(ResourceBundle messages, 
344             String messageKey, Object arg1, Object arg2, Object arg3) 
345     {
346         Object []args = {arg1, arg2, arg3};
347         return getMessage(messages, messageKey, args);
348     }
349 
350     /***
351      * Formats message by adding an <code>int</code> as an argument.
352      * @param messages the resource bundle
353      * @param messageKey the message key
354      * @param arg the argument
355      * @return the resolved message text
356      */
357     final private static String getMessage(ResourceBundle messages, 
358             String messageKey, int arg) 
359     {
360         Object []args = {new Integer(arg)};
361         return getMessage(messages, messageKey, args);
362     }
363     
364     /***
365      * Formats message by adding a <code>boolean</code> as an argument.
366      * @param messages the resource bundle
367      * @param messageKey the message key
368      * @param arg the argument
369      * @return the resolved message text
370      */
371     final private static String getMessage(ResourceBundle messages, 
372             String messageKey, boolean arg) 
373     {
374         Object []args = {String.valueOf(arg)};
375         return getMessage(messages, messageKey, args);
376     }
377 
378     /***  
379      * Returns the package portion of the specified class.
380      * @param className the name of the class from which to extract the 
381      * package 
382      * @return package portion of the specified class
383      */   
384     final private static String getPackageName(final String className)
385     { 
386         final int index = className.lastIndexOf('.');
387         return ((index != -1) ? className.substring(0, index) : ""); // NOI18N
388     }
389 
390     /***
391      * Get the system class loader. This must be done in a doPrivileged 
392      * block because of security.
393      */
394     private static ClassLoader getSystemClassLoaderPrivileged() {
395         return (ClassLoader) AccessController.doPrivileged (
396             new PrivilegedAction () {
397                 public Object run () {
398                     return ClassLoader.getSystemClassLoader();
399                 }
400             }
401         );
402     }
403 }