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 }