001: /*
002: * $Id: MessageResources.java 471754 2006-11-06 14:55:09Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts.util;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025:
026: import java.io.Serializable;
027:
028: import java.text.MessageFormat;
029:
030: import java.util.HashMap;
031: import java.util.Locale;
032:
033: /**
034: * General purpose abstract class that describes an API for retrieving
035: * Locale-sensitive messages from underlying resource locations of an
036: * unspecified design, and optionally utilizing the <code>MessageFormat</code>
037: * class to produce internationalized messages with parametric replacement.
038: * <p> Calls to <code>getMessage()</code> variants without a
039: * <code>Locale</code> argument are presumed to be requesting a message string
040: * in the default <code>Locale</code> for this JVM. <p> Calls to
041: * <code>getMessage()</code> with an unknown key, or an unknown
042: * <code>Locale</code> will return <code>null</code> if the
043: * <code>returnNull</code> property is set to <code>true</code>. Otherwise, a
044: * suitable error message will be returned instead. <p> <strong>IMPLEMENTATION
045: * NOTE</strong> - Classes that extend this class must be Serializable so that
046: * instances may be used in distributable application server environments.
047: *
048: * @version $Rev: 471754 $ $Date: 2005-08-29 23:57:50 -0400 (Mon, 29 Aug 2005)
049: * $
050: */
051: public abstract class MessageResources implements Serializable {
052: // ------------------------------------------------------------- Properties
053:
054: /**
055: * Commons Logging instance.
056: */
057: protected static Log log = LogFactory
058: .getLog(MessageResources.class);
059:
060: // --------------------------------------------------------- Static Methods
061:
062: /**
063: * The default MessageResourcesFactory used to create MessageResources
064: * instances.
065: */
066: protected static MessageResourcesFactory defaultFactory = null;
067:
068: /**
069: * The configuration parameter used to initialize this MessageResources.
070: */
071: protected String config = null;
072:
073: /**
074: * The default Locale for our environment.
075: */
076: protected Locale defaultLocale = Locale.getDefault();
077:
078: /**
079: * The <code>MessageResourcesFactory</code> that created this instance.
080: */
081: protected MessageResourcesFactory factory = null;
082:
083: /**
084: * The set of previously created MessageFormat objects, keyed by the key
085: * computed in <code>messageKey()</code>.
086: */
087: protected HashMap formats = new HashMap();
088:
089: /**
090: * Indicate is a <code>null</code> is returned instead of an error message
091: * string when an unknown Locale or key is requested.
092: */
093: protected boolean returnNull = false;
094:
095: /**
096: * Indicates whether 'escape processing' should be performed on the error
097: * message string.
098: */
099: private boolean escape = true;
100:
101: // ----------------------------------------------------------- Constructors
102:
103: /**
104: * Construct a new MessageResources according to the specified
105: * parameters.
106: *
107: * @param factory The MessageResourcesFactory that created us
108: * @param config The configuration parameter for this MessageResources
109: */
110: public MessageResources(MessageResourcesFactory factory,
111: String config) {
112: this (factory, config, false);
113: }
114:
115: /**
116: * Construct a new MessageResources according to the specified
117: * parameters.
118: *
119: * @param factory The MessageResourcesFactory that created us
120: * @param config The configuration parameter for this
121: * MessageResources
122: * @param returnNull The returnNull property we should initialize with
123: */
124: public MessageResources(MessageResourcesFactory factory,
125: String config, boolean returnNull) {
126: super ();
127: this .factory = factory;
128: this .config = config;
129: this .returnNull = returnNull;
130: }
131:
132: /**
133: * The configuration parameter used to initialize this MessageResources.
134: *
135: * @return parameter used to initialize this MessageResources
136: */
137: public String getConfig() {
138: return (this .config);
139: }
140:
141: /**
142: * The <code>MessageResourcesFactory</code> that created this instance.
143: *
144: * @return <code>MessageResourcesFactory</code> that created instance
145: */
146: public MessageResourcesFactory getFactory() {
147: return (this .factory);
148: }
149:
150: /**
151: * Indicates that a <code>null</code> is returned instead of an error
152: * message string if an unknown Locale or key is requested.
153: *
154: * @return true if null is returned if unknown key or locale is requested
155: */
156: public boolean getReturnNull() {
157: return (this .returnNull);
158: }
159:
160: /**
161: * Indicates that a <code>null</code> is returned instead of an error
162: * message string if an unknown Locale or key is requested.
163: *
164: * @param returnNull true Indicates that a <code>null</code> is returned
165: * if an unknown Locale or key is requested.
166: */
167: public void setReturnNull(boolean returnNull) {
168: this .returnNull = returnNull;
169: }
170:
171: /**
172: * Indicates whether 'escape processing' should be performed on the error
173: * message string.
174: *
175: * @since Struts 1.2.8
176: */
177: public boolean isEscape() {
178: return escape;
179: }
180:
181: /**
182: * Set whether 'escape processing' should be performed on the error
183: * message string.
184: *
185: * @since Struts 1.2.8
186: */
187: public void setEscape(boolean escape) {
188: this .escape = escape;
189: }
190:
191: // --------------------------------------------------------- Public Methods
192:
193: /**
194: * Returns a text message for the specified key, for the default Locale.
195: *
196: * @param key The message key to look up
197: */
198: public String getMessage(String key) {
199: return this .getMessage((Locale) null, key, null);
200: }
201:
202: /**
203: * Returns a text message after parametric replacement of the specified
204: * parameter placeholders.
205: *
206: * @param key The message key to look up
207: * @param args An array of replacement parameters for placeholders
208: */
209: public String getMessage(String key, Object[] args) {
210: return this .getMessage((Locale) null, key, args);
211: }
212:
213: /**
214: * Returns a text message after parametric replacement of the specified
215: * parameter placeholders.
216: *
217: * @param key The message key to look up
218: * @param arg0 The replacement for placeholder {0} in the message
219: */
220: public String getMessage(String key, Object arg0) {
221: return this .getMessage((Locale) null, key, arg0);
222: }
223:
224: /**
225: * Returns a text message after parametric replacement of the specified
226: * parameter placeholders.
227: *
228: * @param key The message key to look up
229: * @param arg0 The replacement for placeholder {0} in the message
230: * @param arg1 The replacement for placeholder {1} in the message
231: */
232: public String getMessage(String key, Object arg0, Object arg1) {
233: return this .getMessage((Locale) null, key, arg0, arg1);
234: }
235:
236: /**
237: * Returns a text message after parametric replacement of the specified
238: * parameter placeholders.
239: *
240: * @param key The message key to look up
241: * @param arg0 The replacement for placeholder {0} in the message
242: * @param arg1 The replacement for placeholder {1} in the message
243: * @param arg2 The replacement for placeholder {2} in the message
244: */
245: public String getMessage(String key, Object arg0, Object arg1,
246: Object arg2) {
247: return this .getMessage((Locale) null, key, arg0, arg1, arg2);
248: }
249:
250: /**
251: * Returns a text message after parametric replacement of the specified
252: * parameter placeholders.
253: *
254: * @param key The message key to look up
255: * @param arg0 The replacement for placeholder {0} in the message
256: * @param arg1 The replacement for placeholder {1} in the message
257: * @param arg2 The replacement for placeholder {2} in the message
258: * @param arg3 The replacement for placeholder {3} in the message
259: */
260: public String getMessage(String key, Object arg0, Object arg1,
261: Object arg2, Object arg3) {
262: return this .getMessage((Locale) null, key, arg0, arg1, arg2,
263: arg3);
264: }
265:
266: /**
267: * Returns a text message for the specified key, for the default Locale. A
268: * null string result will be returned by this method if no relevant
269: * message resource is found for this key or Locale, if the
270: * <code>returnNull</code> property is set. Otherwise, an appropriate
271: * error message will be returned. <p> This method must be implemented by
272: * a concrete subclass.
273: *
274: * @param locale The requested message Locale, or <code>null</code> for
275: * the system default Locale
276: * @param key The message key to look up
277: */
278: public abstract String getMessage(Locale locale, String key);
279:
280: /**
281: * Returns a text message after parametric replacement of the specified
282: * parameter placeholders. A null string result will be returned by this
283: * method if no resource bundle has been configured.
284: *
285: * @param locale The requested message Locale, or <code>null</code> for
286: * the system default Locale
287: * @param key The message key to look up
288: * @param args An array of replacement parameters for placeholders
289: */
290: public String getMessage(Locale locale, String key, Object[] args) {
291: // Cache MessageFormat instances as they are accessed
292: if (locale == null) {
293: locale = defaultLocale;
294: }
295:
296: MessageFormat format = null;
297: String formatKey = messageKey(locale, key);
298:
299: synchronized (formats) {
300: format = (MessageFormat) formats.get(formatKey);
301:
302: if (format == null) {
303: String formatString = getMessage(locale, key);
304:
305: if (formatString == null) {
306: return returnNull ? null
307: : ("???" + formatKey + "???");
308: }
309:
310: format = new MessageFormat(escape(formatString));
311: format.setLocale(locale);
312: formats.put(formatKey, format);
313: }
314: }
315:
316: return format.format(args);
317: }
318:
319: /**
320: * Returns a text message after parametric replacement of the specified
321: * parameter placeholders. A null string result will never be returned by
322: * this method.
323: *
324: * @param locale The requested message Locale, or <code>null</code> for
325: * the system default Locale
326: * @param key The message key to look up
327: * @param arg0 The replacement for placeholder {0} in the message
328: */
329: public String getMessage(Locale locale, String key, Object arg0) {
330: return this .getMessage(locale, key, new Object[] { arg0 });
331: }
332:
333: /**
334: * Returns a text message after parametric replacement of the specified
335: * parameter placeholders. A null string result will never be returned by
336: * this method.
337: *
338: * @param locale The requested message Locale, or <code>null</code> for
339: * the system default Locale
340: * @param key The message key to look up
341: * @param arg0 The replacement for placeholder {0} in the message
342: * @param arg1 The replacement for placeholder {1} in the message
343: */
344: public String getMessage(Locale locale, String key, Object arg0,
345: Object arg1) {
346: return this
347: .getMessage(locale, key, new Object[] { arg0, arg1 });
348: }
349:
350: /**
351: * Returns a text message after parametric replacement of the specified
352: * parameter placeholders. A null string result will never be returned by
353: * this method.
354: *
355: * @param locale The requested message Locale, or <code>null</code> for
356: * the system default Locale
357: * @param key The message key to look up
358: * @param arg0 The replacement for placeholder {0} in the message
359: * @param arg1 The replacement for placeholder {1} in the message
360: * @param arg2 The replacement for placeholder {2} in the message
361: */
362: public String getMessage(Locale locale, String key, Object arg0,
363: Object arg1, Object arg2) {
364: return this .getMessage(locale, key, new Object[] { arg0, arg1,
365: arg2 });
366: }
367:
368: /**
369: * Returns a text message after parametric replacement of the specified
370: * parameter placeholders. A null string result will never be returned by
371: * this method.
372: *
373: * @param locale The requested message Locale, or <code>null</code> for
374: * the system default Locale
375: * @param key The message key to look up
376: * @param arg0 The replacement for placeholder {0} in the message
377: * @param arg1 The replacement for placeholder {1} in the message
378: * @param arg2 The replacement for placeholder {2} in the message
379: * @param arg3 The replacement for placeholder {3} in the message
380: */
381: public String getMessage(Locale locale, String key, Object arg0,
382: Object arg1, Object arg2, Object arg3) {
383: return this .getMessage(locale, key, new Object[] { arg0, arg1,
384: arg2, arg3 });
385: }
386:
387: /**
388: * Return <code>true</code> if there is a defined message for the
389: * specified key in the system default locale.
390: *
391: * @param key The message key to look up
392: */
393: public boolean isPresent(String key) {
394: return this .isPresent(null, key);
395: }
396:
397: /**
398: * Return <code>true</code> if there is a defined message for the
399: * specified key in the specified Locale.
400: *
401: * @param locale The requested message Locale, or <code>null</code> for
402: * the system default Locale
403: * @param key The message key to look up
404: */
405: public boolean isPresent(Locale locale, String key) {
406: String message = getMessage(locale, key);
407:
408: if (message == null) {
409: return false;
410: } else if (message.startsWith("???") && message.endsWith("???")) {
411: return false; // FIXME - Only valid for default implementation
412: } else {
413: return true;
414: }
415: }
416:
417: // ------------------------------------------------------ Protected Methods
418:
419: /**
420: * Escape any single quote characters that are included in the specified
421: * message string.
422: *
423: * @param string The string to be escaped
424: */
425: protected String escape(String string) {
426: if (!isEscape()) {
427: return string;
428: }
429:
430: if ((string == null) || (string.indexOf('\'') < 0)) {
431: return string;
432: }
433:
434: int n = string.length();
435: StringBuffer sb = new StringBuffer(n);
436:
437: for (int i = 0; i < n; i++) {
438: char ch = string.charAt(i);
439:
440: if (ch == '\'') {
441: sb.append('\'');
442: }
443:
444: sb.append(ch);
445: }
446:
447: return sb.toString();
448: }
449:
450: /**
451: * Compute and return a key to be used in caching information by a Locale.
452: * <strong>NOTE</strong> - The locale key for the default Locale in our
453: * environment is a zero length String.
454: *
455: * @param locale The locale for which a key is desired
456: */
457: protected String localeKey(Locale locale) {
458: return (locale == null) ? "" : locale.toString();
459: }
460:
461: /**
462: * Compute and return a key to be used in caching information by Locale
463: * and message key.
464: *
465: * @param locale The Locale for which this format key is calculated
466: * @param key The message key for which this format key is calculated
467: */
468: protected String messageKey(Locale locale, String key) {
469: return (localeKey(locale) + "." + key);
470: }
471:
472: /**
473: * Compute and return a key to be used in caching information by locale
474: * key and message key.
475: *
476: * @param localeKey The locale key for which this cache key is calculated
477: * @param key The message key for which this cache key is
478: * calculated
479: */
480: protected String messageKey(String localeKey, String key) {
481: return (localeKey + "." + key);
482: }
483:
484: /**
485: * Create and return an instance of <code>MessageResources</code> for the
486: * created by the default <code>MessageResourcesFactory</code>.
487: *
488: * @param config Configuration parameter for this message bundle.
489: */
490: public synchronized static MessageResources getMessageResources(
491: String config) {
492: if (defaultFactory == null) {
493: defaultFactory = MessageResourcesFactory.createFactory();
494: }
495:
496: return defaultFactory.createResources(config);
497: }
498:
499: /**
500: * Log a message to the Writer that has been configured for our use.
501: *
502: * @param message The message to be logged
503: */
504: public void log(String message) {
505: log.debug(message);
506: }
507:
508: /**
509: * Log a message and exception to the Writer that has been configured for
510: * our use.
511: *
512: * @param message The message to be logged
513: * @param throwable The exception to be logged
514: */
515: public void log(String message, Throwable throwable) {
516: log.debug(message, throwable);
517: }
518: }
|