1 /*
2 * $Id$
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21 package org.apache.struts.util;
22
23 import java.io.Serializable;
24 import java.text.MessageFormat;
25 import java.util.HashMap;
26 import java.util.Locale;
27
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 /**
32 * General purpose abstract class that describes an API for retrieving
33 * Locale-sensitive messages from underlying resource locations of an
34 * unspecified design, and optionally utilizing the <code>MessageFormat</code>
35 * class to produce internationalized messages with parametric replacement.
36 * <p> Calls to <code>getMessage()</code> variants without a
37 * <code>Locale</code> argument are presumed to be requesting a message string
38 * in the default <code>Locale</code> for this JVM. <p> Calls to
39 * <code>getMessage()</code> with an unknown key, or an unknown
40 * <code>Locale</code> will return <code>null</code> if the
41 * <code>returnNull</code> property is set to <code>true</code>. Otherwise, a
42 * suitable error message will be returned instead. <p> <strong>IMPLEMENTATION
43 * NOTE</strong> - Classes that extend this class must be Serializable so that
44 * instances may be used in distributable application server environments.
45 *
46 * @version $Rev$ $Date: 2005-08-29 23:57:50 -0400 (Mon, 29 Aug 2005)
47 * $
48 */
49 public abstract class MessageResources implements Serializable {
50 private static final long serialVersionUID = -7091558627339276086L;
51
52 // ------------------------------------------------------------- Properties
53
54 /**
55 * The {@code Log} instance for this class.
56 */
57 private transient final Logger log =
58 LoggerFactory.getLogger(MessageResources.class);
59
60 // --------------------------------------------------------- Static Methods
61
62 /**
63 * The default MessageResourcesFactory used to create MessageResources
64 * instances.
65 */
66 protected static MessageResourcesFactory defaultFactory = null;
67
68 /**
69 * The configuration parameter used to initialize this MessageResources.
70 */
71 protected String config = null;
72
73 /**
74 * The default Locale for our environment.
75 */
76 protected Locale defaultLocale = Locale.getDefault();
77
78 /**
79 * The <code>MessageResourcesFactory</code> that created this instance.
80 */
81 protected MessageResourcesFactory factory = null;
82
83 /**
84 * The set of previously created MessageFormat objects, keyed by the key
85 * computed in <code>messageKey()</code>.
86 */
87 protected HashMap<String, MessageFormat> formats = new HashMap<>();
88
89 /**
90 * Indicate is a <code>null</code> is returned instead of an error message
91 * string when an unknown Locale or key is requested.
92 */
93 protected boolean returnNull = false;
94
95 /**
96 * Indicates whether 'escape processing' should be performed on the error
97 * message string.
98 */
99 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, String config) {
111 this(factory, config, false);
112 }
113
114 /**
115 * Construct a new MessageResources according to the specified
116 * parameters.
117 *
118 * @param factory The MessageResourcesFactory that created us
119 * @param config The configuration parameter for this
120 * MessageResources
121 * @param returnNull The returnNull property we should initialize with
122 */
123 public MessageResources(MessageResourcesFactory factory, String config,
124 boolean returnNull) {
125 super();
126 this.factory = factory;
127 this.config = config;
128 this.returnNull = returnNull;
129 }
130
131 /**
132 * The configuration parameter used to initialize this MessageResources.
133 *
134 * @return parameter used to initialize this MessageResources
135 */
136 public String getConfig() {
137 return (this.config);
138 }
139
140 /**
141 * The <code>MessageResourcesFactory</code> that created this instance.
142 *
143 * @return <code>MessageResourcesFactory</code> that created instance
144 */
145 public MessageResourcesFactory getFactory() {
146 return (this.factory);
147 }
148
149 /**
150 * Indicates that a <code>null</code> is returned instead of an error
151 * message string if an unknown Locale or key is requested.
152 *
153 * @return true if null is returned if unknown key or locale is requested
154 */
155 public boolean getReturnNull() {
156 return (this.returnNull);
157 }
158
159 /**
160 * Indicates that a <code>null</code> is returned instead of an error
161 * message string if an unknown Locale or key is requested.
162 *
163 * @param returnNull true Indicates that a <code>null</code> is returned
164 * if an unknown Locale or key is requested.
165 */
166 public void setReturnNull(boolean returnNull) {
167 this.returnNull = returnNull;
168 }
169
170 /**
171 * Indicates whether 'escape processing' should be performed on the error
172 * message string.
173 *
174 * @since Struts 1.2.8
175 */
176 public boolean isEscape() {
177 return escape;
178 }
179
180 /**
181 * Set whether 'escape processing' should be performed on the error
182 * message string.
183 *
184 * @since Struts 1.2.8
185 */
186 public void setEscape(boolean escape) {
187 this.escape = escape;
188 }
189
190 // --------------------------------------------------------- Public Methods
191
192 /**
193 * Returns a text message for the specified key, for the default Locale.
194 *
195 * @param key The message key to look up
196 */
197 public String getMessage(String key) {
198 return this.getMessage((Locale) null, key, null);
199 }
200
201 /**
202 * Returns a text message after parametric replacement of the specified
203 * parameter placeholders.
204 *
205 * @param key The message key to look up
206 * @param args An array of replacement parameters for placeholders
207 */
208 public String getMessage(String key, Object[] args) {
209 return this.getMessage((Locale) null, key, args);
210 }
211
212 /**
213 * Returns a text message after parametric replacement of the specified
214 * parameter placeholders.
215 *
216 * @param key The message key to look up
217 * @param arg0 The replacement for placeholder {0} in the message
218 */
219 public String getMessage(String key, Object arg0) {
220 return this.getMessage((Locale) null, key, arg0);
221 }
222
223 /**
224 * Returns a text message after parametric replacement of the specified
225 * parameter placeholders.
226 *
227 * @param key The message key to look up
228 * @param arg0 The replacement for placeholder {0} in the message
229 * @param arg1 The replacement for placeholder {1} in the message
230 */
231 public String getMessage(String key, Object arg0, Object arg1) {
232 return this.getMessage((Locale) null, key, arg0, arg1);
233 }
234
235 /**
236 * Returns a text message after parametric replacement of the specified
237 * parameter placeholders.
238 *
239 * @param key The message key to look up
240 * @param arg0 The replacement for placeholder {0} in the message
241 * @param arg1 The replacement for placeholder {1} in the message
242 * @param arg2 The replacement for placeholder {2} in the message
243 */
244 public String getMessage(String key, Object arg0, Object arg1, Object arg2) {
245 return this.getMessage((Locale) null, key, arg0, arg1, arg2);
246 }
247
248 /**
249 * Returns a text message after parametric replacement of the specified
250 * parameter placeholders.
251 *
252 * @param key The message key to look up
253 * @param arg0 The replacement for placeholder {0} in the message
254 * @param arg1 The replacement for placeholder {1} in the message
255 * @param arg2 The replacement for placeholder {2} in the message
256 * @param arg3 The replacement for placeholder {3} in the message
257 */
258 public String getMessage(String key, Object arg0, Object arg1, Object arg2,
259 Object arg3) {
260 return this.getMessage((Locale) null, key, arg0, arg1, arg2, arg3);
261 }
262
263 /**
264 * Returns a text message for the specified key, for the default Locale. A
265 * null string result will be returned by this method if no relevant
266 * message resource is found for this key or Locale, if the
267 * <code>returnNull</code> property is set. Otherwise, an appropriate
268 * error message will be returned. <p> This method must be implemented by
269 * a concrete subclass.
270 *
271 * @param locale The requested message Locale, or <code>null</code> for
272 * the system default Locale
273 * @param key The message key to look up
274 */
275 public abstract String getMessage(Locale locale, String key);
276
277 /**
278 * Returns a text message after parametric replacement of the specified
279 * parameter placeholders. A null string result will be returned by this
280 * method if no resource bundle has been configured.
281 *
282 * @param locale The requested message Locale, or <code>null</code> for
283 * the system default Locale
284 * @param key The message key to look up
285 * @param args An array of replacement parameters for placeholders
286 */
287 public String getMessage(Locale locale, String key, Object[] args) {
288 // Cache MessageFormat instances as they are accessed
289 if (locale == null) {
290 locale = defaultLocale;
291 }
292
293 MessageFormat format = null;
294 String formatKey = messageKey(locale, key);
295
296 synchronized (formats) {
297 format = formats.get(formatKey);
298
299 if (format == null) {
300 String formatString = getMessage(locale, key);
301
302 if (formatString == null) {
303 return returnNull ? null : ("???" + formatKey + "???");
304 }
305
306 format = new MessageFormat(escape(formatString));
307 format.setLocale(locale);
308 formats.put(formatKey, format);
309 }
310 }
311
312 return format.format(args);
313 }
314
315 /**
316 * Returns a text message after parametric replacement of the specified
317 * parameter placeholders. A null string result will never be returned by
318 * this method.
319 *
320 * @param locale The requested message Locale, or <code>null</code> for
321 * the system default Locale
322 * @param key The message key to look up
323 * @param arg0 The replacement for placeholder {0} in the message
324 */
325 public String getMessage(Locale locale, String key, Object arg0) {
326 return this.getMessage(locale, key, new Object[] { arg0 });
327 }
328
329 /**
330 * Returns a text message after parametric replacement of the specified
331 * parameter placeholders. A null string result will never be returned by
332 * this method.
333 *
334 * @param locale The requested message Locale, or <code>null</code> for
335 * the system default Locale
336 * @param key The message key to look up
337 * @param arg0 The replacement for placeholder {0} in the message
338 * @param arg1 The replacement for placeholder {1} in the message
339 */
340 public String getMessage(Locale locale, String key, Object arg0, Object arg1) {
341 return this.getMessage(locale, key, new Object[] { arg0, arg1 });
342 }
343
344 /**
345 * Returns a text message after parametric replacement of the specified
346 * parameter placeholders. A null string result will never be returned by
347 * this method.
348 *
349 * @param locale The requested message Locale, or <code>null</code> for
350 * the system default Locale
351 * @param key The message key to look up
352 * @param arg0 The replacement for placeholder {0} in the message
353 * @param arg1 The replacement for placeholder {1} in the message
354 * @param arg2 The replacement for placeholder {2} in the message
355 */
356 public String getMessage(Locale locale, String key, Object arg0,
357 Object arg1, Object arg2) {
358 return this.getMessage(locale, key, new Object[] { arg0, arg1, arg2 });
359 }
360
361 /**
362 * Returns a text message after parametric replacement of the specified
363 * parameter placeholders. A null string result will never be returned by
364 * this method.
365 *
366 * @param locale The requested message Locale, or <code>null</code> for
367 * the system default Locale
368 * @param key The message key to look up
369 * @param arg0 The replacement for placeholder {0} in the message
370 * @param arg1 The replacement for placeholder {1} in the message
371 * @param arg2 The replacement for placeholder {2} in the message
372 * @param arg3 The replacement for placeholder {3} in the message
373 */
374 public String getMessage(Locale locale, String key, Object arg0,
375 Object arg1, Object arg2, Object arg3) {
376 return this.getMessage(locale, key,
377 new Object[] { arg0, arg1, arg2, arg3 });
378 }
379
380 /**
381 * Return <code>true</code> if there is a defined message for the
382 * specified key in the system default locale.
383 *
384 * @param key The message key to look up
385 */
386 public boolean isPresent(String key) {
387 return this.isPresent(null, key);
388 }
389
390 /**
391 * Return <code>true</code> if there is a defined message for the
392 * specified key in the specified Locale.
393 *
394 * @param locale The requested message Locale, or <code>null</code> for
395 * the system default Locale
396 * @param key The message key to look up
397 */
398 public boolean isPresent(Locale locale, String key) {
399 String message = getMessage(locale, key);
400
401 if (message == null) {
402 return false;
403 } else if (message.startsWith("???") && message.endsWith("???")) {
404 return false; // FIXME - Only valid for default implementation
405 } else {
406 return true;
407 }
408 }
409
410 // ------------------------------------------------------ Protected Methods
411
412 /**
413 * Escape any single quote characters that are included in the specified
414 * message string.
415 *
416 * @param string The string to be escaped
417 */
418 protected String escape(String string) {
419 if (!isEscape()) {
420 return string;
421 }
422
423 if ((string == null) || (string.indexOf('\'') < 0)) {
424 return string;
425 }
426
427 int n = string.length();
428 StringBuilder sb = new StringBuilder(n);
429
430 for (int i = 0; i < n; i++) {
431 char ch = string.charAt(i);
432
433 if (ch == '\'') {
434 sb.append('\'');
435 }
436
437 sb.append(ch);
438 }
439
440 return sb.toString();
441 }
442
443 /**
444 * Compute and return a key to be used in caching information by a Locale.
445 * <strong>NOTE</strong> - The locale key for the default Locale in our
446 * environment is a zero length String.
447 *
448 * @param locale The locale for which a key is desired
449 */
450 protected String localeKey(Locale locale) {
451 return (locale == null) ? "" : locale.toString();
452 }
453
454 /**
455 * Compute and return a key to be used in caching information by Locale
456 * and message key.
457 *
458 * @param locale The Locale for which this format key is calculated
459 * @param key The message key for which this format key is calculated
460 */
461 protected String messageKey(Locale locale, String key) {
462 return (localeKey(locale) + "." + key);
463 }
464
465 /**
466 * Compute and return a key to be used in caching information by locale
467 * key and message key.
468 *
469 * @param localeKey The locale key for which this cache key is calculated
470 * @param key The message key for which this cache key is
471 * calculated
472 */
473 protected String messageKey(String localeKey, String key) {
474 return (localeKey + "." + key);
475 }
476
477 /**
478 * Create and return an instance of <code>MessageResources</code> for the
479 * created by the default <code>MessageResourcesFactory</code>.
480 *
481 * @param config Configuration parameter for this message bundle.
482 */
483 public synchronized static MessageResources getMessageResources(
484 String config) {
485 if (defaultFactory == null) {
486 defaultFactory = MessageResourcesFactory.createFactory();
487 }
488
489 return defaultFactory.createResources(config);
490 }
491
492 /**
493 * Log a message to the Writer that has been configured for our use.
494 *
495 * @param message The message to be logged
496 */
497 public void log(String message) {
498 log.debug(message);
499 }
500
501 /**
502 * Log a message and exception to the Writer that has been configured for
503 * our use.
504 *
505 * @param message The message to be logged
506 * @param throwable The exception to be logged
507 */
508 public void log(String message, Throwable throwable) {
509 log.debug(message, throwable);
510 }
511 }