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 }