blob: b16fefe646e7a5c841ffc6b68758c071ea195373 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26/*
27 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
28 * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
29 *
30 * The original version of this source code and documentation is copyrighted
31 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
32 * materials are provided under terms of a License Agreement between Taligent
33 * and Sun. This technology is protected by multiple US and International
34 * patents. This notice and attribution to Taligent may not be removed.
35 * Taligent is a registered trademark of Taligent, Inc.
36 *
37 */
38
39package java.text;
40
41import java.io.InvalidObjectException;
42import java.io.IOException;
43import java.io.ObjectInputStream;
44import java.text.DecimalFormat;
45import java.util.ArrayList;
46import java.util.Arrays;
47import java.util.Date;
48import java.util.List;
49import java.util.Locale;
50
51
52/**
53 * <code>MessageFormat</code> provides a means to produce concatenated
54 * messages in a language-neutral way. Use this to construct messages
55 * displayed for end users.
56 *
57 * <p>
58 * <code>MessageFormat</code> takes a set of objects, formats them, then
59 * inserts the formatted strings into the pattern at the appropriate places.
60 *
61 * <p>
62 * <strong>Note:</strong>
63 * <code>MessageFormat</code> differs from the other <code>Format</code>
64 * classes in that you create a <code>MessageFormat</code> object with one
65 * of its constructors (not with a <code>getInstance</code> style factory
66 * method). The factory methods aren't necessary because <code>MessageFormat</code>
67 * itself doesn't implement locale specific behavior. Any locale specific
68 * behavior is defined by the pattern that you provide as well as the
69 * subformats used for inserted arguments.
70 *
71 * <h4><a name="patterns">Patterns and Their Interpretation</a></h4>
72 *
73 * <code>MessageFormat</code> uses patterns of the following form:
74 * <blockquote><pre>
75 * <i>MessageFormatPattern:</i>
76 * <i>String</i>
77 * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>
78 *
79 * <i>FormatElement:</i>
80 * { <i>ArgumentIndex</i> }
81 * { <i>ArgumentIndex</i> , <i>FormatType</i> }
82 * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
83 *
84 * <i>FormatType: one of </i>
85 * number date time choice
86 *
87 * <i>FormatStyle:</i>
88 * short
89 * medium
90 * long
91 * full
92 * integer
93 * currency
94 * percent
95 * <i>SubformatPattern</i>
96 *
97 * <i>String:</i>
98 * <i>StringPart<sub>opt</sub></i>
99 * <i>String</i> <i>StringPart</i>
100 *
101 * <i>StringPart:</i>
102 * ''
103 * ' <i>QuotedString</i> '
104 * <i>UnquotedString</i>
105 *
106 * <i>SubformatPattern:</i>
107 * <i>SubformatPatternPart<sub>opt</sub></i>
108 * <i>SubformatPattern</i> <i>SubformatPatternPart</i>
109 *
110 * <i>SubFormatPatternPart:</i>
111 * ' <i>QuotedPattern</i> '
112 * <i>UnquotedPattern</i>
113 * </pre></blockquote>
114 *
115 * <p>
116 * Within a <i>String</i>, <code>"''"</code> represents a single
117 * quote. A <i>QuotedString</i> can contain arbitrary characters
118 * except single quotes; the surrounding single quotes are removed.
119 * An <i>UnquotedString</i> can contain arbitrary characters
120 * except single quotes and left curly brackets. Thus, a string that
121 * should result in the formatted message "'{0}'" can be written as
122 * <code>"'''{'0}''"</code> or <code>"'''{0}'''"</code>.
123 * <p>
124 * Within a <i>SubformatPattern</i>, different rules apply.
125 * A <i>QuotedPattern</i> can contain arbitrary characters
126 * except single quotes; but the surrounding single quotes are
127 * <strong>not</strong> removed, so they may be interpreted by the
128 * subformat. For example, <code>"{1,number,$'#',##}"</code> will
129 * produce a number format with the pound-sign quoted, with a result
130 * such as: "$#31,45".
131 * An <i>UnquotedPattern</i> can contain arbitrary characters
132 * except single quotes, but curly braces within it must be balanced.
133 * For example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code>
134 * are valid subformat patterns, but <code>"ab {0'}' de"</code> and
135 * <code>"ab } de"</code> are not.
136 * <p>
137 * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message
138 * format patterns unfortunately have shown to be somewhat confusing.
139 * In particular, it isn't always obvious to localizers whether single
140 * quotes need to be doubled or not. Make sure to inform localizers about
141 * the rules, and tell them (for example, by using comments in resource
142 * bundle source files) which strings will be processed by MessageFormat.
143 * Note that localizers may need to use single quotes in translated
144 * strings where the original version doesn't have them.
145 * </dl>
146 * <p>
147 * The <i>ArgumentIndex</i> value is a non-negative integer written
148 * using the digits '0' through '9', and represents an index into the
149 * <code>arguments</code> array passed to the <code>format</code> methods
150 * or the result array returned by the <code>parse</code> methods.
151 * <p>
152 * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create
153 * a <code>Format</code> instance for the format element. The following
154 * table shows how the values map to Format instances. Combinations not
155 * shown in the table are illegal. A <i>SubformatPattern</i> must
156 * be a valid pattern string for the Format subclass used.
157 * <p>
158 * <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances">
159 * <tr>
160 * <th id="ft">Format Type
161 * <th id="fs">Format Style
162 * <th id="sc">Subformat Created
163 * <tr>
164 * <td headers="ft"><i>(none)</i>
165 * <td headers="fs"><i>(none)</i>
166 * <td headers="sc"><code>null</code>
167 * <tr>
168 * <td headers="ft" rowspan=5><code>number</code>
169 * <td headers="fs"><i>(none)</i>
170 * <td headers="sc"><code>NumberFormat.getInstance(getLocale())</code>
171 * <tr>
172 * <td headers="fs"><code>integer</code>
173 * <td headers="sc"><code>NumberFormat.getIntegerInstance(getLocale())</code>
174 * <tr>
175 * <td headers="fs"><code>currency</code>
176 * <td headers="sc"><code>NumberFormat.getCurrencyInstance(getLocale())</code>
177 * <tr>
178 * <td headers="fs"><code>percent</code>
179 * <td headers="sc"><code>NumberFormat.getPercentInstance(getLocale())</code>
180 * <tr>
181 * <td headers="fs"><i>SubformatPattern</i>
182 * <td headers="sc"><code>new DecimalFormat(subformatPattern, DecimalFormatSymbols.getInstance(getLocale()))</code>
183 * <tr>
184 * <td headers="ft" rowspan=6><code>date</code>
185 * <td headers="fs"><i>(none)</i>
186 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
187 * <tr>
188 * <td headers="fs"><code>short</code>
189 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
190 * <tr>
191 * <td headers="fs"><code>medium</code>
192 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
193 * <tr>
194 * <td headers="fs"><code>long</code>
195 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
196 * <tr>
197 * <td headers="fs"><code>full</code>
198 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
199 * <tr>
200 * <td headers="fs"><i>SubformatPattern</i>
201 * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code>
202 * <tr>
203 * <td headers="ft" rowspan=6><code>time</code>
204 * <td headers="fs"><i>(none)</i>
205 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
206 * <tr>
207 * <td headers="fs"><code>short</code>
208 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
209 * <tr>
210 * <td headers="fs"><code>medium</code>
211 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
212 * <tr>
213 * <td headers="fs"><code>long</code>
214 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
215 * <tr>
216 * <td headers="fs"><code>full</code>
217 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
218 * <tr>
219 * <td headers="fs"><i>SubformatPattern</i>
220 * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code>
221 * <tr>
222 * <td headers="ft"><code>choice</code>
223 * <td headers="fs"><i>SubformatPattern</i>
224 * <td headers="sc"><code>new ChoiceFormat(subformatPattern)</code>
225 * </table>
226 * <p>
227 *
228 * <h4>Usage Information</h4>
229 *
230 * <p>
231 * Here are some examples of usage.
232 * In real internationalized programs, the message format pattern and other
233 * static strings will, of course, be obtained from resource bundles.
234 * Other parameters will be dynamically determined at runtime.
235 * <p>
236 * The first example uses the static method <code>MessageFormat.format</code>,
237 * which internally creates a <code>MessageFormat</code> for one-time use:
238 * <blockquote><pre>
239 * int planet = 7;
240 * String event = "a disturbance in the Force";
241 *
242 * String result = MessageFormat.format(
243 * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
244 * planet, new Date(), event);
245 * </pre></blockquote>
246 * The output is:
247 * <blockquote><pre>
248 * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
249 * </pre></blockquote>
250 *
251 * <p>
252 * The following example creates a <code>MessageFormat</code> instance that
253 * can be used repeatedly:
254 * <blockquote><pre>
255 * int fileCount = 1273;
256 * String diskName = "MyDisk";
257 * Object[] testArgs = {new Long(fileCount), diskName};
258 *
259 * MessageFormat form = new MessageFormat(
260 * "The disk \"{1}\" contains {0} file(s).");
261 *
262 * System.out.println(form.format(testArgs));
263 * </pre></blockquote>
264 * The output with different values for <code>fileCount</code>:
265 * <blockquote><pre>
266 * The disk "MyDisk" contains 0 file(s).
267 * The disk "MyDisk" contains 1 file(s).
268 * The disk "MyDisk" contains 1,273 file(s).
269 * </pre></blockquote>
270 *
271 * <p>
272 * For more sophisticated patterns, you can use a <code>ChoiceFormat</code>
273 * to produce correct forms for singular and plural:
274 * <blockquote><pre>
275 * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
276 * double[] filelimits = {0,1,2};
277 * String[] filepart = {"no files","one file","{0,number} files"};
278 * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
279 * form.setFormatByArgumentIndex(0, fileform);
280 *
281 * int fileCount = 1273;
282 * String diskName = "MyDisk";
283 * Object[] testArgs = {new Long(fileCount), diskName};
284 *
285 * System.out.println(form.format(testArgs));
286 * </pre></blockquote>
287 * The output with different values for <code>fileCount</code>:
288 * <blockquote><pre>
289 * The disk "MyDisk" contains no files.
290 * The disk "MyDisk" contains one file.
291 * The disk "MyDisk" contains 1,273 files.
292 * </pre></blockquote>
293 *
294 * <p>
295 * You can create the <code>ChoiceFormat</code> programmatically, as in the
296 * above example, or by using a pattern. See {@link ChoiceFormat}
297 * for more information.
298 * <blockquote><pre>
299 * form.applyPattern(
300 * "There {0,choice,0#are no files|1#is one file|1&lt;are {0,number,integer} files}.");
301 * </pre></blockquote>
302 *
303 * <p>
304 * <strong>Note:</strong> As we see above, the string produced
305 * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated as special;
306 * occurrences of '{' are used to indicate subformats, and cause recursion.
307 * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
308 * programmatically (instead of using the string patterns), then be careful not to
309 * produce a format that recurses on itself, which will cause an infinite loop.
310 * <p>
311 * When a single argument is parsed more than once in the string, the last match
312 * will be the final result of the parsing. For example,
313 * <blockquote><pre>
314 * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
315 * Object[] objs = {new Double(3.1415)};
316 * String result = mf.format( objs );
317 * // result now equals "3.14, 3.1"
318 * objs = null;
319 * objs = mf.parse(result, new ParsePosition(0));
320 * // objs now equals {new Double(3.1)}
321 * </pre></blockquote>
322 *
323 * <p>
324 * Likewise, parsing with a MessageFormat object using patterns containing
325 * multiple occurrences of the same argument would return the last match. For
326 * example,
327 * <blockquote><pre>
328 * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
329 * String forParsing = "x, y, z";
330 * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
331 * // result now equals {new String("z")}
332 * </pre></blockquote>
333 *
334 * <h4><a name="synchronization">Synchronization</a></h4>
335 *
336 * <p>
337 * Message formats are not synchronized.
338 * It is recommended to create separate format instances for each thread.
339 * If multiple threads access a format concurrently, it must be synchronized
340 * externally.
341 *
342 * @see java.util.Locale
343 * @see Format
344 * @see NumberFormat
345 * @see DecimalFormat
346 * @see ChoiceFormat
347 * @author Mark Davis
348 */
349
350public class MessageFormat extends Format {
351
352 private static final long serialVersionUID = 6479157306784022952L;
353
354 /**
355 * Constructs a MessageFormat for the default locale and the
356 * specified pattern.
357 * The constructor first sets the locale, then parses the pattern and
358 * creates a list of subformats for the format elements contained in it.
359 * Patterns and their interpretation are specified in the
360 * <a href="#patterns">class description</a>.
361 *
362 * @param pattern the pattern for this message format
363 * @exception IllegalArgumentException if the pattern is invalid
364 */
365 public MessageFormat(String pattern) {
366 this.locale = Locale.getDefault();
367 applyPattern(pattern);
368 }
369
370 /**
371 * Constructs a MessageFormat for the specified locale and
372 * pattern.
373 * The constructor first sets the locale, then parses the pattern and
374 * creates a list of subformats for the format elements contained in it.
375 * Patterns and their interpretation are specified in the
376 * <a href="#patterns">class description</a>.
377 *
378 * @param pattern the pattern for this message format
379 * @param locale the locale for this message format
380 * @exception IllegalArgumentException if the pattern is invalid
381 * @since 1.4
382 */
383 public MessageFormat(String pattern, Locale locale) {
384 this.locale = locale;
385 applyPattern(pattern);
386 }
387
388 /**
389 * Sets the locale to be used when creating or comparing subformats.
390 * This affects subsequent calls
391 * <ul>
392 * <li>to the {@link #applyPattern applyPattern}
393 * and {@link #toPattern toPattern} methods if format elements specify
394 * a format type and therefore have the subformats created in the
395 * <code>applyPattern</code> method, as well as
396 * <li>to the <code>format</code> and
397 * {@link #formatToCharacterIterator formatToCharacterIterator} methods
398 * if format elements do not specify a format type and therefore have
399 * the subformats created in the formatting methods.
400 * </ul>
401 * Subformats that have already been created are not affected.
402 *
403 * @param locale the locale to be used when creating or comparing subformats
404 */
405 public void setLocale(Locale locale) {
406 this.locale = locale;
407 }
408
409 /**
410 * Gets the locale that's used when creating or comparing subformats.
411 *
412 * @return the locale used when creating or comparing subformats
413 */
414 public Locale getLocale() {
415 return locale;
416 }
417
418
419 /**
420 * Sets the pattern used by this message format.
421 * The method parses the pattern and creates a list of subformats
422 * for the format elements contained in it.
423 * Patterns and their interpretation are specified in the
424 * <a href="#patterns">class description</a>.
425 *
426 * @param pattern the pattern for this message format
427 * @exception IllegalArgumentException if the pattern is invalid
428 */
429 public void applyPattern(String pattern) {
430 StringBuffer[] segments = new StringBuffer[4];
431 for (int i = 0; i < segments.length; ++i) {
432 segments[i] = new StringBuffer();
433 }
434 int part = 0;
435 int formatNumber = 0;
436 boolean inQuote = false;
437 int braceStack = 0;
438 maxOffset = -1;
439 for (int i = 0; i < pattern.length(); ++i) {
440 char ch = pattern.charAt(i);
441 if (part == 0) {
442 if (ch == '\'') {
443 if (i + 1 < pattern.length()
444 && pattern.charAt(i+1) == '\'') {
445 segments[part].append(ch); // handle doubles
446 ++i;
447 } else {
448 inQuote = !inQuote;
449 }
450 } else if (ch == '{' && !inQuote) {
451 part = 1;
452 } else {
453 segments[part].append(ch);
454 }
455 } else if (inQuote) { // just copy quotes in parts
456 segments[part].append(ch);
457 if (ch == '\'') {
458 inQuote = false;
459 }
460 } else {
461 switch (ch) {
462 case ',':
463 if (part < 3)
464 part += 1;
465 else
466 segments[part].append(ch);
467 break;
468 case '{':
469 ++braceStack;
470 segments[part].append(ch);
471 break;
472 case '}':
473 if (braceStack == 0) {
474 part = 0;
475 makeFormat(i, formatNumber, segments);
476 formatNumber++;
477 } else {
478 --braceStack;
479 segments[part].append(ch);
480 }
481 break;
482 case '\'':
483 inQuote = true;
484 // fall through, so we keep quotes in other parts
485 default:
486 segments[part].append(ch);
487 break;
488 }
489 }
490 }
491 if (braceStack == 0 && part != 0) {
492 maxOffset = -1;
493 throw new IllegalArgumentException("Unmatched braces in the pattern.");
494 }
495 this.pattern = segments[0].toString();
496 }
497
498
499 /**
500 * Returns a pattern representing the current state of the message format.
501 * The string is constructed from internal information and therefore
502 * does not necessarily equal the previously applied pattern.
503 *
504 * @return a pattern representing the current state of the message format
505 */
506 public String toPattern() {
507 // later, make this more extensible
508 int lastOffset = 0;
509 StringBuffer result = new StringBuffer();
510 for (int i = 0; i <= maxOffset; ++i) {
511 copyAndFixQuotes(pattern, lastOffset, offsets[i],result);
512 lastOffset = offsets[i];
513 result.append('{');
514 result.append(argumentNumbers[i]);
515 if (formats[i] == null) {
516 // do nothing, string format
517 } else if (formats[i] instanceof DecimalFormat) {
518 if (formats[i].equals(NumberFormat.getInstance(locale))) {
519 result.append(",number");
520 } else if (formats[i].equals(NumberFormat.getCurrencyInstance(locale))) {
521 result.append(",number,currency");
522 } else if (formats[i].equals(NumberFormat.getPercentInstance(locale))) {
523 result.append(",number,percent");
524 } else if (formats[i].equals(NumberFormat.getIntegerInstance(locale))) {
525 result.append(",number,integer");
526 } else {
527 result.append(",number," +
528 ((DecimalFormat)formats[i]).toPattern());
529 }
530 } else if (formats[i] instanceof SimpleDateFormat) {
531 if (formats[i].equals(DateFormat.getDateInstance(
532 DateFormat.DEFAULT,locale))) {
533 result.append(",date");
534 } else if (formats[i].equals(DateFormat.getDateInstance(
535 DateFormat.SHORT,locale))) {
536 result.append(",date,short");
537 } else if (formats[i].equals(DateFormat.getDateInstance(
538 DateFormat.DEFAULT,locale))) {
539 result.append(",date,medium");
540 } else if (formats[i].equals(DateFormat.getDateInstance(
541 DateFormat.LONG,locale))) {
542 result.append(",date,long");
543 } else if (formats[i].equals(DateFormat.getDateInstance(
544 DateFormat.FULL,locale))) {
545 result.append(",date,full");
546 } else if (formats[i].equals(DateFormat.getTimeInstance(
547 DateFormat.DEFAULT,locale))) {
548 result.append(",time");
549 } else if (formats[i].equals(DateFormat.getTimeInstance(
550 DateFormat.SHORT,locale))) {
551 result.append(",time,short");
552 } else if (formats[i].equals(DateFormat.getTimeInstance(
553 DateFormat.DEFAULT,locale))) {
554 result.append(",time,medium");
555 } else if (formats[i].equals(DateFormat.getTimeInstance(
556 DateFormat.LONG,locale))) {
557 result.append(",time,long");
558 } else if (formats[i].equals(DateFormat.getTimeInstance(
559 DateFormat.FULL,locale))) {
560 result.append(",time,full");
561 } else {
562 result.append(",date,"
563 + ((SimpleDateFormat)formats[i]).toPattern());
564 }
565 } else if (formats[i] instanceof ChoiceFormat) {
566 result.append(",choice,"
567 + ((ChoiceFormat)formats[i]).toPattern());
568 } else {
569 //result.append(", unknown");
570 }
571 result.append('}');
572 }
573 copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
574 return result.toString();
575 }
576
577 /**
578 * Sets the formats to use for the values passed into
579 * <code>format</code> methods or returned from <code>parse</code>
580 * methods. The indices of elements in <code>newFormats</code>
581 * correspond to the argument indices used in the previously set
582 * pattern string.
583 * The order of formats in <code>newFormats</code> thus corresponds to
584 * the order of elements in the <code>arguments</code> array passed
585 * to the <code>format</code> methods or the result array returned
586 * by the <code>parse</code> methods.
587 * <p>
588 * If an argument index is used for more than one format element
589 * in the pattern string, then the corresponding new format is used
590 * for all such format elements. If an argument index is not used
591 * for any format element in the pattern string, then the
592 * corresponding new format is ignored. If fewer formats are provided
593 * than needed, then only the formats for argument indices less
594 * than <code>newFormats.length</code> are replaced.
595 *
596 * @param newFormats the new formats to use
597 * @exception NullPointerException if <code>newFormats</code> is null
598 * @since 1.4
599 */
600 public void setFormatsByArgumentIndex(Format[] newFormats) {
601 for (int i = 0; i <= maxOffset; i++) {
602 int j = argumentNumbers[i];
603 if (j < newFormats.length) {
604 formats[i] = newFormats[j];
605 }
606 }
607 }
608
609 /**
610 * Sets the formats to use for the format elements in the
611 * previously set pattern string.
612 * The order of formats in <code>newFormats</code> corresponds to
613 * the order of format elements in the pattern string.
614 * <p>
615 * If more formats are provided than needed by the pattern string,
616 * the remaining ones are ignored. If fewer formats are provided
617 * than needed, then only the first <code>newFormats.length</code>
618 * formats are replaced.
619 * <p>
620 * Since the order of format elements in a pattern string often
621 * changes during localization, it is generally better to use the
622 * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
623 * method, which assumes an order of formats corresponding to the
624 * order of elements in the <code>arguments</code> array passed to
625 * the <code>format</code> methods or the result array returned by
626 * the <code>parse</code> methods.
627 *
628 * @param newFormats the new formats to use
629 * @exception NullPointerException if <code>newFormats</code> is null
630 */
631 public void setFormats(Format[] newFormats) {
632 int runsToCopy = newFormats.length;
633 if (runsToCopy > maxOffset + 1) {
634 runsToCopy = maxOffset + 1;
635 }
636 for (int i = 0; i < runsToCopy; i++) {
637 formats[i] = newFormats[i];
638 }
639 }
640
641 /**
642 * Sets the format to use for the format elements within the
643 * previously set pattern string that use the given argument
644 * index.
645 * The argument index is part of the format element definition and
646 * represents an index into the <code>arguments</code> array passed
647 * to the <code>format</code> methods or the result array returned
648 * by the <code>parse</code> methods.
649 * <p>
650 * If the argument index is used for more than one format element
651 * in the pattern string, then the new format is used for all such
652 * format elements. If the argument index is not used for any format
653 * element in the pattern string, then the new format is ignored.
654 *
655 * @param argumentIndex the argument index for which to use the new format
656 * @param newFormat the new format to use
657 * @since 1.4
658 */
659 public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
660 for (int j = 0; j <= maxOffset; j++) {
661 if (argumentNumbers[j] == argumentIndex) {
662 formats[j] = newFormat;
663 }
664 }
665 }
666
667 /**
668 * Sets the format to use for the format element with the given
669 * format element index within the previously set pattern string.
670 * The format element index is the zero-based number of the format
671 * element counting from the start of the pattern string.
672 * <p>
673 * Since the order of format elements in a pattern string often
674 * changes during localization, it is generally better to use the
675 * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
676 * method, which accesses format elements based on the argument
677 * index they specify.
678 *
679 * @param formatElementIndex the index of a format element within the pattern
680 * @param newFormat the format to use for the specified format element
681 * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
682 * larger than the number of format elements in the pattern string
683 */
684 public void setFormat(int formatElementIndex, Format newFormat) {
685 formats[formatElementIndex] = newFormat;
686 }
687
688 /**
689 * Gets the formats used for the values passed into
690 * <code>format</code> methods or returned from <code>parse</code>
691 * methods. The indices of elements in the returned array
692 * correspond to the argument indices used in the previously set
693 * pattern string.
694 * The order of formats in the returned array thus corresponds to
695 * the order of elements in the <code>arguments</code> array passed
696 * to the <code>format</code> methods or the result array returned
697 * by the <code>parse</code> methods.
698 * <p>
699 * If an argument index is used for more than one format element
700 * in the pattern string, then the format used for the last such
701 * format element is returned in the array. If an argument index
702 * is not used for any format element in the pattern string, then
703 * null is returned in the array.
704 *
705 * @return the formats used for the arguments within the pattern
706 * @since 1.4
707 */
708 public Format[] getFormatsByArgumentIndex() {
709 int maximumArgumentNumber = -1;
710 for (int i = 0; i <= maxOffset; i++) {
711 if (argumentNumbers[i] > maximumArgumentNumber) {
712 maximumArgumentNumber = argumentNumbers[i];
713 }
714 }
715 Format[] resultArray = new Format[maximumArgumentNumber + 1];
716 for (int i = 0; i <= maxOffset; i++) {
717 resultArray[argumentNumbers[i]] = formats[i];
718 }
719 return resultArray;
720 }
721
722 /**
723 * Gets the formats used for the format elements in the
724 * previously set pattern string.
725 * The order of formats in the returned array corresponds to
726 * the order of format elements in the pattern string.
727 * <p>
728 * Since the order of format elements in a pattern string often
729 * changes during localization, it's generally better to use the
730 * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}
731 * method, which assumes an order of formats corresponding to the
732 * order of elements in the <code>arguments</code> array passed to
733 * the <code>format</code> methods or the result array returned by
734 * the <code>parse</code> methods.
735 *
736 * @return the formats used for the format elements in the pattern
737 */
738 public Format[] getFormats() {
739 Format[] resultArray = new Format[maxOffset + 1];
740 System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);
741 return resultArray;
742 }
743
744 /**
745 * Formats an array of objects and appends the <code>MessageFormat</code>'s
746 * pattern, with format elements replaced by the formatted objects, to the
747 * provided <code>StringBuffer</code>.
748 * <p>
749 * The text substituted for the individual format elements is derived from
750 * the current subformat of the format element and the
751 * <code>arguments</code> element at the format element's argument index
752 * as indicated by the first matching line of the following table. An
753 * argument is <i>unavailable</i> if <code>arguments</code> is
754 * <code>null</code> or has fewer than argumentIndex+1 elements.
755 * <p>
756 * <table border=1 summary="Examples of subformat,argument,and formatted text">
757 * <tr>
758 * <th>Subformat
759 * <th>Argument
760 * <th>Formatted Text
761 * <tr>
762 * <td><i>any</i>
763 * <td><i>unavailable</i>
764 * <td><code>"{" + argumentIndex + "}"</code>
765 * <tr>
766 * <td><i>any</i>
767 * <td><code>null</code>
768 * <td><code>"null"</code>
769 * <tr>
770 * <td><code>instanceof ChoiceFormat</code>
771 * <td><i>any</i>
772 * <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br>
773 * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :
774 * subformat.format(argument)</code>
775 * <tr>
776 * <td><code>!= null</code>
777 * <td><i>any</i>
778 * <td><code>subformat.format(argument)</code>
779 * <tr>
780 * <td><code>null</code>
781 * <td><code>instanceof Number</code>
782 * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
783 * <tr>
784 * <td><code>null</code>
785 * <td><code>instanceof Date</code>
786 * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>
787 * <tr>
788 * <td><code>null</code>
789 * <td><code>instanceof String</code>
790 * <td><code>argument</code>
791 * <tr>
792 * <td><code>null</code>
793 * <td><i>any</i>
794 * <td><code>argument.toString()</code>
795 * </table>
796 * <p>
797 * If <code>pos</code> is non-null, and refers to
798 * <code>Field.ARGUMENT</code>, the location of the first formatted
799 * string will be returned.
800 *
801 * @param arguments an array of objects to be formatted and substituted.
802 * @param result where text is appended.
803 * @param pos On input: an alignment field, if desired.
804 * On output: the offsets of the alignment field.
805 * @exception IllegalArgumentException if an argument in the
806 * <code>arguments</code> array is not of the type
807 * expected by the format element(s) that use it.
808 */
809 public final StringBuffer format(Object[] arguments, StringBuffer result,
810 FieldPosition pos)
811 {
812 return subformat(arguments, result, pos, null);
813 }
814
815 /**
816 * Creates a MessageFormat with the given pattern and uses it
817 * to format the given arguments. This is equivalent to
818 * <blockquote>
819 * <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
820 * </blockquote>
821 *
822 * @exception IllegalArgumentException if the pattern is invalid,
823 * or if an argument in the <code>arguments</code> array
824 * is not of the type expected by the format element(s)
825 * that use it.
826 */
827 public static String format(String pattern, Object ... arguments) {
828 MessageFormat temp = new MessageFormat(pattern);
829 return temp.format(arguments);
830 }
831
832 // Overrides
833 /**
834 * Formats an array of objects and appends the <code>MessageFormat</code>'s
835 * pattern, with format elements replaced by the formatted objects, to the
836 * provided <code>StringBuffer</code>.
837 * This is equivalent to
838 * <blockquote>
839 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
840 * </blockquote>
841 *
842 * @param arguments an array of objects to be formatted and substituted.
843 * @param result where text is appended.
844 * @param pos On input: an alignment field, if desired.
845 * On output: the offsets of the alignment field.
846 * @exception IllegalArgumentException if an argument in the
847 * <code>arguments</code> array is not of the type
848 * expected by the format element(s) that use it.
849 */
850 public final StringBuffer format(Object arguments, StringBuffer result,
851 FieldPosition pos)
852 {
853 return subformat((Object[]) arguments, result, pos, null);
854 }
855
856 /**
857 * Formats an array of objects and inserts them into the
858 * <code>MessageFormat</code>'s pattern, producing an
859 * <code>AttributedCharacterIterator</code>.
860 * You can use the returned <code>AttributedCharacterIterator</code>
861 * to build the resulting String, as well as to determine information
862 * about the resulting String.
863 * <p>
864 * The text of the returned <code>AttributedCharacterIterator</code> is
865 * the same that would be returned by
866 * <blockquote>
867 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
868 * </blockquote>
869 * <p>
870 * In addition, the <code>AttributedCharacterIterator</code> contains at
871 * least attributes indicating where text was generated from an
872 * argument in the <code>arguments</code> array. The keys of these attributes are of
873 * type <code>MessageFormat.Field</code>, their values are
874 * <code>Integer</code> objects indicating the index in the <code>arguments</code>
875 * array of the argument from which the text was generated.
876 * <p>
877 * The attributes/value from the underlying <code>Format</code>
878 * instances that <code>MessageFormat</code> uses will also be
879 * placed in the resulting <code>AttributedCharacterIterator</code>.
880 * This allows you to not only find where an argument is placed in the
881 * resulting String, but also which fields it contains in turn.
882 *
883 * @param arguments an array of objects to be formatted and substituted.
884 * @return AttributedCharacterIterator describing the formatted value.
885 * @exception NullPointerException if <code>arguments</code> is null.
886 * @exception IllegalArgumentException if an argument in the
887 * <code>arguments</code> array is not of the type
888 * expected by the format element(s) that use it.
889 * @since 1.4
890 */
891 public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
892 StringBuffer result = new StringBuffer();
893 ArrayList iterators = new ArrayList();
894
895 if (arguments == null) {
896 throw new NullPointerException(
897 "formatToCharacterIterator must be passed non-null object");
898 }
899 subformat((Object[]) arguments, result, null, iterators);
900 if (iterators.size() == 0) {
901 return createAttributedCharacterIterator("");
902 }
903 return createAttributedCharacterIterator(
904 (AttributedCharacterIterator[])iterators.toArray(
905 new AttributedCharacterIterator[iterators.size()]));
906 }
907
908 /**
909 * Parses the string.
910 *
911 * <p>Caveats: The parse may fail in a number of circumstances.
912 * For example:
913 * <ul>
914 * <li>If one of the arguments does not occur in the pattern.
915 * <li>If the format of an argument loses information, such as
916 * with a choice format where a large number formats to "many".
917 * <li>Does not yet handle recursion (where
918 * the substituted strings contain {n} references.)
919 * <li>Will not always find a match (or the correct match)
920 * if some part of the parse is ambiguous.
921 * For example, if the pattern "{1},{2}" is used with the
922 * string arguments {"a,b", "c"}, it will format as "a,b,c".
923 * When the result is parsed, it will return {"a", "b,c"}.
924 * <li>If a single argument is parsed more than once in the string,
925 * then the later parse wins.
926 * </ul>
927 * When the parse fails, use ParsePosition.getErrorIndex() to find out
928 * where in the string the parsing failed. The returned error
929 * index is the starting offset of the sub-patterns that the string
930 * is comparing with. For example, if the parsing string "AAA {0} BBB"
931 * is comparing against the pattern "AAD {0} BBB", the error index is
932 * 0. When an error occurs, the call to this method will return null.
933 * If the source is null, return an empty array.
934 */
935 public Object[] parse(String source, ParsePosition pos) {
936 if (source == null) {
937 Object[] empty = {};
938 return empty;
939 }
940
941 int maximumArgumentNumber = -1;
942 for (int i = 0; i <= maxOffset; i++) {
943 if (argumentNumbers[i] > maximumArgumentNumber) {
944 maximumArgumentNumber = argumentNumbers[i];
945 }
946 }
947 Object[] resultArray = new Object[maximumArgumentNumber + 1];
948
949 int patternOffset = 0;
950 int sourceOffset = pos.index;
951 ParsePosition tempStatus = new ParsePosition(0);
952 for (int i = 0; i <= maxOffset; ++i) {
953 // match up to format
954 int len = offsets[i] - patternOffset;
955 if (len == 0 || pattern.regionMatches(patternOffset,
956 source, sourceOffset, len)) {
957 sourceOffset += len;
958 patternOffset += len;
959 } else {
960 pos.errorIndex = sourceOffset;
961 return null; // leave index as is to signal error
962 }
963
964 // now use format
965 if (formats[i] == null) { // string format
966 // if at end, use longest possible match
967 // otherwise uses first match to intervening string
968 // does NOT recursively try all possibilities
969 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
970
971 int next;
972 if (patternOffset >= tempLength) {
973 next = source.length();
974 }else{
975 next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset);
976 }
977
978 if (next < 0) {
979 pos.errorIndex = sourceOffset;
980 return null; // leave index as is to signal error
981 } else {
982 String strValue= source.substring(sourceOffset,next);
983 if (!strValue.equals("{"+argumentNumbers[i]+"}"))
984 resultArray[argumentNumbers[i]]
985 = source.substring(sourceOffset,next);
986 sourceOffset = next;
987 }
988 } else {
989 tempStatus.index = sourceOffset;
990 resultArray[argumentNumbers[i]]
991 = formats[i].parseObject(source,tempStatus);
992 if (tempStatus.index == sourceOffset) {
993 pos.errorIndex = sourceOffset;
994 return null; // leave index as is to signal error
995 }
996 sourceOffset = tempStatus.index; // update
997 }
998 }
999 int len = pattern.length() - patternOffset;
1000 if (len == 0 || pattern.regionMatches(patternOffset,
1001 source, sourceOffset, len)) {
1002 pos.index = sourceOffset + len;
1003 } else {
1004 pos.errorIndex = sourceOffset;
1005 return null; // leave index as is to signal error
1006 }
1007 return resultArray;
1008 }
1009
1010 /**
1011 * Parses text from the beginning of the given string to produce an object
1012 * array.
1013 * The method may not use the entire text of the given string.
1014 * <p>
1015 * See the {@link #parse(String, ParsePosition)} method for more information
1016 * on message parsing.
1017 *
1018 * @param source A <code>String</code> whose beginning should be parsed.
1019 * @return An <code>Object</code> array parsed from the string.
1020 * @exception ParseException if the beginning of the specified string
1021 * cannot be parsed.
1022 */
1023 public Object[] parse(String source) throws ParseException {
1024 ParsePosition pos = new ParsePosition(0);
1025 Object[] result = parse(source, pos);
1026 if (pos.index == 0) // unchanged, returned object is null
1027 throw new ParseException("MessageFormat parse error!", pos.errorIndex);
1028
1029 return result;
1030 }
1031
1032 /**
1033 * Parses text from a string to produce an object array.
1034 * <p>
1035 * The method attempts to parse text starting at the index given by
1036 * <code>pos</code>.
1037 * If parsing succeeds, then the index of <code>pos</code> is updated
1038 * to the index after the last character used (parsing does not necessarily
1039 * use all characters up to the end of the string), and the parsed
1040 * object array is returned. The updated <code>pos</code> can be used to
1041 * indicate the starting point for the next call to this method.
1042 * If an error occurs, then the index of <code>pos</code> is not
1043 * changed, the error index of <code>pos</code> is set to the index of
1044 * the character where the error occurred, and null is returned.
1045 * <p>
1046 * See the {@link #parse(String, ParsePosition)} method for more information
1047 * on message parsing.
1048 *
1049 * @param source A <code>String</code>, part of which should be parsed.
1050 * @param pos A <code>ParsePosition</code> object with index and error
1051 * index information as described above.
1052 * @return An <code>Object</code> array parsed from the string. In case of
1053 * error, returns null.
1054 * @exception NullPointerException if <code>pos</code> is null.
1055 */
1056 public Object parseObject(String source, ParsePosition pos) {
1057 return parse(source, pos);
1058 }
1059
1060 /**
1061 * Creates and returns a copy of this object.
1062 *
1063 * @return a clone of this instance.
1064 */
1065 public Object clone() {
1066 MessageFormat other = (MessageFormat) super.clone();
1067
1068 // clone arrays. Can't do with utility because of bug in Cloneable
1069 other.formats = (Format[]) formats.clone(); // shallow clone
1070 for (int i = 0; i < formats.length; ++i) {
1071 if (formats[i] != null)
1072 other.formats[i] = (Format)formats[i].clone();
1073 }
1074 // for primitives or immutables, shallow clone is enough
1075 other.offsets = (int[]) offsets.clone();
1076 other.argumentNumbers = (int[]) argumentNumbers.clone();
1077
1078 return other;
1079 }
1080
1081 /**
1082 * Equality comparison between two message format objects
1083 */
1084 public boolean equals(Object obj) {
1085 if (this == obj) // quick check
1086 return true;
1087 if (obj == null || getClass() != obj.getClass())
1088 return false;
1089 MessageFormat other = (MessageFormat) obj;
1090 return (maxOffset == other.maxOffset
1091 && pattern.equals(other.pattern)
1092 && ((locale != null && locale.equals(other.locale))
1093 || (locale == null && other.locale == null))
1094 && Arrays.equals(offsets,other.offsets)
1095 && Arrays.equals(argumentNumbers,other.argumentNumbers)
1096 && Arrays.equals(formats,other.formats));
1097 }
1098
1099 /**
1100 * Generates a hash code for the message format object.
1101 */
1102 public int hashCode() {
1103 return pattern.hashCode(); // enough for reasonable distribution
1104 }
1105
1106
1107 /**
1108 * Defines constants that are used as attribute keys in the
1109 * <code>AttributedCharacterIterator</code> returned
1110 * from <code>MessageFormat.formatToCharacterIterator</code>.
1111 *
1112 * @since 1.4
1113 */
1114 public static class Field extends Format.Field {
1115
1116 // Proclaim serial compatibility with 1.4 FCS
1117 private static final long serialVersionUID = 7899943957617360810L;
1118
1119 /**
1120 * Creates a Field with the specified name.
1121 *
1122 * @param name Name of the attribute
1123 */
1124 protected Field(String name) {
1125 super(name);
1126 }
1127
1128 /**
1129 * Resolves instances being deserialized to the predefined constants.
1130 *
1131 * @throws InvalidObjectException if the constant could not be
1132 * resolved.
1133 * @return resolved MessageFormat.Field constant
1134 */
1135 protected Object readResolve() throws InvalidObjectException {
1136 if (this.getClass() != MessageFormat.Field.class) {
1137 throw new InvalidObjectException("subclass didn't correctly implement readResolve");
1138 }
1139
1140 return ARGUMENT;
1141 }
1142
1143 //
1144 // The constants
1145 //
1146
1147 /**
1148 * Constant identifying a portion of a message that was generated
1149 * from an argument passed into <code>formatToCharacterIterator</code>.
1150 * The value associated with the key will be an <code>Integer</code>
1151 * indicating the index in the <code>arguments</code> array of the
1152 * argument from which the text was generated.
1153 */
1154 public final static Field ARGUMENT =
1155 new Field("message argument field");
1156 }
1157
1158 // ===========================privates============================
1159
1160 /**
1161 * The locale to use for formatting numbers and dates.
1162 * @serial
1163 */
1164 private Locale locale;
1165
1166 /**
1167 * The string that the formatted values are to be plugged into. In other words, this
1168 * is the pattern supplied on construction with all of the {} expressions taken out.
1169 * @serial
1170 */
1171 private String pattern = "";
1172
1173 /** The initially expected number of subformats in the format */
1174 private static final int INITIAL_FORMATS = 10;
1175
1176 /**
1177 * An array of formatters, which are used to format the arguments.
1178 * @serial
1179 */
1180 private Format[] formats = new Format[INITIAL_FORMATS];
1181
1182 /**
1183 * The positions where the results of formatting each argument are to be inserted
1184 * into the pattern.
1185 * @serial
1186 */
1187 private int[] offsets = new int[INITIAL_FORMATS];
1188
1189 /**
1190 * The argument numbers corresponding to each formatter. (The formatters are stored
1191 * in the order they occur in the pattern, not in the order in which the arguments
1192 * are specified.)
1193 * @serial
1194 */
1195 private int[] argumentNumbers = new int[INITIAL_FORMATS];
1196
1197 /**
1198 * One less than the number of entries in <code>offsets</code>. Can also be thought of
1199 * as the index of the highest-numbered element in <code>offsets</code> that is being used.
1200 * All of these arrays should have the same number of elements being used as <code>offsets</code>
1201 * does, and so this variable suffices to tell us how many entries are in all of them.
1202 * @serial
1203 */
1204 private int maxOffset = -1;
1205
1206 /**
1207 * Internal routine used by format. If <code>characterIterators</code> is
1208 * non-null, AttributedCharacterIterator will be created from the
1209 * subformats as necessary. If <code>characterIterators</code> is null
1210 * and <code>fp</code> is non-null and identifies
1211 * <code>Field.MESSAGE_ARGUMENT</code>, the location of
1212 * the first replaced argument will be set in it.
1213 *
1214 * @exception IllegalArgumentException if an argument in the
1215 * <code>arguments</code> array is not of the type
1216 * expected by the format element(s) that use it.
1217 */
1218 private StringBuffer subformat(Object[] arguments, StringBuffer result,
1219 FieldPosition fp, List characterIterators) {
1220 // note: this implementation assumes a fast substring & index.
1221 // if this is not true, would be better to append chars one by one.
1222 int lastOffset = 0;
1223 int last = result.length();
1224 for (int i = 0; i <= maxOffset; ++i) {
1225 result.append(pattern.substring(lastOffset, offsets[i]));
1226 lastOffset = offsets[i];
1227 int argumentNumber = argumentNumbers[i];
1228 if (arguments == null || argumentNumber >= arguments.length) {
1229 result.append("{" + argumentNumber + "}");
1230 continue;
1231 }
1232 // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
1233 if (false) { // if (argRecursion == 3){
1234 // prevent loop!!!
1235 result.append('\uFFFD');
1236 } else {
1237 Object obj = arguments[argumentNumber];
1238 String arg = null;
1239 Format subFormatter = null;
1240 if (obj == null) {
1241 arg = "null";
1242 } else if (formats[i] != null) {
1243 subFormatter = formats[i];
1244 if (subFormatter instanceof ChoiceFormat) {
1245 arg = formats[i].format(obj);
1246 if (arg.indexOf('{') >= 0) {
1247 subFormatter = new MessageFormat(arg, locale);
1248 obj = arguments;
1249 arg = null;
1250 }
1251 }
1252 } else if (obj instanceof Number) {
1253 // format number if can
1254 subFormatter = NumberFormat.getInstance(locale);
1255 } else if (obj instanceof Date) {
1256 // format a Date if can
1257 subFormatter = DateFormat.getDateTimeInstance(
1258 DateFormat.SHORT, DateFormat.SHORT, locale);//fix
1259 } else if (obj instanceof String) {
1260 arg = (String) obj;
1261
1262 } else {
1263 arg = obj.toString();
1264 if (arg == null) arg = "null";
1265 }
1266
1267 // At this point we are in two states, either subFormatter
1268 // is non-null indicating we should format obj using it,
1269 // or arg is non-null and we should use it as the value.
1270
1271 if (characterIterators != null) {
1272 // If characterIterators is non-null, it indicates we need
1273 // to get the CharacterIterator from the child formatter.
1274 if (last != result.length()) {
1275 characterIterators.add(
1276 createAttributedCharacterIterator(result.substring
1277 (last)));
1278 last = result.length();
1279 }
1280 if (subFormatter != null) {
1281 AttributedCharacterIterator subIterator =
1282 subFormatter.formatToCharacterIterator(obj);
1283
1284 append(result, subIterator);
1285 if (last != result.length()) {
1286 characterIterators.add(
1287 createAttributedCharacterIterator(
1288 subIterator, Field.ARGUMENT,
1289 new Integer(argumentNumber)));
1290 last = result.length();
1291 }
1292 arg = null;
1293 }
1294 if (arg != null && arg.length() > 0) {
1295 result.append(arg);
1296 characterIterators.add(
1297 createAttributedCharacterIterator(
1298 arg, Field.ARGUMENT,
1299 new Integer(argumentNumber)));
1300 last = result.length();
1301 }
1302 }
1303 else {
1304 if (subFormatter != null) {
1305 arg = subFormatter.format(obj);
1306 }
1307 last = result.length();
1308 result.append(arg);
1309 if (i == 0 && fp != null && Field.ARGUMENT.equals(
1310 fp.getFieldAttribute())) {
1311 fp.setBeginIndex(last);
1312 fp.setEndIndex(result.length());
1313 }
1314 last = result.length();
1315 }
1316 }
1317 }
1318 result.append(pattern.substring(lastOffset, pattern.length()));
1319 if (characterIterators != null && last != result.length()) {
1320 characterIterators.add(createAttributedCharacterIterator(
1321 result.substring(last)));
1322 }
1323 return result;
1324 }
1325
1326 /**
1327 * Convenience method to append all the characters in
1328 * <code>iterator</code> to the StringBuffer <code>result</code>.
1329 */
1330 private void append(StringBuffer result, CharacterIterator iterator) {
1331 if (iterator.first() != CharacterIterator.DONE) {
1332 char aChar;
1333
1334 result.append(iterator.first());
1335 while ((aChar = iterator.next()) != CharacterIterator.DONE) {
1336 result.append(aChar);
1337 }
1338 }
1339 }
1340
1341 private static final String[] typeList =
1342 {"", "", "number", "", "date", "", "time", "", "choice"};
1343 private static final String[] modifierList =
1344 {"", "", "currency", "", "percent", "", "integer"};
1345 private static final String[] dateModifierList =
1346 {"", "", "short", "", "medium", "", "long", "", "full"};
1347
1348 private void makeFormat(int position, int offsetNumber,
1349 StringBuffer[] segments)
1350 {
1351 // get the argument number
1352 int argumentNumber;
1353 try {
1354 argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
1355 } catch (NumberFormatException e) {
1356 throw new IllegalArgumentException("can't parse argument number: " + segments[1]);
1357 }
1358 if (argumentNumber < 0) {
1359 throw new IllegalArgumentException("negative argument number: " + argumentNumber);
1360 }
1361
1362 // resize format information arrays if necessary
1363 if (offsetNumber >= formats.length) {
1364 int newLength = formats.length * 2;
1365 Format[] newFormats = new Format[newLength];
1366 int[] newOffsets = new int[newLength];
1367 int[] newArgumentNumbers = new int[newLength];
1368 System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);
1369 System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);
1370 System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);
1371 formats = newFormats;
1372 offsets = newOffsets;
1373 argumentNumbers = newArgumentNumbers;
1374 }
1375 int oldMaxOffset = maxOffset;
1376 maxOffset = offsetNumber;
1377 offsets[offsetNumber] = segments[0].length();
1378 argumentNumbers[offsetNumber] = argumentNumber;
1379
1380 // now get the format
1381 Format newFormat = null;
1382 switch (findKeyword(segments[2].toString(), typeList)) {
1383 case 0:
1384 break;
1385 case 1: case 2:// number
1386 switch (findKeyword(segments[3].toString(), modifierList)) {
1387 case 0: // default;
1388 newFormat = NumberFormat.getInstance(locale);
1389 break;
1390 case 1: case 2:// currency
1391 newFormat = NumberFormat.getCurrencyInstance(locale);
1392 break;
1393 case 3: case 4:// percent
1394 newFormat = NumberFormat.getPercentInstance(locale);
1395 break;
1396 case 5: case 6:// integer
1397 newFormat = NumberFormat.getIntegerInstance(locale);
1398 break;
1399 default: // pattern
1400 newFormat = new DecimalFormat(segments[3].toString(), DecimalFormatSymbols.getInstance(locale));
1401 break;
1402 }
1403 break;
1404 case 3: case 4: // date
1405 switch (findKeyword(segments[3].toString(), dateModifierList)) {
1406 case 0: // default
1407 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
1408 break;
1409 case 1: case 2: // short
1410 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
1411 break;
1412 case 3: case 4: // medium
1413 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
1414 break;
1415 case 5: case 6: // long
1416 newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
1417 break;
1418 case 7: case 8: // full
1419 newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
1420 break;
1421 default:
1422 newFormat = new SimpleDateFormat(segments[3].toString(), locale);
1423 break;
1424 }
1425 break;
1426 case 5: case 6:// time
1427 switch (findKeyword(segments[3].toString(), dateModifierList)) {
1428 case 0: // default
1429 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
1430 break;
1431 case 1: case 2: // short
1432 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
1433 break;
1434 case 3: case 4: // medium
1435 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
1436 break;
1437 case 5: case 6: // long
1438 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale);
1439 break;
1440 case 7: case 8: // full
1441 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale);
1442 break;
1443 default:
1444 newFormat = new SimpleDateFormat(segments[3].toString(), locale);
1445 break;
1446 }
1447 break;
1448 case 7: case 8:// choice
1449 try {
1450 newFormat = new ChoiceFormat(segments[3].toString());
1451 } catch (Exception e) {
1452 maxOffset = oldMaxOffset;
1453 throw new IllegalArgumentException(
1454 "Choice Pattern incorrect");
1455 }
1456 break;
1457 default:
1458 maxOffset = oldMaxOffset;
1459 throw new IllegalArgumentException("unknown format type: " +
1460 segments[2].toString());
1461 }
1462 formats[offsetNumber] = newFormat;
1463 segments[1].setLength(0); // throw away other segments
1464 segments[2].setLength(0);
1465 segments[3].setLength(0);
1466 }
1467
1468 private static final int findKeyword(String s, String[] list) {
1469 s = s.trim().toLowerCase();
1470 for (int i = 0; i < list.length; ++i) {
1471 if (s.equals(list[i]))
1472 return i;
1473 }
1474 return -1;
1475 }
1476
1477 private static final void copyAndFixQuotes(
1478 String source, int start, int end, StringBuffer target) {
1479 for (int i = start; i < end; ++i) {
1480 char ch = source.charAt(i);
1481 if (ch == '{') {
1482 target.append("'{'");
1483 } else if (ch == '}') {
1484 target.append("'}'");
1485 } else if (ch == '\'') {
1486 target.append("''");
1487 } else {
1488 target.append(ch);
1489 }
1490 }
1491 }
1492
1493 /**
1494 * After reading an object from the input stream, do a simple verification
1495 * to maintain class invariants.
1496 * @throws InvalidObjectException if the objects read from the stream is invalid.
1497 */
1498 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
1499 in.defaultReadObject();
1500 boolean isValid = maxOffset >= -1
1501 && formats.length > maxOffset
1502 && offsets.length > maxOffset
1503 && argumentNumbers.length > maxOffset;
1504 if (isValid) {
1505 int lastOffset = pattern.length() + 1;
1506 for (int i = maxOffset; i >= 0; --i) {
1507 if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
1508 isValid = false;
1509 break;
1510 } else {
1511 lastOffset = offsets[i];
1512 }
1513 }
1514 }
1515 if (!isValid) {
1516 throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");
1517 }
1518 }
1519}