001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019:
020: package org.apache.axis2.i18n;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024:
025: import java.util.Enumeration;
026: import java.util.HashSet;
027: import java.util.Hashtable;
028: import java.util.Iterator;
029: import java.util.Locale;
030: import java.util.MissingResourceException;
031: import java.util.ResourceBundle;
032:
033: /**
034: * <p>Wrapper class for resource bundles. Property files are used to store
035: * resource strings, which are the only types of resources available.
036: * Property files can inherit properties from other files so that
037: * a base property file can be used and a small number of properties
038: * can be over-ridden by another property file. For example you may
039: * create an english version of a resource file named "resource.properties".
040: * You then decide that the British English version of all of the properties
041: * except one are the same, so there is no need to redefine all of the
042: * properties in "resource_en_GB", just the one that is different.</p>
043: * <p>The basename is the name of the property file without the ".properties"
044: * extension.</p>
045: * <p>Properties will be cached for performance.<p>
046: * <p>Property values stored in the property files can also contain dynamic
047: * variables. Any dynamic variable defined in PropertiesUtil.getVariableValue()
048: * can be used (such as {date}), as well as arguments in the form {0}, {1}, etc.
049: * Argument values are specified in the various overloaded getString() methods.</p>
050: */
051: public class ProjectResourceBundle extends ResourceBundle {
052: private static final Log log = LogFactory
053: .getLog(ProjectResourceBundle.class);
054:
055: // The static cache of ResourceBundles.
056: // The key is the 'basename + locale + default locale'
057: // The element is a ResourceBundle object
058: private static final Hashtable bundleCache = new Hashtable();
059:
060: private static final Locale defaultLocale = Locale.getDefault();
061:
062: private final ResourceBundle resourceBundle;
063: private final String resourceName;
064:
065: protected Object handleGetObject(String key)
066: throws MissingResourceException {
067: if (log.isDebugEnabled()) {
068: log.debug(this .toString() + "::handleGetObject(" + key
069: + ")");
070: }
071: Object obj;
072: try {
073: obj = resourceBundle.getObject(key);
074: } catch (MissingResourceException e) {
075: /* catch missing resource, ignore, & return null
076: * if this method doesn't return null, then parents
077: * are not searched
078: */
079: obj = null;
080: }
081: return obj;
082: }
083:
084: public Enumeration getKeys() {
085: Enumeration myKeys = resourceBundle.getKeys();
086: if (parent == null) {
087: return myKeys;
088: } else {
089: final HashSet set = new HashSet();
090: while (myKeys.hasMoreElements()) {
091: set.add(myKeys.nextElement());
092: }
093:
094: Enumeration pKeys = parent.getKeys();
095: while (pKeys.hasMoreElements()) {
096: set.add(pKeys.nextElement());
097: }
098:
099: return new Enumeration() {
100: private Iterator it = set.iterator();
101:
102: public boolean hasMoreElements() {
103: return it.hasNext();
104: }
105:
106: public Object nextElement() {
107: return it.next();
108: }
109: };
110: }
111: }
112:
113: /**
114: * Construct a new ProjectResourceBundle
115: *
116: * @param projectName The name of the project to which the class belongs.
117: * It must be a proper prefix of the caller's package.
118: * @param packageName The package name to further construct
119: * the basename.
120: * @param resourceName The name of the resource without the
121: * ".properties" extension
122: * @throws MissingResourceException if projectName is not a prefix of
123: * the caller's package name, or if the resource could not be
124: * found/loaded.
125: */
126: public static ProjectResourceBundle getBundle(String projectName,
127: String packageName, String resourceName)
128: throws MissingResourceException {
129: return getBundle(projectName, packageName, resourceName, null,
130: null, null);
131: }
132:
133: /**
134: * Construct a new ProjectResourceBundle
135: *
136: * @param projectName The name of the project to which the class belongs.
137: * It must be a proper prefix of the caller's package.
138: * @param caller The calling class.
139: * @param resourceName The name of the resource without the
140: * ".properties" extension
141: * @throws MissingResourceException if projectName is not a prefix of
142: * the caller's package name, or if the resource could not be
143: * found/loaded.
144: */
145: public static ProjectResourceBundle getBundle(String projectName,
146: Class caller, String resourceName, Locale locale)
147: throws MissingResourceException {
148: return getBundle(projectName, caller, resourceName, locale,
149: null);
150: }
151:
152: /**
153: * Construct a new ProjectResourceBundle
154: *
155: * @param projectName The name of the project to which the class belongs.
156: * It must be a proper prefix of the caller's package.
157: * @param packageName The package name to construct base name.
158: * @param resourceName The name of the resource without the
159: * ".properties" extension
160: * @param locale The locale
161: * @throws MissingResourceException if projectName is not a prefix of
162: * the caller's package name, or if the resource could not be
163: * found/loaded.
164: */
165: public static ProjectResourceBundle getBundle(String projectName,
166: String packageName, String resourceName, Locale locale,
167: ClassLoader loader) throws MissingResourceException {
168: return getBundle(projectName, packageName, resourceName,
169: locale, loader, null);
170: }
171:
172: /**
173: * Construct a new ProjectResourceBundle
174: *
175: * @param projectName The name of the project to which the class belongs.
176: * It must be a proper prefix of the caller's package.
177: * @param caller The calling class.
178: * This is used to get the package name to further construct
179: * the basename as well as to get the proper ClassLoader.
180: * @param resourceName The name of the resource without the
181: * ".properties" extension
182: * @param locale The locale
183: * @param extendsBundle If non-null, then this ExtendMessages will
184: * default to extendsBundle.
185: * @throws MissingResourceException if projectName is not a prefix of
186: * the caller's package name, or if the resource could not be
187: * found/loaded.
188: */
189: public static ProjectResourceBundle getBundle(String projectName,
190: Class caller, String resourceName, Locale locale,
191: ResourceBundle extendsBundle)
192: throws MissingResourceException {
193: return getBundle(projectName, getPackage(caller.getClass()
194: .getName()), resourceName, locale, caller.getClass()
195: .getClassLoader(), extendsBundle);
196: }
197:
198: /**
199: * Construct a new ProjectResourceBundle
200: *
201: * @param projectName The name of the project to which the class belongs.
202: * It must be a proper prefix of the caller's package.
203: * @param packageName The package name to further construct
204: * the basename.
205: * @param resourceName The name of the resource without the
206: * ".properties" extension
207: * @param locale The locale
208: * @param extendsBundle If non-null, then this ExtendMessages will
209: * default to extendsBundle.
210: * @throws MissingResourceException if projectName is not a prefix of
211: * the caller's package name, or if the resource could not be
212: * found/loaded.
213: */
214: public static ProjectResourceBundle getBundle(String projectName,
215: String packageName, String resourceName, Locale locale,
216: ClassLoader loader, ResourceBundle extendsBundle)
217: throws MissingResourceException {
218: if (log.isDebugEnabled()) {
219: log.debug("getBundle(" + projectName + "," + packageName
220: + "," + resourceName + "," + String.valueOf(locale)
221: + ",...)");
222: }
223:
224: Context context = new Context();
225: context.setLocale(locale);
226: context.setLoader(loader);
227: context.setProjectName(projectName);
228: context.setResourceName(resourceName);
229: context.setParentBundle(extendsBundle);
230:
231: packageName = context.validate(packageName);
232:
233: ProjectResourceBundle bundle = null;
234: try {
235: bundle = getBundle(context, packageName);
236: } catch (RuntimeException e) {
237: log.debug("Exception: ", e);
238: throw e;
239: }
240:
241: if (bundle == null) {
242: throw new MissingResourceException("Cannot find resource '"
243: + packageName + '.' + resourceName + "'",
244: resourceName, "");
245: }
246:
247: return bundle;
248: }
249:
250: /**
251: * get bundle...
252: * - check cache
253: * - try up hierarchy
254: * - if at top of hierarchy, use (link to) context.getParentBundle()
255: */
256: private static synchronized ProjectResourceBundle getBundle(
257: Context context, String packageName)
258: throws MissingResourceException {
259: String cacheKey = context.getCacheKey(packageName);
260:
261: ProjectResourceBundle prb = (ProjectResourceBundle) bundleCache
262: .get(cacheKey);
263:
264: if (prb == null) {
265: String name = packageName + '.' + context.getResourceName();
266: ResourceBundle rb = context.loadBundle(packageName);
267: ResourceBundle parent = context
268: .getParentBundle(packageName);
269:
270: if (rb != null) {
271: prb = new ProjectResourceBundle(name, rb);
272: prb.setParent(parent);
273: if (log.isDebugEnabled()) {
274: log.debug("Created " + prb + ", linked to parent "
275: + String.valueOf(parent));
276: }
277: } else {
278: if (parent != null) {
279: if (parent instanceof ProjectResourceBundle) {
280: prb = (ProjectResourceBundle) parent;
281: } else {
282: prb = new ProjectResourceBundle(name, parent);
283: }
284: if (log.isDebugEnabled()) {
285: log
286: .debug("Root package not found, cross link to "
287: + parent);
288: }
289: }
290: }
291:
292: if (prb != null) {
293: // Cache the resource
294: bundleCache.put(cacheKey, prb);
295: }
296: }
297:
298: return prb;
299: }
300:
301: private static String getPackage(String name) {
302: return name.substring(0, name.lastIndexOf('.')).intern();
303: }
304:
305: /**
306: * Construct a new ProjectResourceBundle
307: */
308: private ProjectResourceBundle(String name, ResourceBundle bundle)
309: throws MissingResourceException {
310: this .resourceBundle = bundle;
311: this .resourceName = name;
312: }
313:
314: public String getResourceName() {
315: return resourceName;
316: }
317:
318: /**
319: * Clears the internal cache
320: */
321: // public static void clearCache() {
322: // bundleCache.clear();
323: // }
324: public String toString() {
325: return resourceName;
326: }
327:
328: private static class Context {
329: private Locale _locale;
330: private ClassLoader _loader;
331: private String _projectName;
332: private String _resourceName;
333: private ResourceBundle _parent;
334:
335: void setLocale(Locale l) {
336: /* 1. Docs indicate that if locale is not specified,
337: * then the default local is used in it's place.
338: * 2. A null value for locale is invalid.
339: *
340: * Therefore, default...
341: */
342: _locale = (l == null) ? defaultLocale : l;
343: }
344:
345: void setLoader(ClassLoader l) {
346: _loader = (l != null) ? l : this .getClass()
347: .getClassLoader();
348: // START FIX: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16868
349: if (_loader == null) {
350: _loader = ClassLoader.getSystemClassLoader();
351: }
352: // END FIX: http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16868
353: }
354:
355: void setProjectName(String name) {
356: _projectName = name.intern();
357: }
358:
359: void setResourceName(String name) {
360: _resourceName = name.intern();
361: }
362:
363: void setParentBundle(ResourceBundle b) {
364: _parent = b;
365: }
366:
367: Locale getLocale() {
368: return _locale;
369: }
370:
371: ClassLoader getLoader() {
372: return _loader;
373: }
374:
375: String getProjectName() {
376: return _projectName;
377: }
378:
379: String getResourceName() {
380: return _resourceName;
381: }
382:
383: ResourceBundle getParentBundle() {
384: return _parent;
385: }
386:
387: String getCacheKey(String packageName) {
388: String loaderName = (_loader == null) ? "" : (":" + _loader
389: .hashCode());
390: return packageName + "." + _resourceName + ":" + _locale
391: + ":" + defaultLocale + loaderName;
392: }
393:
394: ResourceBundle loadBundle(String packageName) {
395: try {
396: return ResourceBundle.getBundle(packageName + '.'
397: + _resourceName, _locale, _loader);
398: } catch (MissingResourceException e) {
399: // Deliberately surpressing print stack.. just the string for info.
400: log
401: .debug("loadBundle: Ignoring MissingResourceException: "
402: + e.getMessage());
403: }
404: return null;
405: }
406:
407: ResourceBundle getParentBundle(String packageName) {
408: ResourceBundle p;
409: if (!packageName.equals(_projectName)) {
410: p = getBundle(this , getPackage(packageName));
411: } else {
412: p = _parent;
413: _parent = null;
414: }
415: return p;
416: }
417:
418: String validate(String packageName)
419: throws MissingResourceException {
420: if (_projectName == null || _projectName.length() == 0) {
421: log.debug("Project name not specified");
422: throw new MissingResourceException(
423: "Project name not specified", "", "");
424: }
425:
426: if (packageName == null || packageName.length() == 0) {
427: log.debug("Package name not specified");
428: throw new MissingResourceException(
429: "Package not specified", packageName, "");
430: }
431: packageName = packageName.intern();
432:
433: /* Ensure that project is a proper prefix of class.
434: * Terminate project name with '.' to ensure proper match.
435: */
436: if (!packageName.equals(_projectName)
437: && !packageName.startsWith(_projectName + '.')) {
438: log.debug("Project not a prefix of Package");
439: throw new MissingResourceException("Project '"
440: + _projectName
441: + "' must be a prefix of Package '"
442: + packageName + "'", packageName + '.'
443: + _resourceName, "");
444: }
445:
446: return packageName;
447: }
448: }
449: }
|