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<String,ResourceBundle>
53              bundles = new Hashtable<String,ResourceBundle>();
54      
55      /** Helper instances that have already been created 
56       */
57      private static Hashtable<String,I18NHelper>
58              helpers = new Hashtable<String,I18NHelper>();
59      
60      /** The default locale for this VM.
61       */
62      private static Locale       locale = Locale.getDefault();
63  
64      /** The bundle used by this instance of the helper.
65       */
66      private ResourceBundle      bundle = null;
67  
68      /** Throwable if ResourceBundle couldn't be loaded
69       */
70      private Throwable           failure = null;
71  
72      /** The unqualified standard name of a bundle. */
73      private static final String bundleSuffix = ".Bundle";    // NOI18N
74  
75      /** Constructor */
76      private I18NHelper() {
77      }
78  
79      /** Constructor for an instance bound to a bundle.
80       * @param bundleName the name of the resource bundle
81       * @param loader the class loader from which to load the resource
82       * bundle
83       */
84      private I18NHelper (String bundleName, ClassLoader loader) {
85          try {
86              bundle = loadBundle (bundleName, loader);
87          }
88          catch (Throwable e) {
89              failure = e;
90          }
91      }
92      
93      /** An instance bound to a bundle. This method uses the current class 
94       * loader to find the bundle.
95       * @param bundleName the name of the bundle
96       * @return the helper instance bound to the bundle
97       */
98      public static I18NHelper getInstance (String bundleName) {
99          return getInstance (bundleName, I18NHelper.class.getClassLoader());
100     }
101 
102     /** An instance bound to a bundle. This method figures out the bundle name
103      * for the class object's package and uses the class' class loader to
104      * find the bundle. Note, the specified class object must not be
105      * <code>null</code>.
106      * @param cls the class object from which to load the resource bundle
107      * @return the helper instance bound to the bundle
108      */
109     public static I18NHelper getInstance (final Class cls) {
110         ClassLoader classLoader = AccessController.doPrivileged (
111             new PrivilegedAction<ClassLoader> () {
112                 public ClassLoader run () {
113                     return cls.getClassLoader();
114                 }
115             }
116             );
117         String bundle = getPackageName (cls.getName()) + bundleSuffix;
118         return getInstance (bundle, classLoader);
119     }
120 
121     /** An instance bound to a bundle. This method uses the specified class
122      * loader to find the bundle. Note, the specified class loader must not
123      * be <code>null</code>.
124      * @param bundleName the name of the bundle
125      * @param loader the class loader from which to load the resource
126      * bundle
127      * @return the helper instance bound to the bundle
128      */
129     public static I18NHelper getInstance (String bundleName, 
130                                           ClassLoader loader) {
131         I18NHelper helper = (I18NHelper) helpers.get (bundleName);
132         if (helper != null) {
133             return helper;
134         }
135         helper = new I18NHelper(bundleName, loader);
136         helpers.put (bundleName, helper);
137         // if two threads simultaneously create the same helper, return the first
138         // one to be put into the Hashtable.  The other will be garbage collected.
139         return (I18NHelper) helpers.get (bundleName);
140     }
141 
142     /** Message formatter
143      * @param messageKey the message key
144      * @return the resolved message text
145      */
146     public String msg (String messageKey) {
147         assertBundle (messageKey);
148         return getMessage (bundle, messageKey);
149     }
150 
151     /** Message formatter
152      * @param messageKey the message key
153      * @param arg1 the first argument
154      * @return the resolved message text
155      */
156     public String msg (String messageKey, Object arg1) {
157         assertBundle (messageKey);
158         return getMessage (bundle, messageKey, arg1);
159     }
160 
161     /** Message formatter
162      * @param messageKey the message key
163      * @param arg1 the first argument
164      * @param arg2 the second argument
165      * @return the resolved message text
166      */
167     public String msg (String messageKey, Object arg1, Object arg2) {
168         assertBundle (messageKey);
169         return getMessage (bundle, messageKey, arg1, arg2);
170     }
171 
172     /** Message formatter
173      * @param messageKey the message key
174      * @param arg1 the first argument
175      * @param arg2 the second argument
176      * @param arg3 the third argument
177      * @return the resolved message text
178      */
179     public String msg (String messageKey, Object arg1, Object arg2, Object arg3) {
180         assertBundle (messageKey);
181         return getMessage (bundle, messageKey, arg1, arg2, arg3);
182     }
183 
184     /** Message formatter
185      * @param messageKey the message key
186      * @param args the array of arguments
187      * @return the resolved message text
188      */
189     public String msg (String messageKey, Object[] args) {
190         assertBundle (messageKey);
191         return getMessage (bundle, messageKey, args);
192     }
193 
194     /** Message formatter
195      * @param messageKey the message key
196      * @param arg the argument
197      * @return the resolved message text
198      */
199     public String msg (String messageKey, int arg) {
200         assertBundle (messageKey);
201         return getMessage(bundle, messageKey, arg);
202     }
203     
204     /** Message formatter
205      * @param messageKey the message key
206      * @param arg the argument
207      * @return the resolved message text
208      */
209     public String msg (String messageKey, boolean arg) {
210         assertBundle (messageKey);
211         return getMessage(bundle, messageKey, arg);
212     }
213     
214     /** Returns the resource bundle used by this I18NHelper.
215      * @return the associated resource bundle
216      * @since 1.1
217      */
218     public ResourceBundle getResourceBundle () {
219         assertBundle ();
220         return bundle;
221     }
222     
223     //========= Internal helper methods ==========
224 
225     /**
226      * Load ResourceBundle by bundle name
227      * @param bundleName the name of the bundle
228      * @param loader the class loader from which to load the resource bundle
229      * @return  the ResourceBundle
230      */
231     final private static ResourceBundle loadBundle(
232         String bundleName, ClassLoader loader) {
233         ResourceBundle messages = (ResourceBundle)bundles.get(bundleName);
234 
235         if (messages == null) //not found as loaded - add
236         {
237             if (loader != null) {
238                 messages = ResourceBundle.getBundle(bundleName, locale, loader);
239             } else {
240                 // the JDO library is loaded by the boostrap class loader
241                 messages = ResourceBundle.getBundle(bundleName, locale,
242                         getSystemClassLoaderPrivileged());
243             }
244             bundles.put(bundleName, messages);
245         }
246         return messages;
247     }
248 
249     /** Assert resources available
250      * @since 1.1
251      * @throws JDOFatalInternalException if the resource bundle could not
252      * be loaded during construction.
253      */
254     private void assertBundle () {
255         if (failure != null)
256             throw new JDOFatalInternalException (
257                 "No resources could be found for bundle:\"" + 
258                 bundle + "\" ", failure);
259     }
260     
261     /** Assert resources available
262      * @param key the message key 
263      * @since 1.0.2
264      * @throws JDOFatalInternalException if the resource bundle could not
265      * be loaded during construction.
266      */
267     private void assertBundle (String key) {
268         if (failure != null)
269             throw new JDOFatalInternalException (
270                 "No resources could be found to annotate error message key:\"" + 
271                 key + "\"", failure);
272     }
273 
274     /**
275      * Returns message as <code>String</code>
276      * @param messages the resource bundle
277      * @param messageKey the message key
278      * @return the resolved message text
279      */
280     final private static String getMessage(ResourceBundle messages, String messageKey) 
281     {
282         return messages.getString(messageKey);
283     }
284 
285     /**
286      * Formats message by adding array of arguments
287      * @param messages the resource bundle
288      * @param messageKey the message key
289      * @param msgArgs an array of arguments to substitute into the message
290      * @return the resolved message text
291      */
292     final private static String getMessage(ResourceBundle messages, 
293             String messageKey, Object[] msgArgs) 
294     {
295         for (int i=0; i<msgArgs.length; i++) {
296             if (msgArgs[i] == null) msgArgs[i] = ""; // NOI18N
297         }
298         MessageFormat formatter = new MessageFormat(messages.getString(messageKey));
299         return formatter.format(msgArgs);
300     }
301     
302     /**
303      * Formats message by adding an <code>Object</code> argument.
304      * @param messages the resource bundle
305      * @param messageKey the message key
306      * @param arg the argument
307      * @return the resolved message text
308      */
309     final private static String getMessage(ResourceBundle messages, 
310             String messageKey, Object arg) 
311     {
312         Object []args = {arg};
313         return getMessage(messages, messageKey, args);
314     }
315     
316     /**
317      * Formats message by adding two <code>Object</code> arguments.
318      * @param messages the resource bundle
319      * @param messageKey the message key
320      * @param arg1 the first argument
321      * @param arg2 the second argument
322      * @return the resolved message text
323      */
324     final private static String getMessage(ResourceBundle messages, 
325             String messageKey, Object arg1, Object arg2) 
326     {
327         Object []args = {arg1, arg2};
328         return getMessage(messages, messageKey, args);
329     }
330     
331     /**
332      * Formats message by adding three <code>Object</code> arguments.
333      * @param messages the resource bundle
334      * @param messageKey the message key
335      * @param arg1 the first argument
336      * @param arg2 the second argument
337      * @param arg3 the third argument
338      * @return the resolved message text
339      */
340     final private static String getMessage(ResourceBundle messages, 
341             String messageKey, Object arg1, Object arg2, Object arg3) 
342     {
343         Object []args = {arg1, arg2, arg3};
344         return getMessage(messages, messageKey, args);
345     }
346 
347     /**
348      * Formats message by adding an <code>int</code> as an argument.
349      * @param messages the resource bundle
350      * @param messageKey the message key
351      * @param arg the argument
352      * @return the resolved message text
353      */
354     final private static String getMessage(ResourceBundle messages, 
355             String messageKey, int arg) 
356     {
357         Object []args = {new Integer(arg)};
358         return getMessage(messages, messageKey, args);
359     }
360     
361     /**
362      * Formats message by adding a <code>boolean</code> as an argument.
363      * @param messages the resource bundle
364      * @param messageKey the message key
365      * @param arg the argument
366      * @return the resolved message text
367      */
368     final private static String getMessage(ResourceBundle messages, 
369             String messageKey, boolean arg) 
370     {
371         Object []args = {String.valueOf(arg)};
372         return getMessage(messages, messageKey, args);
373     }
374 
375     /**  
376      * Returns the package portion of the specified class.
377      * @param className the name of the class from which to extract the 
378      * package 
379      * @return package portion of the specified class
380      */   
381     final private static String getPackageName(final String className)
382     { 
383         final int index = className.lastIndexOf('.');
384         return ((index != -1) ? className.substring(0, index) : ""); // NOI18N
385     }
386 
387     /**
388      * Get the system class loader. This must be done in a doPrivileged 
389      * block because of security.
390      */
391     private static ClassLoader getSystemClassLoaderPrivileged() {
392         return AccessController.doPrivileged (
393             new PrivilegedAction<ClassLoader> () {
394                 public ClassLoader run () {
395                     return ClassLoader.getSystemClassLoader();
396                 }
397             }
398         );
399     }
400 }