KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > commons > cbutil > CBResourceBundle


1 package com.ca.commons.cbutil;
2
3 import java.net.URL JavaDoc;
4 import java.util.*;
5 import java.util.logging.Level JavaDoc;
6 import java.util.logging.Logger JavaDoc;
7
8 /**
9  * Java's PropertyResourceBundle class is tragically
10  * bad. Why a class intended for i18n use deliberately
11  * restricts itself to 8859-1 characters, while using
12  * a bizarre unicode escaping format rather than utf8,
13  * is beyond me (and
14  * certainly beyond any translators I need to send
15  * stuff to.)<p>
16  * <p/>
17  * This class is a reimplementation that automatically
18  * selects whether a file is 16bit unicode, utf-8, or
19  * local character encoding, and loads it accordingly.
20  * Otherwise it is intended to be functionally very
21  * similar to PropertiesResourceBundle.<p>
22  * <p/>
23  * Note that this class does <i>not</i> extend ResourceBundle,
24  * as ResourceBundle is difficult to
25  * extend - all the functionality is hidden away
26  * in private methods. So even though ResourceBundle
27  * does some neat things, we won't be doing anything but a
28  * bare bones rewrite here.
29  * <p/>
30  * <B>Important:</B> This uses a simplified form of the properties
31  * file - keys and phrases are separated by an '=' sign, and while,
32  * for backward compatibility, keys <i>can</i> have escaped characters,
33  * except for '=', they don't have to. A further restriction is that
34  * the '=' sign <i>may not</i> be immediately preceeded by an escaped
35  * escape character (i.e. '\\=' is illegal) - the '=' sign must be
36  * preceeded by a space character in this case. Any space characters
37  * at the start and end of a key/description are trimmed.<p>
38  * <p/>
39  * e.g. [values in square brackets represent byte values within the file]
40  * <p/>
41  * potato = kartoffel // normal ascii (german)
42  * help = [30 d8 30 eb 30d7] // 16 bit unicode (japanese)
43  * file = [e6 96 87 e6 a1 a3] // utf-8 (chinese)
44  */

