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 2007-2008 Sun Microsystems, Inc.
026     */
027    
028    package org.opends.messages;
029    
030    import java.util.Locale;
031    import java.util.List;
032    import java.util.LinkedList;
033    import java.io.Serializable;
034    
035    /**
036     * A builder used specifically for messages.  As messages are
037     * appended they are translated to their string representation
038     * for storage using the locale specified in the constructor.
039     *
040     * Note that before you use this class you should consider whether
041     * it is appropriate.  In general composing messages by appending
042     * message to each other may not produce a message that is
043     * formatted appropriately for all locales.  It is usually better
044     * to create messages by composition.  In other words you should
045     * create a base message that contains one or more string argument
046     * specifiers (%s) and define other message objects to use as
047     * replacement variables.  In this way language translators have
048     * a change to reformat the message for a particular locale if
049     * necessary.
050     */
051    @org.opends.server.types.PublicAPI(
052        stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
053        mayInstantiate=true,
054        mayExtend=false,
055        mayInvoke=true)
056    public final class MessageBuilder implements Appendable, CharSequence,
057        Serializable
058    {
059    
060      private static final long serialVersionUID = -3292823563904285315L;
061    
062      /** Used internally to store appended messages. */
063      private final StringBuilder sb = new StringBuilder();
064    
065      /** Used internally to store appended messages. */
066      private final List<Message> messages = new LinkedList<Message>();
067    
068      /** Used to render the string representation of appended messages. */
069      private final Locale locale;
070    
071      /**
072       * Constructs an instance that will build messages
073       * in the default locale.
074       */
075      public MessageBuilder() {
076        this(Locale.getDefault());
077      }
078    
079      /**
080       * Constructs an instance that will build messages
081       * in the default locale having an initial message.
082       *
083       * @param message initial message
084       */
085      public MessageBuilder(Message message) {
086        this(Locale.getDefault());
087        append(message);
088      }
089    
090      /**
091       * Constructs an instance that will build messages
092       * in the default locale having an initial message.
093       *
094       * @param message initial message
095       */
096      public MessageBuilder(String message) {
097        this(Locale.getDefault());
098        append(message);
099      }
100    
101      /**
102       * Constructs an instance from another <code>MessageBuilder</code>.
103       *
104       * @param mb from which to construct a new message builder
105       */
106      public MessageBuilder(MessageBuilder mb) {
107        for (Message msg : mb.messages) {
108          this.messages.add(msg);
109        }
110        this.sb.append(sb);
111        this.locale = mb.locale;
112      }
113    
114      /**
115       * Constructs an instance that will build messages
116       * in a specified locale.
117       *
118       * @param locale used for translating appended messages
119       */
120      public MessageBuilder(Locale locale) {
121        this.locale = locale;
122      }
123    
124      /**
125       * Append a message to this builder.  The string
126       * representation of the locale specifed in the
127       * constructor will be stored in this builder.
128       *
129       * @param message to be appended
130       * @return reference to this builder
131       */
132      public MessageBuilder append(Message message) {
133        if (message != null) {
134          sb.append(message.toString(locale));
135          messages.add(message);
136        }
137        return this;
138      }
139    
140      /**
141       * Append an integer to this builder.
142       *
143       * @param number to append
144       * @return reference to this builder
145       */
146      public MessageBuilder append(int number) {
147        append(String.valueOf(number));
148        return this;
149      }
150    
151      /**
152       * Append an object to this builder.
153       *
154       * @param object to append
155       * @return reference to this builder
156       */
157      public MessageBuilder append(Object object) {
158        if (object != null) {
159          append(String.valueOf(object));
160        }
161        return this;
162      }
163    
164    
165      /**
166       * Append a string to this builder.
167       *
168       * @param cs to append
169       * @return reference to this builder
170       */
171      public MessageBuilder append(CharSequence cs) {
172        if (cs != null) {
173          sb.append(cs);
174          if (cs instanceof Message) {
175            messages.add((Message)cs);
176          } else {
177            messages.add(Message.raw(cs));
178          }
179        }
180        return this;
181      }
182    
183      /**
184       * Appends a subsequence of the specified character sequence to this
185       * <tt>Appendable</tt>.
186       *
187       * <p> An invocation of this method of the form <tt>out.append(csq, start,
188       * end)</tt> when <tt>csq</tt> is not <tt>null</tt>, behaves in
189       * exactly the same way as the invocation
190       *
191       * <pre>
192       *     out.append(csq.subSequence(start, end)) </pre>
193       *
194       * @param  csq
195       *         The character sequence from which a subsequence will be
196       *         appended.  If <tt>csq</tt> is <tt>null</tt>, then characters
197       *         will be appended as if <tt>csq</tt> contained the four
198       *         characters <tt>"null"</tt>.
199       *
200       * @param  start
201       *         The index of the first character in the subsequence
202       *
203       * @param  end
204       *         The index of the character following the last character in the
205       *         subsequence
206       *
207       * @return  A reference to this <tt>Appendable</tt>
208       *
209       * @throws  IndexOutOfBoundsException
210       *          If <tt>start</tt> or <tt>end</tt> are negative, <tt>start</tt>
211       *          is greater than <tt>end</tt>, or <tt>end</tt> is greater than
212       *          <tt>csq.length()</tt>
213       */
214      public MessageBuilder append(CharSequence csq, int start, int end)
215        throws IndexOutOfBoundsException
216      {
217        return append(csq.subSequence(start, end));
218      }
219    
220      /**
221       * Appends the specified character to this <tt>Appendable</tt>.
222       *
223       * @param  c
224       *         The character to append
225       *
226       * @return  A reference to this <tt>Appendable</tt>
227       */
228      public MessageBuilder append(char c) {
229        return append(String.valueOf(c));
230      }
231    
232      /**
233       * Returns a string containing the characters in this sequence in the same
234       * order as this sequence.  The length of the string will be the length of
235       * this sequence.
236       *
237       * @return  a string consisting of exactly this sequence of characters
238       */
239      public String toString() {
240        return sb.toString();
241      }
242    
243      /**
244       * Returns a string representation of the appended content
245       * in the specific locale.  Only <code>Message</code>s
246       * appended to this builder are rendered in the requested
247       * locale.  Raw strings appended to this buffer are not
248       * translated to different locale.
249       *
250       * @param locale requested
251       * @return String representation
252       */
253      public String toString(Locale locale) {
254        StringBuilder sb = new StringBuilder();
255        for (Message m : messages) {
256          sb.append(m.toString(locale));
257        }
258        return sb.toString();
259      }
260    
261      /**
262       * Returns a raw message representation of the appended content.
263       * <p>
264       * If the first object appended to this <code>MessageBuilder</code>
265       * was a <code>Message</code> then the returned message will
266       * inherit its category and severity. Otherwise the returned message
267       * will have category {@link org.opends.messages.Category#USER_DEFINED}
268       *  and severity {@link org.opends.messages.Severity#INFORMATION}.
269       *
270       * @return Message raw message representing builder content
271       */
272      public Message toMessage() {
273        StringBuffer fmtString = new StringBuffer();
274        for (int i = 0; i < messages.size(); i++) {
275          fmtString.append("%s");
276        }
277    
278        if (messages.isEmpty()) {
279          return Message.raw(fmtString, messages.toArray());
280        } else {
281          // Inherit the category and severity of the first message.
282          MessageDescriptor md = messages.get(0).getDescriptor();
283          return Message.raw(md.getCategory(), md.getSeverity(), fmtString,
284              messages.toArray());
285        }
286      }
287    
288      /**
289       * Returns the length of the string representation of this builder
290       * using the default locale.
291       *
292       * @return  the number of <code>char</code>s in this message
293       */
294      public int length() {
295        return length(Locale.getDefault());
296      }
297    
298      /**
299       * Returns the <code>char</code> value at the specified index of
300       * the string representation of this builder using the default locale.
301       *
302       * @param   index   the index of the <code>char</code> value to be returned
303       *
304       * @return  the specified <code>char</code> value
305       *
306       * @throws  IndexOutOfBoundsException
307       *          if the <tt>index</tt> argument is negative or not less than
308       *          <tt>length()</tt>
309       */
310      public char charAt(int index) throws IndexOutOfBoundsException {
311        return charAt(Locale.getDefault(), index);
312      }
313    
314      /**
315       * Returns a new <code>CharSequence</code> that is a subsequence of
316       * the string representation of this builder using the default locale.
317       * The subsequence starts with the <code>char</code>
318       * value at the specified index and ends with the <code>char</code>
319       * value at index <tt>end - 1</tt>.  The length (in <code>char</code>s)
320       * of the returned sequence is <tt>end - start</tt>, so if
321       * <tt>start == end</tt> then an empty sequence is returned.
322       *
323       * @param   start   the start index, inclusive
324       * @param   end     the end index, exclusive
325       *
326       * @return  the specified subsequence
327       *
328       * @throws  IndexOutOfBoundsException
329       *          if <tt>start</tt> or <tt>end</tt> are negative,
330       *          if <tt>end</tt> is greater than <tt>length()</tt>,
331       *          or if <tt>start</tt> is greater than <tt>end</tt>
332       */
333      public CharSequence subSequence(int start, int end)
334              throws IndexOutOfBoundsException
335      {
336        return subSequence(Locale.getDefault(), start, end);
337      }
338    
339      /**
340       * Returns the length of the string representation of this builder
341       * using a specific locale.
342       *
343       * @param   locale for which the rendering of this message will be
344       *          used in determining the length
345       * @return  the number of <code>char</code>s in this message
346       */
347      public int length(Locale locale) {
348        return toString(locale).length();
349      }
350    
351      /**
352       * Returns the <code>char</code> value at the specified index of
353       * the string representation of this builder using a specific locale.
354       *
355       * @param   locale for which the rendering of this message will be
356       *          used in determining the character
357       * @param   index   the index of the <code>char</code> value to be returned
358       *
359       * @return  the specified <code>char</code> value
360       *
361       * @throws  IndexOutOfBoundsException
362       *          if the <tt>index</tt> argument is negative or not less than
363       *          <tt>length()</tt>
364       */
365      public char charAt(Locale locale, int index)
366              throws IndexOutOfBoundsException
367      {
368        return toString(locale).charAt(index);
369      }
370    
371      /**
372       * Returns a new <code>CharSequence</code> that is a subsequence of
373       * the string representation of this builder using a specific locale.
374       * The subsequence starts with the <code>char</code>
375       * value at the specified index and ends with the <code>char</code>
376       * value at index <tt>end - 1</tt>.  The length (in <code>char</code>s)
377       * of the returned sequence is <tt>end - start</tt>, so if
378       * <tt>start == end</tt> then an empty sequence is returned.
379       *
380       * @param   locale for which the rendering of this message will be
381       *          used in determining the character
382       * @param   start   the start index, inclusive
383       * @param   end     the end index, exclusive
384       *
385       * @return  the specified subsequence
386       *
387       * @throws  IndexOutOfBoundsException
388       *          if <tt>start</tt> or <tt>end</tt> are negative,
389       *          if <tt>end</tt> is greater than <tt>length()</tt>,
390       *          or if <tt>start</tt> is greater than <tt>end</tt>
391       */
392      public CharSequence subSequence(Locale locale, int start, int end)
393        throws IndexOutOfBoundsException
394      {
395        return toString(locale).subSequence(start, end);
396      }
397    
398    
399    }