001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyrighted [year] [name of copyright owner]". 013 * 014 * Copyright 2007-2009 Sun Microsystems, Inc. 015 * Portions copyright 2011 ForgeRock AS 016 */ 017 018package org.forgerock.i18n; 019 020import java.io.Serializable; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Locale; 024 025/** 026 * A mutable sequence of localizable messages and their parameters. As messages 027 * are appended they are translated to their string representation for storage 028 * using the locale specified in the constructor. 029 * <p> 030 * Note that before you use this class you should consider whether it is 031 * appropriate. In general composing messages by appending message to each other 032 * may not produce a message that is formatted appropriately for all locales. 033 * <p> 034 * It is usually better to create messages by composition. In other words you 035 * should create a base message that contains one or more string argument 036 * specifiers (%s) and define other message objects to use as replacement 037 * variables. In this way language translators have a change to reformat the 038 * message for a particular locale if necessary. 039 * 040 * @see LocalizableMessage 041 */ 042public final class LocalizableMessageBuilder implements Appendable, 043 CharSequence, Serializable { 044 /** 045 * Generated serialization ID. 046 */ 047 private static final long serialVersionUID = -3292823563904285315L; 048 049 // Used internally to store appended messages. 050 private final List<LocalizableMessage> messages = new LinkedList<LocalizableMessage>(); 051 052 /** 053 * Creates a new message builder whose content is initially empty. 054 */ 055 public LocalizableMessageBuilder() { 056 // Nothing to do. 057 } 058 059 /** 060 * Creates a new message builder whose content is initially equal to the 061 * provided message. 062 * 063 * @param message 064 * The initial content of the message builder. 065 * @throws NullPointerException 066 * If {@code message} was {@code null}. 067 */ 068 public LocalizableMessageBuilder(final LocalizableMessage message) { 069 append(message); 070 } 071 072 /** 073 * Creates a new message builder whose content is initially equal to the 074 * provided message builder. 075 * 076 * @param builder 077 * The initial content of the message builder. 078 * @throws NullPointerException 079 * If {@code builder} was {@code null}. 080 */ 081 public LocalizableMessageBuilder(final LocalizableMessageBuilder builder) { 082 for (final LocalizableMessage message : builder.messages) { 083 this.messages.add(message); 084 } 085 } 086 087 /** 088 * Creates a new message builder whose content is initially equal to the 089 * {@code String} representation of the provided {@code Object}. 090 * 091 * @param object 092 * The initial content of the message builder, may be 093 * {@code null}. 094 */ 095 public LocalizableMessageBuilder(final Object object) { 096 append(object); 097 } 098 099 /** 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}