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