Source code: org/apache/axis/i18n/ProjectResourceBundle.java
1 /*
2 * Copyright 2001-2004 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 org.apache.axis.i18n;
18
19 import org.apache.axis.components.logger.LogFactory;
20 import org.apache.commons.logging.Log;
21
22 import java.util.Enumeration;
23 import java.util.HashSet;
24 import java.util.Hashtable;
25 import java.util.Iterator;
26 import java.util.Locale;
27 import java.util.MissingResourceException;
28 import java.util.ResourceBundle;
29
30 /**
31 * <p>Wrapper class for resource bundles. Property files are used to store
32 * resource strings, which are the only types of resources available.
33 * Property files can inherit properties from other files so that
34 * a base property file can be used and a small number of properties
35 * can be over-ridden by another property file. For example you may
36 * create an english version of a resource file named "resource.properties".
37 * You then decide that the British English version of all of the properties
38 * except one are the same, so there is no need to redefine all of the
39 * properties in "resource_en_GB", just the one that is different.</p>
40 * <p>The basename is the name of the property file without the ".properties"
41 * extension.</p>
42 * <p>Properties will be cached for performance.<p>
43 * <p>Property values stored in the property files can also contain dynamic
44 * variables. Any dynamic variable defined in PropertiesUtil.getVariableValue()
45 * can be used (such as {date}), as well as arguments in the form {0}, {1}, etc.
46 * Argument values are specified in the various overloaded getString() methods.</p>
47 *
48 * @author Richard A. Sitze (rsitze@us.ibm.com)
49 * @author Karl Moss (kmoss@macromedia.com)
50 * @author Glen Daniels (gdaniels@apache.org)
51 */
52 public class ProjectResourceBundle extends ResourceBundle {
53 protected static Log log =
54 LogFactory.getLog(ProjectResourceBundle.class.getName());
55
56
57 // The static cache of ResourceBundles.
58 // The key is the 'basename + locale + default locale'
59 // The element is a ResourceBundle object
60 private static final Hashtable bundleCache = new Hashtable();
61
62 private static final Locale defaultLocale = Locale.getDefault();
63
64 private final ResourceBundle resourceBundle;
65 private final String resourceName;
66
67
68 protected Object handleGetObject(String key)
69 throws MissingResourceException
70 {
71 if (log.isDebugEnabled()) {
72 log.debug(this.toString() + "::handleGetObject(" + key + ")");
73 }
74 // return resourceBundle.handleGetObject(key);
75 Object obj;
76 try {
77 obj = resourceBundle.getObject(key);
78 } catch (MissingResourceException e) {
79 /* catch missing resource, ignore, & return null
80 * if this method doesn't return null, then parents
81 * are not searched
82 */
83 obj = null;
84 }
85 return obj;
86 }
87
88 public Enumeration getKeys() {
89 Enumeration myKeys = resourceBundle.getKeys();
90 if (parent == null) {
91 return myKeys;
92 } else {
93 final HashSet set = new HashSet();
94 while (myKeys.hasMoreElements()) {
95 set.add(myKeys.nextElement());
96 }
97
98 Enumeration pKeys = parent.getKeys();
99 while (pKeys.hasMoreElements()) {
100 set.add(pKeys.nextElement());
101 }
102
103 return new Enumeration() {
104 private Iterator it = set.iterator();
105 public boolean hasMoreElements() { return it.hasNext(); }
106 public Object nextElement() { return it.next(); }
107 };
108 }
109 }
110
111
112 /**
113 * Construct a new ProjectResourceBundle
114 *
115 * @param projectName The name of the project to which the class belongs.
116 * It must be a proper prefix of the caller's package.
117 *
118 * @param caller The calling class.
119 * This is used to get the package name to further construct
120 * the basename as well as to get the proper ClassLoader.
121 *
122 * @param resourceName The name of the resource without the
123 * ".properties" extension
124 *
125 * @throws MissingResourceException if projectName is not a prefix of
126 * the caller's package name, or if the resource could not be
127 * found/loaded.
128 */
129 public static ProjectResourceBundle getBundle(String projectName,
130 String packageName,
131 String resourceName)
132 throws MissingResourceException
133 {
134 return getBundle(projectName, packageName, resourceName, null, null, null);
135 }
136
137 /**
138 * Construct a new ProjectResourceBundle
139 *
140 * @param projectName The name of the project to which the class belongs.
141 * It must be a proper prefix of the caller's package.
142 *
143 * @param caller The calling class.
144 * This is used to get the package name to further construct
145 * the basename as well as to get the proper ClassLoader.
146 *
147 * @param resourceName The name of the resource without the
148 * ".properties" extension
149 *
150 * @throws MissingResourceException if projectName is not a prefix of
151 * the caller's package name, or if the resource could not be
152 * found/loaded.
153 */
154 public static ProjectResourceBundle getBundle(String projectName,
155 Class caller,
156 String resourceName,
157 Locale locale)
158 throws MissingResourceException
159 {
160 return getBundle(projectName,
161 caller,
162 resourceName,
163 locale,
164 null);
165 }
166
167 /**
168 * Construct a new ProjectResourceBundle
169 *
170 * @param projectName The name of the project to which the class belongs.
171 * It must be a proper prefix of the caller's package.
172 *
173 * @param caller The calling class.
174 * This is used to get the package name to further construct
175 * the basename as well as to get the proper ClassLoader.
176 *
177 * @param resourceName The name of the resource without the
178 * ".properties" extension
179 *
180 * @param locale The locale
181 *
182 * @throws MissingResourceException if projectName is not a prefix of
183 * the caller's package name, or if the resource could not be
184 * found/loaded.
185 */
186 public static ProjectResourceBundle getBundle(String projectName,
187 String packageName,
188 String resourceName,
189 Locale locale,
190 ClassLoader loader)
191 throws MissingResourceException
192 {
193 return getBundle(projectName, packageName, resourceName, locale, loader, null);
194 }
195
196 /**
197 * Construct a new ProjectResourceBundle
198 *
199 * @param projectName The name of the project to which the class belongs.
200 * It must be a proper prefix of the caller's package.
201 *
202 * @param caller The calling class.
203 * This is used to get the package name to further construct
204 * the basename as well as to get the proper ClassLoader.
205 *
206 * @param resourceName The name of the resource without the
207 * ".properties" extension
208 *
209 * @param locale The locale
210 *
211 * @param extendsBundle If non-null, then this ExtendMessages will
212 * default to extendsBundle.
213 *
214 * @throws MissingResourceException if projectName is not a prefix of
215 * the caller's package name, or if the resource could not be
216 * found/loaded.
217 */
218 public static ProjectResourceBundle getBundle(String projectName,
219 Class caller,
220 String resourceName,
221 Locale locale,
222 ResourceBundle extendsBundle)
223 throws MissingResourceException
224 {
225 return getBundle(projectName,
226 getPackage(caller.getClass().getName()),
227 resourceName,
228 locale,
229 caller.getClass().getClassLoader(),
230 extendsBundle);
231 }
232
233 /**
234 * Construct a new ProjectResourceBundle
235 *
236 * @param projectName The name of the project to which the class belongs.
237 * It must be a proper prefix of the caller's package.
238 *
239 * @param caller The calling class.
240 * This is used to get the package name to further construct
241 * the basename as well as to get the proper ClassLoader.
242 *
243 * @param resourceName The name of the resource without the
244 * ".properties" extension
245 *
246 * @param locale The locale
247 *
248 * @param extendsBundle If non-null, then this ExtendMessages will
249 * default to extendsBundle.
250 *
251 * @throws MissingResourceException if projectName is not a prefix of
252 * the caller's package name, or if the resource could not be
253 * found/loaded.
254 */
255 public static ProjectResourceBundle getBundle(String projectName,
256 String packageName,
257 String resourceName,
258 Locale locale,
259 ClassLoader loader,
260 ResourceBundle extendsBundle)
261 throws MissingResourceException
262 {
263 if (log.isDebugEnabled()) {
264 log.debug("getBundle(" + projectName + ","
265 + packageName + ","
266 + resourceName + ","
267 + String.valueOf(locale) + ",...)");
268 }
269
270 Context context = new Context();
271 context.setLocale(locale);
272 context.setLoader(loader);
273 context.setProjectName(projectName);
274 context.setResourceName(resourceName);
275 context.setParentBundle(extendsBundle);
276
277 packageName = context.validate(packageName);
278
279 ProjectResourceBundle bundle = null;
280 try {
281 bundle = getBundle(context, packageName);
282 } catch (RuntimeException e) {
283 log.debug("Exception: ", e);
284 throw e;
285 }
286
287 if (bundle == null) {
288 throw new MissingResourceException("Cannot find resource '" +
289 packageName + '.' + resourceName + "'",
290 resourceName, "");
291 }
292
293 return bundle;
294 }
295
296 /**
297 * get bundle...
298 * - check cache
299 * - try up hierarchy
300 * - if at top of hierarchy, use (link to) context.getParentBundle()
301 */
302 private static synchronized ProjectResourceBundle getBundle(Context context, String packageName)
303 throws MissingResourceException
304 {
305 String cacheKey = context.getCacheKey(packageName);
306
307 ProjectResourceBundle prb = (ProjectResourceBundle)bundleCache.get(cacheKey);
308
309 if (prb == null) {
310 String name = packageName + '.' + context.getResourceName();
311 ResourceBundle rb = context.loadBundle(packageName);
312 ResourceBundle parent = context.getParentBundle(packageName);
313
314 if (rb != null) {
315 prb = new ProjectResourceBundle(name, rb);
316 prb.setParent(parent);
317 if (log.isDebugEnabled()) {
318 log.debug("Created " + prb + ", linked to parent " + String.valueOf(parent));
319 }
320 } else {
321 if (parent != null) {
322 if (parent instanceof ProjectResourceBundle) {
323 prb = (ProjectResourceBundle)parent;
324 } else {
325 prb = new ProjectResourceBundle(name, parent);
326 }
327 if (log.isDebugEnabled()) {
328 log.debug("Root package not found, cross link to " + parent);
329 }
330 }
331 }
332
333 if (prb != null) {
334 // Cache the resource
335 bundleCache.put(cacheKey, prb);
336 }
337 }
338
339 return prb;
340 }
341
342 private static final String getPackage(String name) {
343 return name.substring(0, name.lastIndexOf('.')).intern();
344 }
345
346 /**
347 * Construct a new ProjectResourceBundle
348 */
349 private ProjectResourceBundle(String name, ResourceBundle bundle)
350 throws MissingResourceException
351 {
352 this.resourceBundle = bundle;
353 this.resourceName = name;
354 }
355
356 public String getResourceName() {
357 return resourceName;
358 }
359
360 /**
361 * Clears the internal cache
362 */
363 public static void clearCache()
364 {
365 bundleCache.clear();
366 }
367
368 public String toString() {
369 return resourceName;
370 }
371
372
373 private static class Context {
374 private Locale _locale;
375 private ClassLoader _loader;
376 private String _projectName;
377 private String _resourceName;
378 private ResourceBundle _parent;
379
380 void setLocale(Locale l) {
381 /* 1. Docs indicate that if locale is not specified,
382 * then the default local is used in it's place.
383 * 2. A null value for locale is invalid.
384 *
385 * Therefore, default...
386 */
387 _locale = (l == null) ? defaultLocale : l;
388 }
389
390 void setLoader(ClassLoader l) {
391 _loader = (l != null) ? l : this.getClass().getClassLoader();
392 // START FIX: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16868
393 if (_loader == null) {
394 _loader = ClassLoader.getSystemClassLoader();
395 }
396 // END FIX: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16868
397 }
398
399 void setProjectName(String name) { _projectName = name.intern(); }
400 void setResourceName(String name) { _resourceName = name.intern(); }
401 void setParentBundle(ResourceBundle b) { _parent = b; }
402
403 Locale getLocale() { return _locale; }
404 ClassLoader getLoader() { return _loader; }
405 String getProjectName() { return _projectName; }
406 String getResourceName() { return _resourceName; }
407 ResourceBundle getParentBundle() { return _parent; }
408
409 String getCacheKey(String packageName)
410 {
411 String loaderName = (_loader == null) ? "" : (":" + _loader.hashCode());
412 return packageName + "." + _resourceName + ":" + _locale + ":" + defaultLocale + loaderName;
413 }
414
415 ResourceBundle loadBundle(String packageName)
416 {
417 try {
418 return ResourceBundle.getBundle(packageName + '.' + _resourceName,
419 _locale,
420 _loader);
421 } catch (MissingResourceException e) {
422 // Deliberately surpressing print stack.. just the string for info.
423 log.debug("loadBundle: Ignoring MissingResourceException: " + e.getMessage());
424 }
425 return null;
426 }
427
428 ResourceBundle getParentBundle(String packageName)
429 {
430 ResourceBundle p;
431 if (packageName != _projectName) {
432 p = getBundle(this, getPackage(packageName));
433 } else {
434 p = _parent;
435 _parent = null;
436 }
437 return p;
438 }
439
440 String validate(String packageName)
441 throws MissingResourceException
442 {
443 if (_projectName == null || _projectName.length() == 0) {
444 log.debug("Project name not specified");
445 throw new MissingResourceException("Project name not specified",
446 "", "");
447 }
448
449 if (packageName == null || packageName.length() == 0) {
450 log.debug("Package name not specified");
451 throw new MissingResourceException("Package not specified",
452 packageName, "");
453 }
454 packageName = packageName.intern();
455
456 /* Ensure that project is a proper prefix of class.
457 * Terminate project name with '.' to ensure proper match.
458 */
459 if (packageName != _projectName && !packageName.startsWith(_projectName + '.')) {
460 log.debug("Project not a prefix of Package");
461 throw new MissingResourceException("Project '" + _projectName
462 + "' must be a prefix of Package '"
463 + packageName + "'",
464 packageName + '.' + _resourceName, "");
465 }
466
467 return packageName;
468 }
469 }
470 }