KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opencms > i18n > CmsResourceBundleLoader


1 /*
2  * File : $Source: /usr/local/cvs/opencms/src/org/opencms/i18n/CmsResourceBundleLoader.java,v $
3  * Date : $Date: 2006/03/27 14:53:01 $
4  * Version: $Revision: 1.2 $
5  *
6  * This library is part of OpenCms -
7  * the Open Source Content Mananagement System
8  *
9  * Copyright (C) 2002 - 2005 Alkacon Software (http://www.alkacon.com)
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library 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 GNU
19  * Lesser General Public License for more details.
20  *
21  * For further information about Alkacon Software, please see the
22  * company website: http://www.alkacon.com
23  *
24  * For further information about OpenCms, please see the
25  * project website: http://www.opencms.org
26  *
27  * You should have received a copy of the GNU Lesser General Public
28  * License along with this library; if not, write to the Free Software
29  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30  */

31
32 package org.opencms.i18n;
33
34 import org.opencms.util.CmsFileUtil;
35
36 import java.io.File JavaDoc;
37 import java.io.FileInputStream JavaDoc;
38 import java.io.IOException JavaDoc;
39 import java.io.InputStream JavaDoc;
40 import java.net.URL JavaDoc;
41 import java.util.HashMap JavaDoc;
42 import java.util.Locale JavaDoc;
43 import java.util.Map JavaDoc;
44 import java.util.MissingResourceException JavaDoc;
45 import java.util.ResourceBundle JavaDoc;
46
47 /**
48  * Resource bundle loader for property based resource bundles from OpenCms that has a flushable cache.<p>
49  *
50  * The main reason for implementing this is that the Java default resource bundle loading mechanism
51  * provided by {@link java.util.ResourceBundle#getBundle(java.lang.String, java.util.Locale)} uses a
52  * cache that can NOT be flushed by any standard means. This means for every simple change in a resource
53  * bundle, the Java VM (and the webapp container that runs OpenCms) must be restarted.
54  * This non-standard resource bundle loader avoids this by providing a flushable cache.<p>
55  *
56  * In case the requersted bundle can not be found, a fallback mechanism to
57  * {@link java.util.ResourceBundle#getBundle(java.lang.String, java.util.Locale)} is used to look up
58  * the resource bundle with the Java default resource bundle loading mechanism.<p>
59  *
60  * @see java.util.ResourceBundle
61  * @see java.util.PropertyResourceBundle
62  * @see org.opencms.i18n.CmsResourceBundle
63  *
64  * @author Alexander Kandzior
65  *
66  * @version $Revision: 1.2 $
67  *
68  * @since 6.2.0
69  */

