View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyrighted [year] [name of copyright owner]".
13   *
14   *      Copyright 2007-2009 Sun Microsystems, Inc.
15   *      Portions copyright 2011 ForgeRock AS
16   */
17  
18  package org.forgerock.i18n;
19  
20  import java.io.Serializable;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.Locale;
24  
25  /**
26   * A mutable sequence of localizable messages and their parameters. As messages
27   * are appended they are translated to their string representation for storage
28   * using the locale specified in the constructor.
29   * <p>
30   * Note that before you use this class you should consider whether it is
31   * appropriate. In general composing messages by appending message to each other
32   * may not produce a message that is formatted appropriately for all locales.
33   * <p>
34   * It is usually better to create messages by composition. In other words you
35   * should create a base message that contains one or more string argument
36   * specifiers (%s) and define other message objects to use as replacement
37   * variables. In this way language translators have a change to reformat the
38   * message for a particular locale if necessary.
39   *
40   * @see LocalizableMessage
41   */
42  public final class LocalizableMessageBuilder implements Appendable,
43          CharSequence, Serializable {
44      /**
45       * Generated serialization ID.
46       */
47      private static final long serialVersionUID = -3292823563904285315L;
48  
49      // Used internally to store appended messages.
50      private final List<LocalizableMessage> messages = new LinkedList<LocalizableMessage>();
51  
52      /**
53       * Creates a new message builder whose content is initially empty.
54       */
55      public LocalizableMessageBuilder() {
56          // Nothing to do.
57      }
58  
59      /**
60       * Creates a new message builder whose content is initially equal to the
61       * provided message.
62       *
63       * @param message
64       *            The initial content of the message builder.
65       * @throws NullPointerException
66       *             If {@code message} was {@code null}.
67       */
68      public LocalizableMessageBuilder(final LocalizableMessage message) {
69          append(message);
70      }
71  
72      /**
73       * Creates a new message builder whose content is initially equal to the
74       * provided message builder.
75       *
76       * @param builder
77       *            The initial content of the message builder.
78       * @throws NullPointerException
79       *             If {@code builder} was {@code null}.
80       */
81      public LocalizableMessageBuilder(final LocalizableMessageBuilder builder) {
82          for (final LocalizableMessage message : builder.messages) {
83              this.messages.add(message);
84          }
85      }
86  
87      /**
88       * Creates a new message builder whose content is initially equal to the
89       * {@code String} representation of the provided {@code Object}.
90       *
91       * @param object
92       *            The initial content of the message builder, may be
93       *            {@code null}.
94       */
95      public LocalizableMessageBuilder(final Object object) {
96          append(object);
97      }
98  
99      /**
100      * Appends the provided character to this message builder.
101      *
102      * @param c
103      *            The character to be appended.
104      * @return A reference to this message builder.
105      */
106     public LocalizableMessageBuilder append(final char c) {
107         return append(LocalizableMessage.valueOf(c));
108     }
109 
110     /**
111      * Appends the provided character sequence to this message builder.
112      *
113      * @param cs
114      *            The character sequence to be appended.
115      * @return A reference to this message builder.
116      * @throws NullPointerException
117      *             If {@code cs} was {@code null}.
118      */
119     public LocalizableMessageBuilder append(final CharSequence cs) {
120         if (cs == null) {
121             throw new NullPointerException("cs was null");
122         }
123 
124         return append((Object) cs);
125     }
126 
127     /**
128      * Appends a subsequence of the provided character sequence to this message
129      * builder.
130      * <p>
131      * An invocation of this method of the form {@code append(cs, start, end)},
132      * behaves in exactly the same way as the invocation
133      *
134      * <pre>
135      * append(cs.subSequence(start, end))
136      * </pre>
137      *
138      * @param cs
139      *            The character sequence to be appended.
140      * @param start
141      *            The index of the first character in the subsequence.
142      * @param end
143      *            The index of the character following the last character in the
144      *            subsequence.
145      * @return A reference to this message builder.
146      * @throws IndexOutOfBoundsException
147      *             If {@code start} or {@code end} are negative, {@code start}
148      *             is greater than {@code end}, or {@code end} is greater than
149      *             {@code csq.length()}.
150      * @throws NullPointerException
151      *             If {@code cs} was {@code null}.
152      */
153     public LocalizableMessageBuilder append(final CharSequence cs,
154             final int start, final int end) {
155         return append(cs.subSequence(start, end));
156     }
157 
158     /**
159      * Appends the provided integer to this message builder.
160      *
161      * @param value
162      *            The integer to be appended.
163      * @return A reference to this message builder.
164      */
165     public LocalizableMessageBuilder append(final int value) {
166         return append(LocalizableMessage.valueOf(value));
167     }
168 
169     /**
170      * Appends the provided message to this message builder.
171      *
172      * @param message
173      *            The message to be appended.
174      * @return A reference to this message builder.
175      * @throws NullPointerException
176      *             If {@code message} was {@code null}.
177      */
178     public LocalizableMessageBuilder append(final LocalizableMessage message) {
179         if (message == null) {
180             throw new NullPointerException("message was null");
181         }
182 
183         messages.add(message);
184         return this;
185     }
186 
187     /**
188      * Appends the {@code String} representation of the provided {@code Object}
189      * to this message builder.
190      *
191      * @param object
192      *            The object to be appended, may be {@code null}.
193      * @return A reference to this message builder.
194      */
195     public LocalizableMessageBuilder append(final Object object) {
196         return append(LocalizableMessage.valueOf(object));
197     }
198 
199     /**
200      * Returns the {@code char} value at the specified index of the
201      * {@code String} representation of this message builder in the default
202      * locale.
203      *
204      * @param index
205      *            The index of the {@code char} value to be returned.
206      * @return The specified {@code char} value.
207      * @throws IndexOutOfBoundsException
208      *             If the {@code index} argument is negative or not less than
209      *             {@code length()}.
210      */
211     public char charAt(final int index) {
212         return charAt(Locale.getDefault(), index);
213     }
214 
215     /**
216      * Returns the {@code char} value at the specified index of the
217      * {@code String} representation of this message builder in the specified
218      * locale.
219      *
220      * @param locale
221      *            The locale.
222      * @param index
223      *            The index of the {@code char} value to be returned.
224      * @return The specified {@code char} value.
225      * @throws IndexOutOfBoundsException
226      *             If the {@code index} argument is negative or not less than
227      *             {@code length()}.
228      * @throws NullPointerException
229      *             If {@code locale} was {@code null}.
230      */
231     public char charAt(final Locale locale, final int index) {
232         return toString(locale).charAt(index);
233     }
234 
235     /**
236      * Returns the length of the {@code String} representation of this message
237      * builder in the default locale.
238      *
239      * @return The length of the {@code String} representation of this message
240      *         builder in the default locale.
241      */
242     public int length() {
243         return length(Locale.getDefault());
244     }
245 
246     /**
247      * Returns the length of the {@code String} representation of this message
248      * builder in the specified locale.
249      *
250      * @param locale
251      *            The locale.
252      * @return The length of the {@code String} representation of this message
253      *         builder in the specified locale.
254      * @throws NullPointerException
255      *             If {@code locale} was {@code null}.
256      */
257     public int length(final Locale locale) {
258         return toString(locale).length();
259     }
260 
261     /**
262      * Returns a new {@code CharSequence} which is a subsequence of the
263      * {@code String} representation of this message builder in the default
264      * locale. The subsequence starts with the {@code char} value at the
265      * specified index and ends with the {@code char} value at index
266      * {@code end - 1} . The length (in {@code char}s) of the returned sequence
267      * is {@code end - start}, so if {@code start == end} then an empty sequence
268      * is returned.
269      *
270      * @param start
271      *            The start index, inclusive.
272      * @param end
273      *            The end index, exclusive.
274      * @return The specified subsequence.
275      * @throws IndexOutOfBoundsException
276      *             If {@code start} or {@code end} are negative, if {@code end}
277      *             is greater than {@code length()}, or if {@code start} is
278      *             greater than {@code end}.
279      */
280     public CharSequence subSequence(final int start, final int end) {
281         return subSequence(Locale.getDefault(), start, end);
282     }
283 
284     /**
285      * Returns a new {@code CharSequence} which is a subsequence of the
286      * {@code String} representation of this message builder in the specified
287      * locale. The subsequence starts with the {@code char} value at the
288      * specified index and ends with the {@code char} value at index
289      * {@code end - 1} . The length (in {@code char}s) of the returned sequence
290      * is {@code end - start}, so if {@code start == end} then an empty sequence
291      * is returned.
292      *
293      * @param locale
294      *            The locale.
295      * @param start
296      *            The start index, inclusive.
297      * @param end
298      *            The end index, exclusive.
299      * @return The specified subsequence.
300      * @throws IndexOutOfBoundsException
301      *             If {@code start} or {@code end} are negative, if {@code end}
302      *             is greater than {@code length()}, or if {@code start} is
303      *             greater than {@code end}.
304      * @throws NullPointerException
305      *             If {@code locale} was {@code null}.
306      */
307     public CharSequence subSequence(final Locale locale, final int start,
308             final int end) {
309         return toString(locale).subSequence(start, end);
310     }
311 
312     /**
313      * Returns the {@link LocalizableMessage} representation of this message
314      * builder. Subsequent changes to this message builder will not modify the
315      * returned {@code LocalizableMessage}.
316      *
317      * @return The {@code LocalizableMessage} representation of this message
318      *         builder.
319      */
320     public LocalizableMessage toMessage() {
321         if (messages.isEmpty()) {
322             return LocalizableMessage.EMPTY;
323         }
324 
325         final int sz = messages.size();
326         final StringBuffer fmtString = new StringBuffer(sz * 2);
327         for (int i = 0; i < sz; i++) {
328             fmtString.append("%s");
329         }
330 
331         return LocalizableMessage.raw(fmtString, messages.toArray());
332     }
333 
334     /**
335      * Returns the {@code String} representation of this message builder in the
336      * default locale.
337      *
338      * @return The {@code String} representation of this message builder.
339      */
340     @Override
341     public String toString() {
342         return toString(Locale.getDefault());
343     }
344 
345     /**
346      * Returns the {@code String} representation of this message builder in the
347      * specified locale.
348      *
349      * @param locale
350      *            The locale.
351      * @return The {@code String} representation of this message builder.
352      * @throws NullPointerException
353      *             If {@code locale} was {@code null}.
354      */
355     public String toString(final Locale locale) {
356         final StringBuilder builder = new StringBuilder();
357         for (final LocalizableMessage message : messages) {
358             builder.append(message.toString(locale));
359         }
360         return builder.toString();
361     }
362 
363 }