View Javadoc

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