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>'&nbsp;&nbsp;'</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    }