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 }