001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 028 package org.opends.messages; 029 030 import java.util.Locale; 031 import java.util.Formatter; 032 import java.util.Formattable; 033 import java.util.IllegalFormatException; 034 035 /** 036 * Renders sensitive textural strings. In most cases message are intended 037 * to render textural strings in a locale-sensitive manner although this 038 * class defines convenience methods for creating uninternationalized 039 * <code>Message</code> objects that render the same text regardless of 040 * the requested locale. 041 * 042 * This class implements <code>CharSequence</code> so that messages can 043 * be supplied as arguments to other messages. This way messages can 044 * be composed of fragments of other messages if necessary. 045 * 046 * @see org.opends.messages.MessageDescriptor 047 */ 048 @org.opends.server.types.PublicAPI( 049 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 050 mayInstantiate=true, 051 mayExtend=false, 052 mayInvoke=true) 053 public final class Message implements CharSequence, Formattable, 054 Comparable<Message> { 055 056 /** Represents an empty message string. */ 057 public static final Message EMPTY = Message.raw(""); 058 059 // Variable used to workaround a bug in AIX Java 1.6 060 // TODO: remove this code once the JDK issue referenced in 3077 is closed. 061 private final boolean isAIXPost5 = isAIXPost5(); 062 063 /** 064 * Creates an uninternationalized message that will render itself 065 * the same way regardless of the locale requested in 066 * <code>toString(Locale)</code>. The message will have a 067 * category of <code>Categore.USER_DEFINED</code> and a severity 068 * of <code>Severity.INFORMATION</code> 069 * 070 * Note that the types for <code>args</code> must be consistent with any 071 * argument specifiers appearing in <code>formatString</code> according 072 * to the rules of java.util.Formatter. A mismatch in type information 073 * will cause this message to render without argument substitution. 074 * 075 * Before using this method you should be sure that the message you 076 * are creating is locale sensitive. If so you should instead create 077 * a formal message. 078 * 079 * @param formatString of the message or the message itself if not 080 * arguments are necessary 081 * @param args any arguments for the format string 082 * @return a message object that will render the same in all locales; 083 * null if <code>formatString</code> is null 084 */ 085 static public Message raw(CharSequence formatString, Object... args) { 086 Message message = null; 087 if (formatString != null) { 088 message = new MessageDescriptor.Raw(formatString).get(args); 089 } 090 return message; 091 } 092 093 /** 094 * Creates an uninternationalized message that will render itself 095 * the same way regardless of the locale requested in 096 * <code>toString(Locale)</code>. 097 * 098 * Note that the types for <code>args</code> must be consistent with any 099 * argument specifiers appearing in <code>formatString</code> according 100 * to the rules of java.util.Formatter. A mismatch in type information 101 * will cause this message to render without argument substitution. 102 * 103 * Before using this method you should be sure that the message you 104 * are creating is locale sensitive. If so you should instead create 105 * a formal message. 106 * 107 * @param category of this message 108 * @param severity of this message 109 * @param formatString of the message or the message itself if not 110 * arguments are necessary 111 * @param args any arguments for the format string 112 * @return a message object that will render the same in all locales; 113 * null if <code>formatString</code> is null 114 */ 115 static public Message raw(Category category, Severity severity, 116 CharSequence formatString, Object... args) { 117 Message message = null; 118 if (formatString != null) { 119 MessageDescriptor.Raw md = 120 new MessageDescriptor.Raw(formatString, 121 category, 122 severity); 123 message = md.get(args); 124 } 125 return message; 126 } 127 128 /** 129 * Creates an uninternationalized message from the string representation 130 * of an object. 131 * 132 * Note that the types for <code>args</code> must be consistent with any 133 * argument specifiers appearing in <code>formatString</code> according 134 * to the rules of java.util.Formatter. A mismatch in type information 135 * will cause this message to render without argument substitution. 136 * 137 * @param object from which the message will be created 138 * @param arguments for message 139 * @return a message object that will render the same in all locales; 140 * null if <code>object</code> is null 141 */ 142 static public Message fromObject(Object object, Object... arguments) { 143 Message message = null; 144 if (object != null) { 145 CharSequence cs = object.toString(); 146 message = raw(cs, arguments); 147 } 148 return message; 149 } 150 151 /** 152 * Returns the string representation of the message in the default locale. 153 * @param message to stringify 154 * @return String representation of of <code>message</code> of null if 155 * <code>message</code> is null 156 */ 157 static public String toString(Message message) { 158 return message != null ? message.toString() : null; 159 } 160 161 /** Descriptor of this message. */ 162 private final MessageDescriptor descriptor; 163 164 /** Values used to replace argument specifiers in the format string. */ 165 private final Object[] args; 166 167 /** 168 * Gets the string representation of this message. 169 * @return String representation of this message 170 */ 171 public String toString() { 172 return toString(Locale.getDefault()); 173 } 174 175 /** 176 * Gets the string representation of this message appropriate for 177 * <code>locale</code>. 178 * @param locale for which the string representation 179 * will be returned 180 * @return String representation of this message 181 */ 182 public String toString(Locale locale) { 183 String s; 184 String fmt = descriptor.getFormatString(locale); 185 if (descriptor.requiresFormatter()) { 186 try { 187 // TODO: remove this code once the JDK issue referenced in 3077 is 188 // closed. 189 if (isAIXPost5) 190 { 191 // Java 6 in AIX Formatter does not handle properly Formattable 192 // arguments; this code is a workaround for the problem. 193 boolean changeType = false; 194 for (Object o : args) 195 { 196 if (o instanceof Formattable) 197 { 198 changeType = true; 199 break; 200 } 201 } 202 if (changeType) 203 { 204 Object[] newArgs = new Object[args.length]; 205 for (int i=0; i<args.length; i++) 206 { 207 if (args[i] instanceof Formattable) 208 { 209 newArgs[i] = args[i].toString(); 210 } 211 else 212 { 213 newArgs[i] = args[i]; 214 } 215 } 216 s = new Formatter(locale).format(locale, fmt, newArgs).toString(); 217 } 218 else 219 { 220 s = new Formatter(locale).format(locale, fmt, args).toString(); 221 } 222 } 223 else 224 { 225 s = new Formatter(locale).format(locale, fmt, args).toString(); 226 } 227 } catch (IllegalFormatException e) { 228 // This should not happend with any of our internal messages. 229 // However, this may happen for raw messages that have a 230 // mismatch between argument specifier type and argument type. 231 s = fmt; 232 } 233 } else { 234 s = fmt; 235 } 236 if (s == null) s = ""; 237 return s; 238 } 239 240 /** 241 * Gets the descriptor that holds descriptive information 242 * about this message. 243 * @return MessageDescriptor information 244 */ 245 public MessageDescriptor getDescriptor() { 246 return this.descriptor; 247 } 248 249 /** 250 * Returns the length of this message as rendered using the default 251 * locale. 252 * 253 * @return the number of <code>char</code>s in this message 254 */ 255 public int length() { 256 return length(Locale.getDefault()); 257 } 258 259 /** 260 * Returns the byte representation of this messages in the default 261 * locale. 262 * 263 * @return bytes for this message 264 */ 265 public byte[] getBytes() { 266 return toString().getBytes(); 267 } 268 269 /** 270 * Returns the <code>char</code> value at the specified index of 271 * this message rendered using the default locale. 272 * 273 * @param index the index of the <code>char</code> value to be returned 274 * 275 * @return the specified <code>char</code> value 276 * 277 * @throws IndexOutOfBoundsException 278 * if the <tt>index</tt> argument is negative or not less than 279 * <tt>length()</tt> 280 */ 281 public char charAt(int index) throws IndexOutOfBoundsException { 282 return charAt(Locale.getDefault(), index); 283 } 284 285 /** 286 * Returns a new <code>CharSequence</code> that is a subsequence 287 * of this message rendered using the default locale. 288 * The subsequence starts with the <code>char</code> 289 * value at the specified index and ends with the <code>char</code> 290 * value at index <tt>end - 1</tt>. The length (in <code>char</code>s) 291 * of the returned sequence is <tt>end - start</tt>, so if 292 * <tt>start == end</tt> then an empty sequence is returned. 293 * 294 * @param start the start index, inclusive 295 * @param end the end index, exclusive 296 * 297 * @return the specified subsequence 298 * 299 * @throws IndexOutOfBoundsException 300 * if <tt>start</tt> or <tt>end</tt> are negative, 301 * if <tt>end</tt> is greater than <tt>length()</tt>, 302 * or if <tt>start</tt> is greater than <tt>end</tt> 303 */ 304 public CharSequence subSequence(int start, int end) 305 throws IndexOutOfBoundsException 306 { 307 return subSequence(Locale.getDefault(), start, end); 308 } 309 310 /** 311 * Returns the length of this message as rendered using a specific 312 * locale. 313 * 314 * @param locale for which the rendering of this message will be 315 * used in determining the length 316 * @return the number of <code>char</code>s in this message 317 */ 318 public int length(Locale locale) { 319 return toString(locale).length(); 320 } 321 322 /** 323 * Returns the <code>char</code> value at the specified index of 324 * this message rendered using a specific. 325 * 326 * @param locale for which the rendering of this message will be 327 * used in determining the character 328 * @param index the index of the <code>char</code> value to be returned 329 * 330 * @return the specified <code>char</code> value 331 * 332 * @throws IndexOutOfBoundsException 333 * if the <tt>index</tt> argument is negative or not less than 334 * <tt>length()</tt> 335 */ 336 public char charAt(Locale locale, int index) 337 throws IndexOutOfBoundsException 338 { 339 return toString(locale).charAt(index); 340 } 341 342 /** 343 * Returns a new <code>CharSequence</code> that is a subsequence 344 * of this message rendered using a specific locale. 345 * The subsequence starts with the <code>char</code> 346 * value at the specified index and ends with the <code>char</code> 347 * value at index <tt>end - 1</tt>. The length (in <code>char</code>s) 348 * of the returned sequence is <tt>end - start</tt>, so if 349 * <tt>start == end</tt> then an empty sequence is returned. 350 * 351 * @param locale for which the rendering of this message will be 352 * used in determining the character 353 * @param start the start index, inclusive 354 * @param end the end index, exclusive 355 * 356 * @return the specified subsequence 357 * 358 * @throws IndexOutOfBoundsException 359 * if <tt>start</tt> or <tt>end</tt> are negative, 360 * if <tt>end</tt> is greater than <tt>length()</tt>, 361 * or if <tt>start</tt> is greater than <tt>end</tt> 362 */ 363 public CharSequence subSequence(Locale locale, int start, int end) 364 throws IndexOutOfBoundsException 365 { 366 return toString(locale).subSequence(start, end); 367 } 368 369 /** 370 * Formats the object using the provided {@link Formatter formatter}. 371 * 372 * @param formatter 373 * The {@link Formatter formatter}. 374 * 375 * @param flags 376 * The flags modify the output format. The value is interpreted as 377 * a bitmask. Any combination of the following flags may be set: 378 * {@link java.util.FormattableFlags#LEFT_JUSTIFY}, {@link 379 * java.util.FormattableFlags#UPPERCASE}, and {@link 380 * java.util.FormattableFlags#ALTERNATE}. If no flags are set, the 381 * default formatting of the implementing class will apply. 382 * 383 * @param width 384 * The minimum number of characters to be written to the output. 385 * If the length of the converted value is less than the 386 * <tt>width</tt> then the output will be padded by 387 * <tt>' '</tt> until the total number of characters 388 * equals width. The padding is at the beginning by default. If 389 * the {@link java.util.FormattableFlags#LEFT_JUSTIFY} flag is set 390 * then the padding will be at the end. If <tt>width</tt> is 391 * <tt>-1</tt> then there is no minimum. 392 * 393 * @param precision 394 * The maximum number of characters to be written to the output. 395 * The precision is applied before the width, thus the output will 396 * be truncated to <tt>precision</tt> characters even if the 397 * <tt>width</tt> is greater than the <tt>precision</tt>. If 398 * <tt>precision</tt> is <tt>-1</tt> then there is no explicit 399 * limit on the number of characters. 400 * 401 * @throws IllegalFormatException 402 * If any of the parameters are invalid. For specification of all 403 * possible formatting errors, see the <a 404 * href="../util/Formatter.html#detail">Details</a> section of the 405 * formatter class specification. 406 */ 407 public void formatTo(Formatter formatter, int flags, 408 int width, int precision) 409 throws IllegalFormatException 410 { 411 // Ignores flags, width and precission for now. 412 // see javadoc for Formattable 413 Locale l = formatter.locale(); 414 formatter.format(l, descriptor.getFormatString(l), args); 415 } 416 417 418 /** 419 * Creates a parameterized instance. See the class header 420 * for instructions on how to create messages outside this 421 * package. 422 * @param descriptor for this message 423 * @param args arguments for replacing specifiers in the 424 * message's format string 425 */ 426 Message(MessageDescriptor descriptor, Object... args) { 427 this.descriptor = descriptor; 428 this.args = args; 429 } 430 431 /** 432 * Compares this object with the specified object for order. Returns a 433 * negative integer, zero, or a positive integer as this object is less 434 * than, equal to, or greater than the specified object. 435 * 436 * @param o the object to be compared. 437 * @return a negative integer, zero, or a positive integer as this object 438 * is less than, equal to, or greater than the specified object. 439 */ 440 public int compareTo(Message o) { 441 return toString().compareTo(o.toString()); 442 } 443 444 /** 445 * Indicates whether some other message is "equal to" this one. Messages 446 * are considered equal if their string representation in the default 447 * locale are equal. 448 * 449 * @param o the reference object with which to compare. 450 * @return <code>true</code> if this object is the same as the obj 451 * argument; <code>false</code> otherwise. 452 * @see #hashCode() 453 * @see java.util.Hashtable 454 */ 455 public boolean equals(Object o) { 456 if (this == o) return true; 457 if (o == null || getClass() != o.getClass()) return false; 458 459 Message message = (Message) o; 460 461 return toString().equals(message.toString()); 462 } 463 464 /** 465 * Returns a hash code value for the object. 466 * 467 * @return a hash code value for this object. 468 * @see java.lang.Object#equals(java.lang.Object) 469 * @see java.util.Hashtable 470 */ 471 public int hashCode() { 472 int result; 473 result = 31 * toString().hashCode(); 474 return result; 475 } 476 477 478 // TODO: remove this code once the JDK issue referenced in 3077 is closed. 479 /** 480 * Returns whether we are running post 1.5 on AIX or not. 481 * @return <CODE>true</CODE> if we are running post 1.5 on AIX and 482 * <CODE>false</CODE> otherwise. 483 */ 484 private boolean isAIXPost5() 485 { 486 boolean isJDK15 = false; 487 try 488 { 489 String javaRelease = System.getProperty ("java.version"); 490 isJDK15 = javaRelease.startsWith("1.5"); 491 } 492 catch (Throwable t) 493 { 494 System.err.println("Cannot get the java version: " + t); 495 } 496 boolean isAIX = "aix".equalsIgnoreCase(System.getProperty("os.name")); 497 return !isJDK15 && isAIX; 498 } 499 500 }