45
46 public class CBResourceBundle
47 {
48     Hashtable translations = new Hashtable();
49
50     private static Logger JavaDoc log = Logger.getLogger(CBResourceBundle.class.getName());
51
52     /**
53      * This creates a Resource bundle using only the name of the
54      * the resource bundle (e.g. "language.JX"). It then uses
55      * the default locale and resource loader to track down the
56      * appropriate translation file.
57      *
58      * @param baseName the name of the translation file to look up.
59      * this name is extended using the standard locality rules
60      * to try to find localised files (e.g. "language.JX" becomes
61      * language/JX_fr_CA in french-speaking canada).
62      */

63
64     public CBResourceBundle(String JavaDoc baseName)
65     {
66         loadBundle(baseName, Locale.getDefault(), ClassLoader.getSystemClassLoader());
67     }
68
69     /**
70      * This creates a Resource bundle the name of the
71      * the resource bundle (e.g. "language.JX"). It then uses
72      * the specified locale to track down the
73      * appropriate translation file.
74      *
75      * @param baseName the name of the translation file to look up.
76      * this name is extended using the standard locality rules
77      * to try to find localised files (e.g. "language.JX" becomes
78      * language/JX_fr_CA in french-speaking canada).
79      * @param locale a specific locale to use in place of the default
80      * system locale.
81      */

82
83     public CBResourceBundle(String JavaDoc baseName, Locale locale)
84     {
85         loadBundle(baseName, locale, ClassLoader.getSystemClassLoader());
86     }
87
88
89     /**
90      * This creates a Resource bundle the name of the
91      * the resource bundle (e.g. "language.JX"). It then uses
92      * the specified locale to track down the
93      * appropriate translation file, and teh specified class loader
94      * to retrieve the file.
95      *
96      * @param baseName the name of the translation file to look up.
97      * this name is extended using the standard locality rules
98      * to try to find localised files (e.g. "language.JX" becomes
99      * language/JX_fr_CA in french-speaking canada).
100      * @param locale a specific locale to use in place of the default
101      * system locale.
102      * @param loader a custom class loader (such as CBClassLoader) used to
103      * retrieve the translation file.
104      */

105
106     public CBResourceBundle(String JavaDoc baseName, Locale locale, ClassLoader JavaDoc loader)
107     {
108         loadBundle(baseName, locale, loader);
109     }
110
111     /**
112      * This method searches through all the valid permutations of the base
113      * bundle name (modified for locale - e.g. JX_fr_CA.properties, JX_fr.properties,
114      * and JX.properties...). <p>
115      * If successful, it loads the data (the translation strings) into a local
116      * data store (Hashtable).
117      *
118      * @param baseName the name of the translation file to look up.
119      * this name is extended using the standard locality rules
120      * to try to find localised files (e.g. "language.JX" becomes
121      * language/JX_fr_CA in french-speaking canada).
122      * @param locale a specific locale to use in place of the default
123      * system locale.
124      * @param loader a custom class loader (such as CBClassLoader) used to
125      * retrieve the translation file.
126      */

127     protected void loadBundle(String JavaDoc baseName, Locale locale, ClassLoader JavaDoc loader)
128     {
129         Vector names = getBundleNames(baseName, locale);
130         for (int i = names.size() - 1; i >= 0; i--)
131         {
132             URL JavaDoc url = loader.getResource(names.get(i).toString()); // XXX why getResource, not findResource???
133
if (loadData(url) == true)
134                 return; // once a single file has been loaded, we're done.
135
}
136         
137         // couldn't succesfully load anything...
138
log.warning("unable to load resource bundle '" + baseName + "'");
139     }
140
141     /**
142      * Calculate the bundles along the search path from the base bundle to the
143      * bundle specified by baseName and locale.
144      *
145      * @param baseName the base bundle name
146      * @param locale the locale
147      * the search path.
148      */

149
150     protected static Vector getBundleNames(String JavaDoc baseName, Locale locale)
151     {
152         final Vector result = new Vector(8);
153         final String JavaDoc language = locale.getLanguage();
154         final int languageLength = language.length();
155         final String JavaDoc country = locale.getCountry();
156         final int countryLength = country.length();
157         final String JavaDoc variant = locale.getVariant();
158         final int variantLength = variant.length();
159
160         if (baseName.toLowerCase().endsWith(".properties"))
161         {
162             baseName = baseName.substring(baseName.length() - 11);
163         }
164
165         baseName = baseName.replace('.', '/'); // note forward slash used, rather than File.separator, for jar access etc.
166
final StringBuffer JavaDoc temp = new StringBuffer JavaDoc(baseName);
167
168         result.addElement(temp.toString() + ".properties");
169         result.addElement(temp.toString());
170
171         if (languageLength + countryLength + variantLength == 0)
172         {
173             return result; //The locale is "", "", "".
174
}
175
176         temp.append('_');
177         temp.append(language);
178
179         result.addElement(temp.toString() + ".properties");
180         result.addElement(temp.toString());
181
182         if (countryLength + variantLength == 0)
183         {
184             return result;
185         }
186
187         temp.append('_');
188         temp.append(country);
189
190         result.addElement(temp.toString() + ".properties");
191         result.addElement(temp.toString());
192
193         if (variantLength == 0)
194         {
195             return result;
196         }
197
198         temp.append('_');
199         temp.append(variant);
200
201         result.addElement(temp.toString() + ".properties");
202         result.addElement(temp.toString());
203
204         return result;
205     }
206
207
208     /**
209      * This loads the data from a translation file, checking on the way
210      * what file format it is in. (UTF-8, 16bit unicode, or local encoding).
211      *
212      * @param url the URL to read the data InputStream from.
213      * @return whether the load data operation was successfull, or whether
214      * it was interupted (for whatever reason; no file, error reading
215      * file, bad encoding, yadda yadda yadda).
216      */

217
218     protected boolean loadData(URL JavaDoc url)
219     {
220         if (url == null) return false; // can't read from a null url!
221

222         log.finer("Resource Bundle Reading data from " + ((url == null) ? "null url" : url.toString()));
223
224         try
225         {
226             /*
227              * First, slurp all the data from the input stream into a byte array.
228              */

229             byte[] data = CBUtility.readStream(url.openStream());
230             
231             /*
232              * Convert the byte array to a String using cunning auto-detecting
233              * encoding methods.
234              */

235              
236             String JavaDoc text = CBUtility.readI18NByteArray(data);
237             
238             /*
239              * Load up the translations hashtable with the parsed data found
240              * in the string...
241              */

242
243             return parseData(text);
244         }
245         catch (Exception JavaDoc e)
246         {
247             log.log(Level.FINER, "Unable to read data from url: " + ((url == null) ? "(null url)" : url.toString()), e);
248             return false;
249         }
250     }
251
252     /**
253      * parses the byte array as per a normal resource file
254      * (i.e. looking for key/data pairs seperated by an
255      * unescaped '=' sign) after first converting the byte
256      * array into a String, using whichever language encoding
257      * (unicode16, utf8, locale-specific) seems appropriate.
258      */

259
260     protected boolean parseData(String JavaDoc text)
261     {
262         int startSize = translations.size();
263
264         int start = 0, end = 0;
265         while ((end = text.indexOf('\n', start)) != -1)
266         {
267             String JavaDoc line = text.substring(start, end);
268
269
270             line = line.trim();
271
272             if (line.length() != 0 && line.charAt(0) != '#') // ignore blank lines and commented lines.
273
{
274                 try
275                 {
276                     int equalPos = 0;
277
278                     do // skip through all escaped equals characters until we find a non-escaped one.
279
{
280                         equalPos = line.indexOf('=', equalPos + 1);
281                     }
282                     while (line.charAt(equalPos - 1) == '\\');
283
284                     String JavaDoc key = unescape(line.substring(0, equalPos)).trim();
285                     String JavaDoc trans = line.substring(equalPos + 1).trim();
286                     translations.put(key, trans);
287
288                 }
289                 catch (Exception JavaDoc e)
290                 {
291                     log.log(Level.FINER, "Exception parsing data line '" + line, e);
292                 } // prob. array ex. - ignore this line.
293
}
294
295             start = end + 1;
296         }
297         
298         // check if we added any new translations - if we did, then this was
299
// at least partially successfull.
300
boolean success = (startSize < translations.size());
301         if (success == false)
302             log.finer("ParseData unsuccessfull - no new data found");
303         return success;
304
305     }
306
307     /**
308      * Removes all escapes ('\?' -> '?') from a string.
309      * -> Not particularly efficient, but o.k. for short strings.
310      */

311
312     protected String JavaDoc unescape(String JavaDoc escapeMe)
313     {
314         int pos = 0;
315         while ((pos = escapeMe.indexOf('\\', pos)) >= 0)
316             escapeMe = escapeMe.substring(0, pos) + escapeMe.substring(pos + 1);
317
318         return escapeMe;
319     }
320
321     /**
322      * returns the translation keys.
323      *
324      * @return an Enumeration of all the known keys (usually translatable
325      * strings).
326      */

327
328     public Enumeration keys()
329     {
330         return translations.keys();
331     }
332
333     /**
334      * returns the translation keys. Synonym for 'keys()', kept
335      * for compatibility with ResourceBundle.
336      *
337      * @return an Enumeration of all the known keys (usually translatable
338      * strings).
339      */

340
341     public Enumeration getKeys()
342     {
343         return translations.keys();
344     }
345
346     /**
347      * Returns the object corresponding to a given key.
348      *
349      * @param key the original text to translate/look up
350      * @return the corresponding translation/object
351      */

352
353     public Object JavaDoc get(Object JavaDoc key)
354     {
355         return translations.get(key);
356     }
357
358     /**
359      * Returns the object corresponding to a given key. kept
360      * for compatibility with ResourceBundle.
361      *
362      * @param key the original text to translate/look up
363      * @return the corresponding translation/object
364      */

365
366     public Object JavaDoc getObject(Object JavaDoc key)
367     {
368         return translations.get(key);
369     }
370
371
372     /**
373      * Convenience class returning a particular object
374      * as a String. If the object <i>was</i> a String
375      * already it is passed back unchanged, otherwise
376      * 'toString()' is called on the object before returning.
377      * This class never throws a ClassCastException.
378      *
379      * @param key the original text to translate/look up
380      * @return the corresponding translation/object as a String
381      */

382
383     public String JavaDoc getString(String JavaDoc key)
384     {
385         if (key == null) return "";
386         Object JavaDoc o = translations.get(key);
387         if (o == null) return "";
388
389         return (o instanceof String JavaDoc) ? (String JavaDoc) o : o.toString();
390     }
391
392
393 }
Popular Tags