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 }