001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.i18n; 029 030import org.opencms.main.CmsLog; 031import org.opencms.util.CmsDateUtil; 032import org.opencms.util.CmsStringUtil; 033 034import java.text.DateFormat; 035import java.text.MessageFormat; 036import java.util.Date; 037import java.util.Locale; 038import java.util.MissingResourceException; 039import java.util.ResourceBundle; 040 041import org.apache.commons.logging.Log; 042 043/** 044 * Reads localized resource Strings from a <code>java.util.ResourceBundle</code> 045 * and provides convenience methods to access the Strings from a template.<p> 046 * 047 * This class is frequently used from JSP templates. Because of that, throwing of 048 * exceptions related to the access of the resource bundle are suppressed 049 * so that a template always execute. The class provides an {@link #isInitialized()} method 050 * that can be checked to see if the instance was properly initialized.<p> 051 * 052 * @since 6.0.0 053 */ 054public class CmsMessages { 055 056 /** The suffix of a "short" localized key name. */ 057 public static final String KEY_SHORT_SUFFIX = ".short"; 058 059 /** Prefix / Suffix for unknown keys. */ 060 public static final String UNKNOWN_KEY_EXTENSION = "???"; 061 062 /** The log object for this class. */ 063 private static final Log LOG = CmsLog.getLog(CmsMessages.class); 064 065 /** The resource bundle base name this object was initialized with. */ 066 private String m_bundleName; 067 068 /** The locale to use for looking up the messages from the bundle. */ 069 private Locale m_locale; 070 071 /** The resource bundle this message object was initialized with. */ 072 private ResourceBundle m_resourceBundle; 073 074 /** 075 * Constructor for the messages with an initialized <code>java.util.Locale</code>. 076 * 077 * @param bundleName the base ResourceBundle name 078 * @param locale the m_locale to use, eg. "de", "en" etc. 079 */ 080 public CmsMessages(String bundleName, Locale locale) { 081 082 try { 083 m_locale = locale; 084 m_bundleName = bundleName; 085 m_resourceBundle = CmsResourceBundleLoader.getBundle(bundleName, m_locale); 086 } catch (MissingResourceException e) { 087 m_resourceBundle = null; 088 } catch (Exception e) { 089 m_resourceBundle = null; 090 LOG.error("Error creating messages for bundle " + bundleName + " for locale " + m_locale); 091 } 092 } 093 094 /** 095 * Constructor for the messages with a language string.<p> 096 * 097 * The <code>language</code> is a 2 letter language ISO code, e.g. <code>"EN"</code>.<p> 098 * 099 * The Locale for the messages will be created like this:<br> 100 * <code>new Locale(language, "", "")</code>.<p> 101 * 102 * @param bundleName the base ResourceBundle name 103 * @param language ISO language indentificator for the m_locale of the bundle 104 */ 105 public CmsMessages(String bundleName, String language) { 106 107 this(bundleName, language, "", ""); 108 } 109 110 /** 111 * Constructor for the messages with language and country code strings.<p> 112 * 113 * The <code>language</code> is a 2 letter language ISO code, e.g. <code>"EN"</code>. 114 * The <code>country</code> is a 2 letter country ISO code, e.g. <code>"us"</code>.<p> 115 * 116 * The Locale for the messages will be created like this:<br> 117 * <code>new Locale(language, country, "")</code>. 118 * 119 * @param bundleName the base ResourceBundle name 120 * @param language ISO language indentificator for the m_locale of the bundle 121 * @param country ISO 2 letter country code for the m_locale of the bundle 122 */ 123 public CmsMessages(String bundleName, String language, String country) { 124 125 this(bundleName, language, country, ""); 126 } 127 128 /** 129 * Constructor for the messages with language, country code and variant strings.<p> 130 * 131 * The <code>language</code> is a 2 letter language ISO code, e.g. <code>"EN"</code>. 132 * The <code>country</code> is a 2 letter country ISO code, e.g. <code>"us"</code>. 133 * The <code>variant</code> is a vendor or browser-specific code, e.g. <code>"POSIX"</code>.<p> 134 * 135 * The Locale for the messages will be created like this:<br> 136 * <code>new Locale(language, country, variant)</code>. 137 * 138 * @param bundleName the base ResourceBundle name 139 * @param language language indentificator for the m_locale of the bundle 140 * @param country 2 letter country code for the m_locale of the bundle 141 * @param variant a vendor or browser-specific variant code 142 */ 143 public CmsMessages(String bundleName, String language, String country, String variant) { 144 145 this(bundleName, new Locale(language, country, variant)); 146 } 147 148 /** 149 * Empty constructor for subclassing.<p> 150 */ 151 protected CmsMessages() { 152 153 // empty constructor for subclassing 154 } 155 156 /** 157 * Formats an unknown key.<p> 158 * 159 * @param keyName the key to format 160 * @return the formatted unknown key 161 * 162 * @see #isUnknownKey(String) 163 */ 164 public static String formatUnknownKey(String keyName) { 165 166 StringBuffer buf = new StringBuffer(64); 167 buf.append(UNKNOWN_KEY_EXTENSION); 168 buf.append(" "); 169 buf.append(keyName); 170 buf.append(" "); 171 buf.append(UNKNOWN_KEY_EXTENSION); 172 return buf.toString(); 173 } 174 175 /** 176 * Returns <code>true</code> if the provided value matches the scheme 177 * <code>"??? " + keyName + " ???"</code>, that is the value appears to be an unknown key.<p> 178 * 179 * Also returns <code>true</code> if the given value is <code>null</code>.<p> 180 * 181 * @param value the value to check 182 * @return true if the value is matches the scheme for unknown keys 183 * 184 * @see #formatUnknownKey(String) 185 */ 186 public static boolean isUnknownKey(String value) { 187 188 return (value == null) || (value.startsWith(UNKNOWN_KEY_EXTENSION)); 189 } 190 191 /** 192 * @see java.lang.Object#equals(java.lang.Object) 193 */ 194 @Override 195 public boolean equals(Object obj) { 196 197 if (obj == this) { 198 return true; 199 } 200 if (obj instanceof CmsMultiMessages) { 201 return false; 202 } 203 if (obj instanceof CmsMessages) { 204 CmsMessages other = (CmsMessages)obj; 205 return other.getBundleName().equals(m_bundleName) && other.getLocale().equals(m_locale); 206 } 207 return false; 208 } 209 210 /** 211 * Returns a formated date String from a Date value, 212 * the format being {@link DateFormat#SHORT} and the locale 213 * based on this instance.<p> 214 * 215 * @param date the Date object to format as String 216 * @return the formatted date 217 */ 218 public String getDate(Date date) { 219 220 return CmsDateUtil.getDate(date, DateFormat.SHORT, m_locale); 221 } 222 223 /** 224 * Returns a formated date String from a Date value, 225 * the formatting based on the provided option and the locale 226 * based on this instance.<p> 227 * 228 * @param date the Date object to format as String 229 * @param format the format to use, see {@link CmsMessages} for possible values 230 * @return the formatted date 231 */ 232 public String getDate(Date date, int format) { 233 234 return CmsDateUtil.getDate(date, format, m_locale); 235 } 236 237 /** 238 * Returns a formated date String from a timestamp value, 239 * the format being {@link DateFormat#SHORT} and the locale 240 * based on this instance.<p> 241 * 242 * @param time the time value to format as date 243 * @return the formatted date 244 */ 245 public String getDate(long time) { 246 247 return CmsDateUtil.getDate(new Date(time), DateFormat.SHORT, m_locale); 248 } 249 250 /** 251 * Returns a formated date and time String from a Date value, 252 * the format being {@link DateFormat#SHORT} and the locale 253 * based on this instance.<p> 254 * 255 * @param date the Date object to format as String 256 * @return the formatted date and time 257 */ 258 public String getDateTime(Date date) { 259 260 return CmsDateUtil.getDateTime(date, DateFormat.SHORT, m_locale); 261 } 262 263 /** 264 * Returns a formated date and time String from a Date value, 265 * the formatting based on the provided option and the locale 266 * based on this instance.<p> 267 * 268 * @param date the Date object to format as String 269 * @param format the format to use, see {@link CmsMessages} for possible values 270 * @return the formatted date and time 271 */ 272 public String getDateTime(Date date, int format) { 273 274 return CmsDateUtil.getDateTime(date, format, m_locale); 275 } 276 277 /** 278 * Returns a formated date and time String from a timestamp value, 279 * the format being {@link DateFormat#SHORT} and the locale 280 * based on this instance.<p> 281 * 282 * @param time the time value to format as date 283 * @return the formatted date and time 284 */ 285 public String getDateTime(long time) { 286 287 return CmsDateUtil.getDateTime(new Date(time), DateFormat.SHORT, m_locale); 288 } 289 290 /** 291 * Returns the locale to use for looking up this messages.<p> 292 * 293 * @return the locale to use for looking up this messages 294 */ 295 public Locale getLocale() { 296 297 return m_locale; 298 } 299 300 /** 301 * Returns the resource bundle this message object was initialized with.<p> 302 * 303 * @return the resource bundle this message object was initialized with or null if initialization was not successful 304 */ 305 public ResourceBundle getResourceBundle() { 306 307 return m_resourceBundle; 308 } 309 310 /** 311 * Directly calls the getString(String) method of the wrapped ResourceBundle.<p> 312 * 313 * If you use this this class on a template, you should consider using 314 * the {@link #key(String)} method to get the value from the ResourceBundle because it 315 * handles the exception for you in a convenient way. 316 * 317 * @param keyName the key 318 * @return the resource string for the given key 319 * 320 * @throws CmsMessageException in case the key is not found or the bundle is not initialized 321 */ 322 public String getString(String keyName) throws CmsMessageException { 323 324 if (m_resourceBundle != null) { 325 try { 326 return m_resourceBundle.getString(keyName); 327 } catch (MissingResourceException e) { 328 throw new CmsMessageException( 329 Messages.get().container(Messages.ERR_CANT_FIND_RESOURCE_FOR_BUNDLE_2, keyName, m_bundleName)); 330 } 331 } else { 332 throw new CmsMessageException( 333 Messages.get().container(Messages.ERR_MESSAGE_BUNDLE_NOT_INITIALIZED_1, m_bundleName)); 334 } 335 } 336 337 /** 338 * @see java.lang.Object#hashCode() 339 */ 340 @Override 341 public int hashCode() { 342 343 return m_locale.hashCode() + (m_bundleName == null ? 0 : m_bundleName.hashCode()); 344 } 345 346 /** 347 * Checks if the bundle was properly initialized. 348 * 349 * @return <code>true</code> if bundle was initialized, <code>false</code> otherwise 350 */ 351 public boolean isInitialized() { 352 353 return (m_resourceBundle != null); 354 } 355 356 /** 357 * Indicates that users of this CmsMessages instance should not cache message from it.<p> 358 * 359 * @return true if messages from this CmsMessages instance should not be cached 360 */ 361 public boolean isUncacheable() { 362 363 return (m_resourceBundle instanceof CmsVfsResourceBundle); 364 } 365 366 /** 367 * Returns the localized resource string for a given message key.<p> 368 * 369 * If the key was not found in the bundle, the return value is 370 * <code>"??? " + keyName + " ???"</code>. This will also be returned 371 * if the bundle was not properly initialized first. 372 * 373 * @param keyName the key for the desired string 374 * @return the resource string for the given key 375 */ 376 public String key(String keyName) { 377 378 return key(keyName, false); 379 } 380 381 /** 382 * Returns the localized resource string for a given message key.<p> 383 * 384 * If the key was not found in the bundle, the return value 385 * depends on the setting of the allowNull parameter. If set to false, 386 * the return value is always a String in the format 387 * <code>"??? " + keyName + " ???"</code>. 388 * If set to true, null is returned if the key is not found. 389 * This will also be returned 390 * if the bundle was not properly initialized first. 391 * 392 * @param keyName the key for the desired string 393 * @param allowNull if true, 'null' is an allowed return value 394 * @return the resource string for the given key 395 */ 396 public String key(String keyName, boolean allowNull) { 397 398 try { 399 if (m_resourceBundle != null) { 400 return m_resourceBundle.getString(keyName); 401 } 402 } catch (MissingResourceException e) { 403 // not found, return warning 404 if (allowNull) { 405 return null; 406 } 407 } 408 return formatUnknownKey(keyName); 409 } 410 411 /** 412 * Returns the selected localized message for the initialized resource bundle and locale.<p> 413 * 414 * Convenience method for messages with one argument.<p> 415 * 416 * @param key the message key 417 * @param arg0 the message argument 418 * 419 * @return the selected localized message for the initialized resource bundle and locale 420 */ 421 public String key(String key, Object arg0) { 422 423 return key(key, new Object[] {arg0}); 424 } 425 426 /** 427 * Returns the selected localized message for the initialized resource bundle and locale.<p> 428 * 429 * Convenience method for messages with two arguments.<p> 430 * 431 * @param key the message key 432 * @param arg0 the first message argument 433 * @param arg1 the second message argument 434 * 435 * @return the selected localized message for the initialized resource bundle and locale 436 */ 437 public String key(String key, Object arg0, Object arg1) { 438 439 return key(key, new Object[] {arg0, arg1}); 440 } 441 442 /** 443 * Returns the selected localized message for the initialized resource bundle and locale.<p> 444 * 445 * Convenience method for messages with three arguments.<p> 446 * 447 * @param key the message key 448 * @param arg0 the first message argument 449 * @param arg1 the second message argument 450 * @param arg2 the third message argument 451 * 452 * @return the selected localized message for the initialized resource bundle and locale 453 */ 454 public String key(String key, Object arg0, Object arg1, Object arg2) { 455 456 return key(key, new Object[] {arg0, arg1, arg2}); 457 } 458 459 /** 460 * Returns the selected localized message for the initialized resource bundle and locale.<p> 461 * 462 * If the key was found in the bundle, it will be formatted using 463 * a <code>{@link MessageFormat}</code> using the provided parameters.<p> 464 * 465 * If the key was not found in the bundle, the return value is 466 * <code>"??? " + keyName + " ???"</code>. This will also be returned 467 * if the bundle was not properly initialized first. 468 * 469 * @param key the message key 470 * @param args the message arguments 471 * 472 * @return the selected localized message for the initialized resource bundle and locale 473 */ 474 public String key(String key, Object[] args) { 475 476 if ((args == null) || (args.length == 0)) { 477 // no parameters available, use simple key method 478 return key(key); 479 } 480 481 String result = key(key, true); 482 if (result == null) { 483 // key was not found 484 result = formatUnknownKey(key); 485 } else { 486 // key was found in the bundle - create and apply the formatter 487 MessageFormat formatter = new MessageFormat(result, m_locale); 488 result = formatter.format(args); 489 } 490 // return the result 491 return result; 492 } 493 494 /** 495 * Returns the localized resource string for a given message key.<p> 496 * 497 * If the key was not found in the bundle, the provided default value 498 * is returned.<p> 499 * 500 * @param keyName the key for the desired string 501 * @param defaultValue the default value in case the key does not exist in the bundle 502 * @return the resource string for the given key it it exists, or the given default if not 503 */ 504 public String keyDefault(String keyName, String defaultValue) { 505 506 String result = key(keyName, true); 507 return (result == null) ? defaultValue : result; 508 } 509 510 /** 511 * Returns the localized resource string for a given message key, 512 * treating all values appended with "|" as replacement parameters.<p> 513 * 514 * If the key was found in the bundle, it will be formatted using 515 * a <code>{@link MessageFormat}</code> using the provided parameters. 516 * The parameters have to be appended to the key separated by a "|". 517 * For example, the keyName <code>error.message|First|Second</code> 518 * would use the key <code>error.message</code> with the parameters 519 * <code>First</code> and <code>Second</code>. This would be the same as calling 520 * <code>{@link CmsMessages#key(String, Object[])}</code>.<p> 521 * 522 * If no parameters are appended with "|", this is the same as calling 523 * <code>{@link CmsMessages#key(String)}</code>.<p> 524 * 525 * If the key was not found in the bundle, the return value is 526 * <code>"??? " + keyName + " ???"</code>. This will also be returned 527 * if the bundle was not properly initialized first. 528 * 529 * @param keyName the key for the desired string, optinally containing parameters appended with a "|" 530 * @return the resource string for the given key 531 * 532 * @see #key(String, Object[]) 533 * @see #key(String) 534 */ 535 public String keyWithParams(String keyName) { 536 537 if (keyName.indexOf('|') == -1) { 538 // no separator found, key has no parameters 539 return key(keyName, false); 540 } else { 541 // this key contains parameters 542 String[] values = CmsStringUtil.splitAsArray(keyName, '|'); 543 String cutKeyName = values[0]; 544 String[] params = new String[values.length - 1]; 545 System.arraycopy(values, 1, params, 0, params.length); 546 return key(cutKeyName, params); 547 } 548 } 549 550 /** 551 * @see java.lang.Object#toString() 552 */ 553 @Override 554 public String toString() { 555 556 StringBuffer result = new StringBuffer(); 557 558 result.append('['); 559 result.append(this.getClass().getName()); 560 result.append(", baseName: "); 561 result.append(m_bundleName); 562 result.append(", locale: "); 563 result.append(getLocale()); 564 result.append(']'); 565 566 return result.toString(); 567 } 568 569 /** 570 * Returns the name of the resource bundle this object was initialized with.<p> 571 * 572 * @return the name of the resource bundle this object was initialized with 573 */ 574 protected String getBundleName() { 575 576 return m_bundleName; 577 } 578 579 /** 580 * Sets the bundleName.<p> 581 * 582 * @param bundleName the bundleName to set 583 */ 584 protected void setBundleName(String bundleName) { 585 586 m_bundleName = bundleName; 587 } 588 589 /** 590 * Sets the locale.<p> 591 * 592 * @param locale the locale to set 593 */ 594 protected void setLocale(Locale locale) { 595 596 m_locale = locale; 597 } 598 599 /** 600 * Sets the resource bundle.<p> 601 * 602 * @param resourceBundle the resource bundle to set 603 */ 604 protected void setResourceBundle(ResourceBundle resourceBundle) { 605 606 m_resourceBundle = resourceBundle; 607 } 608}