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 }