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 }