70 public final class CmsResourceBundleLoader {
71
72     /**
73      * Cache key for the ResourceBundle cache.<p>
74      *
75      * Resource bundles are keyed by the combination of bundle name, locale, and class loader.
76      */

77     private static class BundleKey {
78
79         private String JavaDoc m_baseName;
80         private int m_hashcode;
81         private Locale JavaDoc m_locale;
82
83         /**
84          * Create an ampty bundly key.<p>
85          */

86         BundleKey() {
87
88             // noop
89
}
90
91         /**
92          * Create an initialized bundle key.<p>
93          *
94          * @param s the base name
95          * @param l the locale
96          */

97         BundleKey(String JavaDoc s, Locale JavaDoc l) {
98
99             set(s, l);
100         }
101
102         /**
103          * @see java.lang.Object#equals(java.lang.Object)
104          */

105         public boolean equals(Object JavaDoc o) {
106
107             if (!(o instanceof BundleKey)) {
108                 return false;
109             }
110             BundleKey key = (BundleKey)o;
111             return m_hashcode == key.m_hashcode && m_baseName.equals(key.m_baseName) && m_locale.equals(key.m_locale);
112         }
113
114         /**
115          * @see java.lang.Object#hashCode()
116          */

117         public int hashCode() {
118
119             return m_hashcode;
120         }
121
122         /**
123          * @see java.lang.Object#toString()
124          */

125         public String JavaDoc toString() {
126
127             return m_baseName + "_" + m_locale;
128         }
129
130         /**
131          * Initialize this bundle key.<p>
132          *
133          * @param s the base name
134          * @param l the locale
135          */

136         void set(String JavaDoc s, Locale JavaDoc l) {
137
138             m_baseName = s;
139             m_locale = l;
140             m_hashcode = m_baseName.hashCode() ^ m_locale.hashCode();
141         }
142     }
143
144     /** The resource bundle cache. */
145     private static Map JavaDoc m_bundleCache;
146
147     /** The last default Locale we saw, if this ever changes then we have to reset our caches. */
148     private static Locale JavaDoc m_lastDefaultLocale;
149
150     /** Cache lookup key to avoid having to a new one for every getBundle() call. */
151     private static BundleKey m_lookupKey = new BundleKey();
152
153     /** Singleton cache entry to represent previous failed lookups. */
154     private static final Object JavaDoc NULL_ENTRY = new Object JavaDoc();
155
156     /**
157      * Hides the public constructor.<p>
158      */

159     private CmsResourceBundleLoader() {
160
161         // noop
162
}
163
164     /**
165      * Flushes the resource bundle cache.<p>
166      */

167     public static synchronized void flushBundleCache() {
168
169         m_bundleCache = new HashMap JavaDoc();
170     }
171
172     /**
173      * Get the appropriate ResourceBundle for the given locale. The following
174      * strategy is used:
175      *
176      * <p>A sequence of candidate bundle names are generated, and tested in
177      * this order, where the suffix 1 means the string from the specified
178      * locale, and the suffix 2 means the string from the default locale:</p>
179      *
180      * <ul>
181      * <li>baseName + "_" + language1 + "_" + country1 + "_" + variant1</li>
182      * <li>baseName + "_" + language1 + "_" + country1</li>
183      * <li>baseName + "_" + language1</li>
184      * <li>baseName + "_" + language2 + "_" + country2 + "_" + variant2</li>
185      * <li>baseName + "_" + language2 + "_" + country2</li>
186      * <li>baseName + "_" + language2</li>
187      * <li>baseName</li>
188      * </ul>
189      *
190      * <p>In the sequence, entries with an empty string are ignored. Next,
191      * <code>getBundle</code> tries to instantiate the resource bundle:</p>
192      *
193      * <ul>
194      * <li>This implementation only resolves property based resource bundles.
195      * Class based resource bundles are nor found.</li>
196      * <li>A search is made for a property resource file, by replacing
197      * '.' with '/' and appending ".properties", and using
198      * ClassLoader.getResource(). If a file is found, then a
199      * PropertyResourceBundle is created from the file's contents.</li>
200      * </ul>
201      *
202      * <p>If no resource bundle was found, the default resource bundle loader
203      * is used to look for the resource bundle. Class based resource bundles
204      * will be found now.<p>
205      *
206      * @param baseName the name of the ResourceBundle
207      * @param locale A locale
208      * @return the desired resource bundle
209      */

210     // This method is synchronized so that the cache is properly
211
// handled.
212
public static synchronized ResourceBundle JavaDoc getBundle(String JavaDoc baseName, Locale JavaDoc locale) {
213
214         // If the default locale changed since the last time we were called,
215
// all cache entries are invalidated.
216
Locale JavaDoc defaultLocale = Locale.getDefault();
217         if (defaultLocale != m_lastDefaultLocale) {
218             m_bundleCache = new HashMap JavaDoc();
219             m_lastDefaultLocale = defaultLocale;
220         }
221
222         // This will throw NullPointerException if any arguments are null.
223
m_lookupKey.set(baseName, locale);
224
225         Object JavaDoc obj = m_bundleCache.get(m_lookupKey);
226
227         if (obj instanceof CmsResourceBundle) {
228             return (CmsResourceBundle)obj;
229         } else if (obj == NULL_ENTRY) {
230             // Lookup has failed previously. Fall through.
231
} else {
232             // First, look for a bundle for the specified locale. We don't want
233
// the base bundle this time.
234
boolean wantBase = locale.equals(defaultLocale);
235             CmsResourceBundle bundle = tryBundle(baseName, locale, wantBase);
236
237             // Try the default locale if neccessary.
238
if (bundle == null && !locale.equals(defaultLocale)) {
239                 bundle = tryBundle(baseName, defaultLocale, true);
240             }
241
242             BundleKey key = new BundleKey(baseName, locale);
243             if (bundle == null) {
244                 // Cache the fact that this lookup has previously failed.
245
m_bundleCache.put(key, NULL_ENTRY);
246             } else {
247                 // Cache the result and return it.
248
m_bundleCache.put(key, bundle);
249                 return bundle;
250             }
251         }
252
253         // unable to find the resource bundle with this implementation
254
// use default Java mechanism to look up the bundle again
255
return ResourceBundle.getBundle(baseName, locale);
256     }
257
258     /**
259      * Tries to load a property file with the specified name.
260      *
261      * @param localizedName the name
262      * @return the resource bundle if it was loaded, otherwise the backup
263      */

264     private static CmsResourceBundle tryBundle(String JavaDoc localizedName) {
265
266         CmsResourceBundle bundle = null;
267
268         try {
269
270             InputStream JavaDoc is = null;
271             String JavaDoc resourceName = localizedName.replace('.', '/') + ".properties";
272             URL JavaDoc url = CmsResourceBundleLoader.class.getClassLoader().getResource(resourceName);
273
274             if (url != null) {
275                 String JavaDoc path = CmsFileUtil.normalizePath(url);
276                 File JavaDoc file = new File JavaDoc(path);
277                 try {
278                     // try to load the resource bundle from a file, NOT with the resource loader first
279
// this is important since using #getResourceAsStream() may return cached results,
280
// for example Tomcat by default does cache all resources loaded by the class loader
281
// this means a changed resource bundle file is not loaded
282
is = new FileInputStream JavaDoc(file);
283                 } catch (IOException JavaDoc ex) {
284                     // this will happen if the resource is contained for example in a .jar file
285
is = CmsResourceBundleLoader.class.getClassLoader().getResourceAsStream(resourceName);
286                 }
287             }
288             if (is != null) {
289                 bundle = new CmsResourceBundle(is);
290             }
291         } catch (IOException JavaDoc ex) {
292             // can't localized these message since this may lead to a chicken-egg problem
293
MissingResourceException JavaDoc mre = new MissingResourceException JavaDoc(
294                 "Failed to load bundle '" + localizedName + "'",
295                 localizedName,
296                 "");
297             mre.initCause(ex);
298             throw mre;
299         }
300
301         return bundle;
302     }
303
304     /**
305      * Tries to load a the bundle for a given locale, also loads the backup
306      * locales with the same language.
307      *
308      * @param baseName the raw bundle name, without locale qualifiers
309      * @param locale the locale
310      * @param wantBase whether a resource bundle made only from the base name
311      * (with no locale information attached) should be returned.
312      * @return the resource bundle if it was loaded, otherwise the backup
313      */

314     private static CmsResourceBundle tryBundle(String JavaDoc baseName, Locale JavaDoc locale, boolean wantBase) {
315
316         String JavaDoc language = locale.getLanguage();
317         String JavaDoc country = locale.getCountry();
318         String JavaDoc variant = locale.getVariant();
319
320         int baseLen = baseName.length();
321
322         // Build up a StringBuffer containing the complete bundle name, fully
323
// qualified by locale.
324
StringBuffer JavaDoc sb = new StringBuffer JavaDoc(baseLen + variant.length() + 7);
325
326         sb.append(baseName);
327
328         if (language.length() > 0) {
329             sb.append('_');
330             sb.append(language);
331
332             if (country.length() > 0) {
333                 sb.append('_');
334                 sb.append(country);
335
336                 if (variant.length() > 0) {
337                     sb.append('_');
338                     sb.append(variant);
339                 }
340             }
341         }
342
343         // Now try to load bundles, starting with the most specialized name.
344
// Build up the parent chain as we go.
345
String JavaDoc bundleName = sb.toString();
346         CmsResourceBundle first = null; // The most specialized bundle.
347
CmsResourceBundle last = null; // The least specialized bundle.
348

349         while (true) {
350             CmsResourceBundle foundBundle = tryBundle(bundleName);
351             if (foundBundle != null) {
352                 if (first == null) {
353                     first = foundBundle;
354                 }
355
356                 if (last != null) {
357                     last.setParent(foundBundle);
358                 }
359                 foundBundle.setLocale(locale);
360
361                 last = foundBundle;
362             }
363             int idx = bundleName.lastIndexOf('_');
364             // Try the non-localized base name only if we already have a
365
// localized child bundle, or wantBase is true.
366
if (idx > baseLen || (idx == baseLen && (first != null || wantBase))) {
367                 bundleName = bundleName.substring(0, idx);
368             } else {
369                 break;
370             }
371         }
372
373         return first;
374     }
375 }
Popular Tags