KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > text > MessageFormat


1 /*
2  * @(#)MessageFormat.java 1.56 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 /*
9  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
10  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
11  *
12  * The original version of this source code and documentation is copyrighted
13  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
14  * materials are provided under terms of a License Agreement between Taligent
15  * and Sun. This technology is protected by multiple US and International
16  * patents. This notice and attribution to Taligent may not be removed.
17  * Taligent is a registered trademark of Taligent, Inc.
18  *
19  */

20
21 package java.text;
22
23 import java.io.InvalidObjectException JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.ObjectInputStream JavaDoc;
26 import java.text.DecimalFormat JavaDoc;
27 import java.util.ArrayList JavaDoc;
28 import java.util.Date JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.Locale JavaDoc;
31 import sun.text.Utility;
32
33
34 /**
35  * <code>MessageFormat</code> provides a means to produce concatenated
36  * messages in language-neutral way. Use this to construct messages
37  * displayed for end users.
38  *
39  * <p>
40  * <code>MessageFormat</code> takes a set of objects, formats them, then
41  * inserts the formatted strings into the pattern at the appropriate places.
42  *
43  * <p>
44  * <strong>Note:</strong>
45  * <code>MessageFormat</code> differs from the other <code>Format</code>
46  * classes in that you create a <code>MessageFormat</code> object with one
47  * of its constructors (not with a <code>getInstance</code> style factory
48  * method). The factory methods aren't necessary because <code>MessageFormat</code>
49  * itself doesn't implement locale specific behavior. Any locale specific
50  * behavior is defined by the pattern that you provide as well as the
51  * subformats used for inserted arguments.
52  *
53  * <h4><a name="patterns">Patterns and Their Interpretation</a></h4>
54  *
55  * <code>MessageFormat</code> uses patterns of the following form:
56  * <blockquote><pre>
57  * <i>MessageFormatPattern:</i>
58  * <i>String</i>
59  * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>
60  *
61  * <i>FormatElement:</i>
62  * { <i>ArgumentIndex</i> }
63  * { <i>ArgumentIndex</i> , <i>FormatType</i> }
64  * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
65  *
66  * <i>FormatType: one of </i>
67  * number date time choice
68  *
69  * <i>FormatStyle:</i>
70  * short
71  * medium
72  * long
73  * full
74  * integer
75  * currency
76  * percent
77  * <i>SubformatPattern</i>
78  *
79  * <i>String:</i>
80  * <i>StringPart<sub>opt</sub></i>
81  * <i>String</i> <i>StringPart</i>
82  *
83  * <i>StringPart:</i>
84  * ''
85  * ' <i>QuotedString</i> '
86  * <i>UnquotedString</i>
87  *
88  * <i>SubformatPattern:</i>
89  * <i>SubformatPatternPart<sub>opt</sub></i>
90  * <i>SubformatPattern</i> <i>SubformatPatternPart</i>
91  *
92  * <i>SubFormatPatternPart:</i>
93  * ' <i>QuotedPattern</i> '
94  * <i>UnquotedPattern</i>
95  * </pre></blockquote>
96  *
97  * <p>
98  * Within a <i>String</i>, <code>"''"</code> represents a single
99  * quote. A <i>QuotedString</i> can contain arbitrary characters
100  * except single quotes; the surrounding single quotes are removed.
101  * An <i>UnquotedString</i> can contain arbitrary characters
102  * except single quotes and left curly brackets. Thus, a string that
103  * should result in the formatted message "'{0}'" can be written as
104  * <code>"'''{'0}''"</code> or <code>"'''{0}'''"</code>.
105  * <p>
106  * Within a <i>SubformatPattern</i>, different rules apply.
107  * A <i>QuotedPattern</i> can contain arbitrary characters
108  * except single quotes; but the surrounding single quotes are
109  * <strong>not</strong> removed, so they may be interpreted by the
110  * subformat. For example, <code>"{1,number,$'#',##}"</code> will
111  * produce a number format with the pound-sign quoted, with a result
112  * such as: "$#31,45".
113  * An <i>UnquotedPattern</i> can contain arbitrary characters
114  * except single quotes, but curly braces within it must be balanced.
115  * For example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code>
116  * are valid subformat patterns, but <code>"ab {0'}' de"</code> and
117  * <code>"ab } de"</code> are not.
118  * <p>
119  * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message
120  * format patterns unfortunately have shown to be somewhat confusing.
121  * In particular, it isn't always obvious to localizers whether single
122  * quotes need to be doubled or not. Make sure to inform localizers about
123  * the rules, and tell them (for example, by using comments in resource
124  * bundle source files) which strings will be processed by MessageFormat.
125  * Note that localizers may need to use single quotes in translated
126  * strings where the original version doesn't have them.
127  * </dl>
128  * <p>
129  * The <i>ArgumentIndex</i> value is a non-negative integer written
130  * using the digits '0' through '9', and represents an index into the
131  * <code>arguments</code> array passed to the <code>format</code> methods
132  * or the result array returned by the <code>parse</code> methods.
133  * <p>
134  * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create
135  * a <code>Format</code> instance for the format element. The following
136  * table shows how the values map to Format instances. Combinations not
137  * shown in the table are illegal. A <i>SubformatPattern</i> must
138  * be a valid pattern string for the Format subclass used.
139  * <p>
140  * <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances">
141  * <tr>
142  * <th id="ft">Format Type
143  * <th id="fs">Format Style
144  * <th id="sc">Subformat Created
145  * <tr>
146  * <td headers="ft"><i>(none)</i>
147  * <td headers="fs"><i>(none)</i>
148  * <td headers="sc"><code>null</code>
149  * <tr>
150  * <td headers="ft" rowspan=5><code>number</code>
151  * <td headers="fs"><i>(none)</i>
152  * <td headers="sc"><code>NumberFormat.getInstance(getLocale())</code>
153  * <tr>
154  * <td headers="fs"><code>integer</code>
155  * <td headers="sc"><code>NumberFormat.getIntegerInstance(getLocale())</code>
156  * <tr>
157  * <td headers="fs"><code>currency</code>
158  * <td headers="sc"><code>NumberFormat.getCurrencyInstance(getLocale())</code>
159  * <tr>
160  * <td headers="fs"><code>percent</code>
161  * <td headers="sc"><code>NumberFormat.getPercentInstance(getLocale())</code>
162  * <tr>
163  * <td headers="fs"><i>SubformatPattern</i>
164  * <td headers="sc"><code>new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))</code>
165  * <tr>
166  * <td headers="ft" rowspan=6><code>date</code>
167  * <td headers="fs"><i>(none)</i>
168  * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
169  * <tr>
170  * <td headers="fs"><code>short</code>
171  * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
172  * <tr>
173  * <td headers="fs"><code>medium</code>
174  * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
175  * <tr>
176  * <td headers="fs"><code>long</code>
177  * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
178  * <tr>
179  * <td headers="fs"><code>full</code>
180  * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
181  * <tr>
182  * <td headers="fs"><i>SubformatPattern</i>
183  * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())
184  * <tr>
185  * <td headers="ft" rowspan=6><code>time</code>
186  * <td headers="fs"><i>(none)</i>
187  * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
188  * <tr>
189  * <td headers="fs"><code>short</code>
190  * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
191  * <tr>
192  * <td headers="fs"><code>medium</code>
193  * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
194  * <tr>
195  * <td headers="fs"><code>long</code>
196  * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
197  * <tr>
198  * <td headers="fs"><code>full</code>
199  * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
200  * <tr>
201  * <td headers="fs"><i>SubformatPattern</i>
202  * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())
203  * <tr>
204  * <td headers="ft"><code>choice</code>
205  * <td headers="fs"><i>SubformatPattern</i>
206  * <td headers="sc"><code>new ChoiceFormat(subformatPattern)</code>
207  * </table>
208  * <p>
209  *
210  * <h4>Usage Information</h4>
211  *
212  * <p>
213  * Here are some examples of usage.
214  * In real internationalized programs, the message format pattern and other
215  * static strings will, of course, be obtained from resource bundles.
216  * Other parameters will be dynamically determined at runtime.
217  * <p>
218  * The first example uses the static method <code>MessageFormat.format</code>,
219  * which internally creates a <code>MessageFormat</code> for one-time use:
220  * <blockquote><pre>
221  * int planet = 7;
222  * String event = "a disturbance in the Force";
223  *
224  * String result = MessageFormat.format(
225  * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
226  * planet, new Date(), event);
227  * </pre></blockquote>
228  * The output is:
229  * <blockquote><pre>
230  * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
231  * </pre></blockquote>
232  *
233  * <p>
234  * The following example creates a <code>MessageFormat</code> instance that
235  * can be used repeatedly:
236  * <blockquote><pre>
237  * int fileCount = 1273;
238  * String diskName = "MyDisk";
239  * Object[] testArgs = {new Long(fileCount), diskName};
240  *
241  * MessageFormat form = new MessageFormat(
242  * "The disk \"{1}\" contains {0} file(s).");
243  *
244  * System.out.println(form.format(testArgs));
245  * </pre></blockquote>
246  * The output with different values for <code>fileCount</code>:
247  * <blockquote><pre>
248  * The disk "MyDisk" contains 0 file(s).
249  * The disk "MyDisk" contains 1 file(s).
250  * The disk "MyDisk" contains 1,273 file(s).
251  * </pre></blockquote>
252  *
253  * <p>
254  * For more sophisticated patterns, you can use a <code>ChoiceFormat</code>
255  * to produce correct forms for singular and plural:
256  * <blockquote><pre>
257  * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
258  * double[] filelimits = {0,1,2};
259  * String[] filepart = {"no files","one file","{0,number} files"};
260  * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
261  * form.setFormatByArgumentIndex(0, fileform);
262  *
263  * int fileCount = 1273;
264  * String diskName = "MyDisk";
265  * Object[] testArgs = {new Long(fileCount), diskName};
266  *
267  * System.out.println(form.format(testArgs));
268  * </pre></blockquote>
269  * The output with different values for <code>fileCount</code>:
270  * <blockquote><pre>
271  * The disk "MyDisk" contains no files.
272  * The disk "MyDisk" contains one file.
273  * The disk "MyDisk" contains 1,273 files.
274  * </pre></blockquote>
275  *
276  * <p>
277  * You can create the <code>ChoiceFormat</code> programmatically, as in the
278  * above example, or by using a pattern. See {@link ChoiceFormat}
279  * for more information.
280  * <blockquote><pre>
281  * form.applyPattern(
282  * "There {0,choice,0#are no files|1#is one file|1&lt;are {0,number,integer} files}.");
283  * </pre></blockquote>
284  *
285  * <p>
286  * <strong>Note:</strong> As we see above, the string produced
287  * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated specially;
288  * occurences of '{' are used to indicated subformats, and cause recursion.
289  * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
290  * programmatically (instead of using the string patterns), then be careful not to
291  * produce a format that recurses on itself, which will cause an infinite loop.
292  * <p>
293  * When a single argument is parsed more than once in the string, the last match
294  * will be the final result of the parsing. For example,
295  * <blockquote><pre>
296  * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
297  * Object[] objs = {new Double(3.1415)};
298  * String result = mf.format( objs );
299  * // result now equals "3.14, 3.1"
300  * objs = null;
301  * objs = mf.parse(result, new ParsePosition(0));
302  * // objs now equals {new Double(3.1)}
303  * </pre></blockquote>
304  *
305  * <p>
306  * Likewise, parsing with a MessageFormat object using patterns containing
307  * multiple occurences of the same argument would return the last match. For
308  * example,
309  * <blockquote><pre>
310  * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
311  * String forParsing = "x, y, z";
312  * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
313  * // result now equals {new String("z")}
314  * </pre></blockquote>
315  *
316  * <h4><a name="synchronization">Synchronization</a></h4>
317  *
318  * <p>
319  * Message formats are not synchronized.
320  * It is recommended to create separate format instances for each thread.
321  * If multiple threads access a format concurrently, it must be synchronized
322  * externally.
323  *
324  * @see java.util.Locale
325  * @see Format
326  * @see NumberFormat
327  * @see DecimalFormat
328  * @see ChoiceFormat
329  * @version 1.56, 12/19/03
330  * @author Mark Davis
331  */

332
333 public class MessageFormat extends Format JavaDoc {
334
335     private static final long serialVersionUID = 6479157306784022952L;
336
337     /**
338      * Constructs a MessageFormat for the default locale and the
339      * specified pattern.
340      * The constructor first sets the locale, then parses the pattern and
341      * creates a list of subformats for the format elements contained in it.
342      * Patterns and their interpretation are specified in the
343      * <a HREF="#patterns">class description</a>.
344      *
345      * @param pattern the pattern for this message format
346      * @exception IllegalArgumentException if the pattern is invalid
347      */

348     public MessageFormat(String JavaDoc pattern) {
349         this.locale = Locale.getDefault();
350         applyPattern(pattern);
351     }
352
353     /**
354      * Constructs a MessageFormat for the specified locale and
355      * pattern.
356      * The constructor first sets the locale, then parses the pattern and
357      * creates a list of subformats for the format elements contained in it.
358      * Patterns and their interpretation are specified in the
359      * <a HREF="#patterns">class description</a>.
360      *
361      * @param pattern the pattern for this message format
362      * @param locale the locale for this message format
363      * @exception IllegalArgumentException if the pattern is invalid
364      * @since 1.4
365      */

366     public MessageFormat(String JavaDoc pattern, Locale JavaDoc locale) {
367         this.locale = locale;
368         applyPattern(pattern);
369     }
370
371     /**
372      * Sets the locale to be used when creating or comparing subformats.
373      * This affects subsequent calls to the {@link #applyPattern applyPattern}
374      * and {@link #toPattern toPattern} methods as well as to the
375      * <code>format</code> and
376      * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
377      *
378      * @param locale the locale to be used when creating or comparing subformats
379      */

380     public void setLocale(Locale JavaDoc locale) {
381         this.locale = locale;
382     }
383
384     /**
385      * Gets the locale that's used when creating or comparing subformats.
386      *
387      * @return the locale used when creating or comparing subformats
388      */

389     public Locale JavaDoc getLocale() {
390         return locale;
391     }
392
393
394     /**
395      * Sets the pattern used by this message format.
396      * The method parses the pattern and creates a list of subformats
397      * for the format elements contained in it.
398      * Patterns and their interpretation are specified in the
399      * <a HREF="#patterns">class description</a>.
400      *
401      * @param pattern the pattern for this message format
402      * @exception IllegalArgumentException if the pattern is invalid
403      */

404     public void applyPattern(String JavaDoc pattern) {
405             StringBuffer JavaDoc[] segments = new StringBuffer JavaDoc[4];
406             for (int i = 0; i < segments.length; ++i) {
407                 segments[i] = new StringBuffer JavaDoc();
408             }
409             int part = 0;
410             int formatNumber = 0;
411             boolean inQuote = false;
412             int braceStack = 0;
413             maxOffset = -1;
414             for (int i = 0; i < pattern.length(); ++i) {
415                 char ch = pattern.charAt(i);
416                 if (part == 0) {
417                     if (ch == '\'') {
418                         if (i + 1 < pattern.length()
419                             && pattern.charAt(i+1) == '\'') {
420                             segments[part].append(ch); // handle doubles
421
++i;
422                         } else {
423                             inQuote = !inQuote;
424                         }
425                     } else if (ch == '{' && !inQuote) {
426                         part = 1;
427                     } else {
428                         segments[part].append(ch);
429                     }
430                 } else if (inQuote) { // just copy quotes in parts
431
segments[part].append(ch);
432                     if (ch == '\'') {
433                         inQuote = false;
434                     }
435                 } else {
436                     switch (ch) {
437                     case ',':
438                         if (part < 3)
439                             part += 1;
440                         else
441                             segments[part].append(ch);
442                         break;
443                     case '{':
444                         ++braceStack;
445                         segments[part].append(ch);
446                         break;
447                     case '}':
448                         if (braceStack == 0) {
449                             part = 0;
450                             makeFormat(i, formatNumber, segments);
451                             formatNumber++;
452                         } else {
453                             --braceStack;
454                             segments[part].append(ch);
455                         }
456                         break;
457                     case '\'':
458                         inQuote = true;
459                         // fall through, so we keep quotes in other parts
460
default:
461                         segments[part].append(ch);
462                         break;
463                     }
464                 }
465             }
466             if (braceStack == 0 && part != 0) {
467                 maxOffset = -1;
468                 throw new IllegalArgumentException JavaDoc("Unmatched braces in the pattern.");
469             }
470             this.pattern = segments[0].toString();
471     }
472
473
474     /**
475      * Returns a pattern representing the current state of the message format.
476      * The string is constructed from internal information and therefore
477      * does not necessarily equal the previously applied pattern.
478      *
479      * @return a pattern representing the current state of the message format
480      */

481     public String JavaDoc toPattern() {
482         // later, make this more extensible
483
int lastOffset = 0;
484         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
485         for (int i = 0; i <= maxOffset; ++i) {
486             copyAndFixQuotes(pattern, lastOffset, offsets[i],result);
487             lastOffset = offsets[i];
488             result.append('{');
489             result.append(argumentNumbers[i]);
490             if (formats[i] == null) {
491                 // do nothing, string format
492
} else if (formats[i] instanceof DecimalFormat JavaDoc) {
493                 if (formats[i].equals(NumberFormat.getInstance(locale))) {
494                     result.append(",number");
495                 } else if (formats[i].equals(NumberFormat.getCurrencyInstance(locale))) {
496                     result.append(",number,currency");
497                 } else if (formats[i].equals(NumberFormat.getPercentInstance(locale))) {
498                     result.append(",number,percent");
499                 } else if (formats[i].equals(NumberFormat.getIntegerInstance(locale))) {
500                     result.append(",number,integer");
501                 } else {
502                     result.append(",number," +
503                                   ((DecimalFormat JavaDoc)formats[i]).toPattern());
504                 }
505             } else if (formats[i] instanceof SimpleDateFormat JavaDoc) {
506                 if (formats[i].equals(DateFormat.getDateInstance(
507                                                                DateFormat.DEFAULT,locale))) {
508                     result.append(",date");
509                 } else if (formats[i].equals(DateFormat.getDateInstance(
510                                                                       DateFormat.SHORT,locale))) {
511                     result.append(",date,short");
512                 } else if (formats[i].equals(DateFormat.getDateInstance(
513                                                                       DateFormat.DEFAULT,locale))) {
514                     result.append(",date,medium");
515                 } else if (formats[i].equals(DateFormat.getDateInstance(
516                                                                       DateFormat.LONG,locale))) {
517                     result.append(",date,long");
518                 } else if (formats[i].equals(DateFormat.getDateInstance(
519                                                                       DateFormat.FULL,locale))) {
520                     result.append(",date,full");
521                 } else if (formats[i].equals(DateFormat.getTimeInstance(
522                                                                       DateFormat.DEFAULT,locale))) {
523                     result.append(",time");
524                 } else if (formats[i].equals(DateFormat.getTimeInstance(
525                                                                       DateFormat.SHORT,locale))) {
526                     result.append(",time,short");
527                 } else if (formats[i].equals(DateFormat.getTimeInstance(
528                                                                       DateFormat.DEFAULT,locale))) {
529                     result.append(",time,medium");
530                 } else if (formats[i].equals(DateFormat.getTimeInstance(
531                                                                       DateFormat.LONG,locale))) {
532                     result.append(",time,long");
533                 } else if (formats[i].equals(DateFormat.getTimeInstance(
534                                                                       DateFormat.FULL,locale))) {
535                     result.append(",time,full");
536                 } else {
537                     result.append(",date,"
538                                   + ((SimpleDateFormat JavaDoc)formats[i]).toPattern());
539                 }
540             } else if (formats[i] instanceof ChoiceFormat JavaDoc) {
541                 result.append(",choice,"
542                               + ((ChoiceFormat JavaDoc)formats[i]).toPattern());
543             } else {
544                 //result.append(", unknown");
545
}
546             result.append('}');
547         }
548         copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
549         return result.toString();
550     }
551
552     /**
553      * Sets the formats to use for the values passed into
554      * <code>format</code> methods or returned from <code>parse</code>
555      * methods. The indices of elements in <code>newFormats</code>
556      * correspond to the argument indices used in the previously set
557      * pattern string.
558      * The order of formats in <code>newFormats</code> thus corresponds to
559      * the order of elements in the <code>arguments</code> array passed
560      * to the <code>format</code> methods or the result array returned
561      * by the <code>parse</code> methods.
562      * <p>
563      * If an argument index is used for more than one format element
564      * in the pattern string, then the corresponding new format is used
565      * for all such format elements. If an argument index is not used
566      * for any format element in the pattern string, then the
567      * corresponding new format is ignored. If fewer formats are provided
568      * than needed, then only the formats for argument indices less
569      * than <code>newFormats.length</code> are replaced.
570      *
571      * @param newFormats the new formats to use
572      * @exception NullPointerException if <code>newFormats</code> is null
573      * @since 1.4
574      */

575     public void setFormatsByArgumentIndex(Format JavaDoc[] newFormats) {
576         for (int i = 0; i <= maxOffset; i++) {
577             int j = argumentNumbers[i];
578             if (j < newFormats.length) {
579                 formats[i] = newFormats[j];
580             }
581         }
582     }
583
584     /**
585      * Sets the formats to use for the format elements in the
586      * previously set pattern string.
587      * The order of formats in <code>newFormats</code> corresponds to
588      * the order of format elements in the pattern string.
589      * <p>
590      * If more formats are provided than needed by the pattern string,
591      * the remaining ones are ignored. If fewer formats are provided
592      * than needed, then only the first <code>newFormats.length</code>
593      * formats are replaced.
594      * <p>
595      * Since the order of format elements in a pattern string often
596      * changes during localization, it is generally better to use the
597      * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
598      * method, which assumes an order of formats corresponding to the
599      * order of elements in the <code>arguments</code> array passed to
600      * the <code>format</code> methods or the result array returned by
601      * the <code>parse</code> methods.
602      *
603      * @param newFormats the new formats to use
604      * @exception NullPointerException if <code>newFormats</code> is null
605      */

606     public void setFormats(Format JavaDoc[] newFormats) {
607         int runsToCopy = newFormats.length;
608         if (runsToCopy > maxOffset + 1) {
609             runsToCopy = maxOffset + 1;
610         }
611         for (int i = 0; i < runsToCopy; i++) {
612             formats[i] = newFormats[i];
613         }
614     }
615
616     /**
617      * Sets the format to use for the format elements within the
618      * previously set pattern string that use the given argument
619      * index.
620      * The argument index is part of the format element definition and
621      * represents an index into the <code>arguments</code> array passed
622      * to the <code>format</code> methods or the result array returned
623      * by the <code>parse</code> methods.
624      * <p>
625      * If the argument index is used for more than one format element
626      * in the pattern string, then the new format is used for all such
627      * format elements. If the argument index is not used for any format
628      * element in the pattern string, then the new format is ignored.
629      *
630      * @param argumentIndex the argument index for which to use the new format
631      * @param newFormat the new format to use
632      * @since 1.4
633      */

634     public void setFormatByArgumentIndex(int argumentIndex, Format JavaDoc newFormat) {
635         for (int j = 0; j <= maxOffset; j++) {
636             if (argumentNumbers[j] == argumentIndex) {
637                 formats[j] = newFormat;
638             }
639         }
640     }
641
642     /**
643      * Sets the format to use for the format element with the given
644      * format element index within the previously set pattern string.
645      * The format element index is the zero-based number of the format
646      * element counting from the start of the pattern string.
647      * <p>
648      * Since the order of format elements in a pattern string often
649      * changes during localization, it is generally better to use the
650      * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
651      * method, which accesses format elements based on the argument
652      * index they specify.
653      *
654      * @param formatElementIndex the index of a format element within the pattern
655      * @param newFormat the format to use for the specified format element
656      * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
657      * larger than the number of format elements in the pattern string
658      */

659     public void setFormat(int formatElementIndex, Format JavaDoc newFormat) {
660         formats[formatElementIndex] = newFormat;
661     }
662
663     /**
664      * Gets the formats used for the values passed into
665      * <code>format</code> methods or returned from <code>parse</code>
666      * methods. The indices of elements in the returned array
667      * correspond to the argument indices used in the previously set
668      * pattern string.
669      * The order of formats in the returned array thus corresponds to
670      * the order of elements in the <code>arguments</code> array passed
671      * to the <code>format</code> methods or the result array returned
672      * by the <code>parse</code> methods.
673      * <p>
674      * If an argument index is used for more than one format element
675      * in the pattern string, then the format used for the last such
676      * format element is returned in the array. If an argument index
677      * is not used for any format element in the pattern string, then
678      * null is returned in the array.
679      *
680      * @return the formats used for the arguments within the pattern
681      * @since 1.4
682      */

683     public Format JavaDoc[] getFormatsByArgumentIndex() {
684         int maximumArgumentNumber = -1;
685         for (int i = 0; i <= maxOffset; i++) {
686             if (argumentNumbers[i] > maximumArgumentNumber) {
687                 maximumArgumentNumber = argumentNumbers[i];
688             }
689         }
690         Format JavaDoc[] resultArray = new Format JavaDoc[maximumArgumentNumber + 1];
691         for (int i = 0; i <= maxOffset; i++) {
692             resultArray[argumentNumbers[i]] = formats[i];
693         }
694         return resultArray;
695     }
696
697     /**
698      * Gets the formats used for the format elements in the
699      * previously set pattern string.
700      * The order of formats in the returned array corresponds to
701      * the order of format elements in the pattern string.
702      * <p>
703      * Since the order of format elements in a pattern string often
704      * changes during localization, it's generally better to use the
705      * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}
706      * method, which assumes an order of formats corresponding to the
707      * order of elements in the <code>arguments</code> array passed to
708      * the <code>format</code> methods or the result array returned by
709      * the <code>parse</code> methods.
710      *
711      * @return the formats used for the format elements in the pattern
712      */

713     public Format JavaDoc[] getFormats() {
714         Format JavaDoc[] resultArray = new Format JavaDoc[maxOffset + 1];
715         System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);
716         return resultArray;
717     }
718
719     /**
720      * Formats an array of objects and appends the <code>MessageFormat</code>'s
721      * pattern, with format elements replaced by the formatted objects, to the
722      * provided <code>StringBuffer</code>.
723      * <p>
724      * The text substituted for the individual format elements is derived from
725      * the current subformat of the format element and the
726      * <code>arguments</code> element at the format element's argument index
727      * as indicated by the first matching line of the following table. An
728      * argument is <i>unavailable</i> if <code>arguments</code> is
729      * <code>null</code> or has fewer than argumentIndex+1 elements.
730      * <p>
731      * <table border=1 summary="Examples of subformat,argument,and formatted text">
732      * <tr>
733      * <th>Subformat
734      * <th>Argument
735      * <th>Formatted Text
736      * <tr>
737      * <td><i>any</i>
738      * <td><i>unavailable</i>
739      * <td><code>"{" + argumentIndex + "}"</code>
740      * <tr>
741      * <td><i>any</i>
742      * <td><code>null</code>
743      * <td><code>"null"</code>
744      * <tr>
745      * <td><code>instanceof ChoiceFormat</code>
746      * <td><i>any</i>
747      * <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br>
748      * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :
749      * subformat.format(argument)</code>
750      * <tr>
751      * <td><code>!= null</code>
752      * <td><i>any</i>
753      * <td><code>subformat.format(argument)</code>
754      * <tr>
755      * <td><code>null</code>
756      * <td><code>instanceof Number</code>
757      * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
758      * <tr>
759      * <td><code>null</code>
760      * <td><code>instanceof Date</code>
761      * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>
762      * <tr>
763      * <td><code>null</code>
764      * <td><code>instanceof String</code>
765      * <td><code>argument</code>
766      * <tr>
767      * <td><code>null</code>
768      * <td><i>any</i>
769      * <td><code>argument.toString()</code>
770      * </table>
771      * <p>
772      * If <code>pos</code> is non-null, and refers to
773      * <code>Field.ARGUMENT</code>, the location of the first formatted
774      * string will be returned.
775      *
776      * @param arguments an array of objects to be formatted and substituted.
777      * @param result where text is appended.
778      * @param pos On input: an alignment field, if desired.
779      * On output: the offsets of the alignment field.
780      * @exception IllegalArgumentException if an argument in the
781      * <code>arguments</code> array is not of the type
782      * expected by the format element(s) that use it.
783      */

784     public final StringBuffer JavaDoc format(Object JavaDoc[] arguments, StringBuffer JavaDoc result,
785                                      FieldPosition JavaDoc pos)
786     {
787         return subformat(arguments, result, pos, null);
788     }
789
790     /**
791      * Creates a MessageFormat with the given pattern and uses it
792      * to format the given arguments. This is equivalent to
793      * <blockquote>
794      * <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>
795      * </blockquote>
796      *
797      * @exception IllegalArgumentException if the pattern is invalid,
798      * or if an argument in the <code>arguments</code> array
799      * is not of the type expected by the format element(s)
800      * that use it.
801      */

802     public static String JavaDoc format(String JavaDoc pattern, Object JavaDoc ... arguments) {
803         MessageFormat JavaDoc temp = new MessageFormat JavaDoc(pattern);
804         return temp.format(arguments);
805     }
806
807     // Overrides
808
/**
809      * Formats an array of objects and appends the <code>MessageFormat</code>'s
810      * pattern, with format elements replaced by the formatted objects, to the
811      * provided <code>StringBuffer</code>.
812      * This is equivalent to
813      * <blockquote>
814      * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
815      * </blockquote>
816      *
817      * @param arguments an array of objects to be formatted and substituted.
818      * @param result where text is appended.
819      * @param pos On input: an alignment field, if desired.
820      * On output: the offsets of the alignment field.
821      * @exception IllegalArgumentException if an argument in the
822      * <code>arguments</code> array is not of the type
823      * expected by the format element(s) that use it.
824      */

825     public final StringBuffer JavaDoc format(Object JavaDoc arguments, StringBuffer JavaDoc result,
826                                      FieldPosition JavaDoc pos)
827     {
828         return subformat((Object JavaDoc[]) arguments, result, pos, null);
829     }
830
831     /**
832      * Formats an array of objects and inserts them into the
833      * <code>MessageFormat</code>'s pattern, producing an
834      * <code>AttributedCharacterIterator</code>.
835      * You can use the returned <code>AttributedCharacterIterator</code>
836      * to build the resulting String, as well as to determine information
837      * about the resulting String.
838      * <p>
839      * The text of the returned <code>AttributedCharacterIterator</code> is
840      * the same that would be returned by
841      * <blockquote>
842      * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
843      * </blockquote>
844      * <p>
845      * In addition, the <code>AttributedCharacterIterator</code> contains at
846      * least attributes indicating where text was generated from an
847      * argument in the <code>arguments</code> array. The keys of these attributes are of
848      * type <code>MessageFormat.Field</code>, their values are
849      * <code>Integer</code> objects indicating the index in the <code>arguments</code>
850      * array of the argument from which the text was generated.
851      * <p>
852      * The attributes/value from the underlying <code>Format</code>
853      * instances that <code>MessageFormat</code> uses will also be
854      * placed in the resulting <code>AttributedCharacterIterator</code>.
855      * This allows you to not only find where an argument is placed in the
856      * resulting String, but also which fields it contains in turn.
857      *
858      * @param arguments an array of objects to be formatted and substituted.
859      * @return AttributedCharacterIterator describing the formatted value.
860      * @exception NullPointerException if <code>arguments</code> is null.
861      * @exception IllegalArgumentException if an argument in the
862      * <code>arguments</code> array is not of the type
863      * expected by the format element(s) that use it.
864      * @since 1.4
865      */

866     public AttributedCharacterIterator JavaDoc formatToCharacterIterator(Object JavaDoc arguments) {
867         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
868         ArrayList JavaDoc iterators = new ArrayList JavaDoc();
869
870         if (arguments == null) {
871             throw new NullPointerException JavaDoc(
872                    "formatToCharacterIterator must be passed non-null object");
873         }
874         subformat((Object JavaDoc[]) arguments, result, null, iterators);
875         if (iterators.size() == 0) {
876             return createAttributedCharacterIterator("");
877         }
878         return createAttributedCharacterIterator(
879                      (AttributedCharacterIterator JavaDoc[])iterators.toArray(
880                      new AttributedCharacterIterator JavaDoc[iterators.size()]));
881     }
882
883     /**
884      * Parses the string.
885      *
886      * <p>Caveats: The parse may fail in a number of circumstances.
887      * For example:
888      * <ul>
889      * <li>If one of the arguments does not occur in the pattern.
890      * <li>If the format of an argument loses information, such as
891      * with a choice format where a large number formats to "many".
892      * <li>Does not yet handle recursion (where
893      * the substituted strings contain {n} references.)
894      * <li>Will not always find a match (or the correct match)
895      * if some part of the parse is ambiguous.
896      * For example, if the pattern "{1},{2}" is used with the
897      * string arguments {"a,b", "c"}, it will format as "a,b,c".
898      * When the result is parsed, it will return {"a", "b,c"}.
899      * <li>If a single argument is parsed more than once in the string,
900      * then the later parse wins.
901      * </ul>
902      * When the parse fails, use ParsePosition.getErrorIndex() to find out
903      * where in the string did the parsing failed. The returned error
904      * index is the starting offset of the sub-patterns that the string
905      * is comparing with. For example, if the parsing string "AAA {0} BBB"
906      * is comparing against the pattern "AAD {0} BBB", the error index is
907      * 0. When an error occurs, the call to this method will return null.
908      * If the source is null, return an empty array.
909      */

910     public Object JavaDoc[] parse(String JavaDoc source, ParsePosition JavaDoc pos) {
911         if (source == null) {
912             Object JavaDoc[] empty = {};
913             return empty;
914         }
915
916         int maximumArgumentNumber = -1;
917         for (int i = 0; i <= maxOffset; i++) {
918             if (argumentNumbers[i] > maximumArgumentNumber) {
919                 maximumArgumentNumber = argumentNumbers[i];
920             }
921         }
922         Object JavaDoc[] resultArray = new Object JavaDoc[maximumArgumentNumber + 1];
923
924         int patternOffset = 0;
925         int sourceOffset = pos.index;
926         ParsePosition JavaDoc tempStatus = new ParsePosition JavaDoc(0);
927         for (int i = 0; i <= maxOffset; ++i) {
928             // match up to format
929
int len = offsets[i] - patternOffset;
930             if (len == 0 || pattern.regionMatches(patternOffset,
931                                                   source, sourceOffset, len)) {
932                 sourceOffset += len;
933                 patternOffset += len;
934             } else {
935                 pos.errorIndex = sourceOffset;
936                 return null; // leave index as is to signal error
937
}
938
939             // now use format
940
if (formats[i] == null) { // string format
941
// if at end, use longest possible match
942
// otherwise uses first match to intervening string
943
// does NOT recursively try all possibilities
944
int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
945
946                 int next;
947                 if (patternOffset >= tempLength) {
948                     next = source.length();
949                 }else{
950                     next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset);
951                 }
952
953                 if (next < 0) {
954                     pos.errorIndex = sourceOffset;
955                     return null; // leave index as is to signal error
956
} else {
957                     String JavaDoc strValue= source.substring(sourceOffset,next);
958                     if (!strValue.equals("{"+argumentNumbers[i]+"}"))
959                         resultArray[argumentNumbers[i]]
960                             = source.substring(sourceOffset,next);
961                     sourceOffset = next;
962                 }
963             } else {
964                 tempStatus.index = sourceOffset;
965                 resultArray[argumentNumbers[i]]
966                     = formats[i].parseObject(source,tempStatus);
967                 if (tempStatus.index == sourceOffset) {
968                     pos.errorIndex = sourceOffset;
969                     return null; // leave index as is to signal error
970
}
971                 sourceOffset = tempStatus.index; // update
972
}
973         }
974         int len = pattern.length() - patternOffset;
975         if (len == 0 || pattern.regionMatches(patternOffset,
976                                               source, sourceOffset, len)) {
977             pos.index = sourceOffset + len;
978         } else {
979             pos.errorIndex = sourceOffset;
980             return null; // leave index as is to signal error
981
}
982         return resultArray;
983     }
984
985     /**
986      * Parses text from the beginning of the given string to produce an object
987      * array.
988      * The method may not use the entire text of the given string.
989      * <p>
990      * See the {@link #parse(String, ParsePosition)} method for more information
991      * on message parsing.
992      *
993      * @param source A <code>String</code> whose beginning should be parsed.
994      * @return An <code>Object</code> array parsed from the string.
995      * @exception ParseException if the beginning of the specified string
996      * cannot be parsed.
997      */

998     public Object JavaDoc[] parse(String JavaDoc source) throws ParseException JavaDoc {
999         ParsePosition JavaDoc pos = new ParsePosition JavaDoc(0);
1000        Object JavaDoc[] result = parse(source, pos);
1001        if (pos.index == 0) // unchanged, returned object is null
1002
throw new ParseException JavaDoc("MessageFormat parse error!", pos.errorIndex);
1003
1004        return result;
1005    }
1006
1007    /**
1008     * Parses text from a string to produce an object array.
1009     * <p>
1010     * The method attempts to parse text starting at the index given by
1011     * <code>pos</code>.
1012     * If parsing succeeds, then the index of <code>pos</code> is updated
1013     * to the index after the last character used (parsing does not necessarily
1014     * use all characters up to the end of the string), and the parsed
1015     * object array is returned. The updated <code>pos</code> can be used to
1016     * indicate the starting point for the next call to this method.
1017     * If an error occurs, then the index of <code>pos</code> is not
1018     * changed, the error index of <code>pos</code> is set to the index of
1019     * the character where the error occurred, and null is returned.
1020     * <p>
1021     * See the {@link #parse(String, ParsePosition)} method for more information
1022     * on message parsing.
1023     *
1024     * @param source A <code>String</code>, part of which should be parsed.
1025     * @param pos A <code>ParsePosition</code> object with index and error
1026     * index information as described above.
1027     * @return An <code>Object</code> array parsed from the string. In case of
1028     * error, returns null.
1029     * @exception NullPointerException if <code>pos</code> is null.
1030     */

1031    public Object JavaDoc parseObject(String JavaDoc source, ParsePosition JavaDoc pos) {
1032        return parse(source, pos);
1033    }
1034
1035    /**
1036     * Creates and returns a copy of this object.
1037     *
1038     * @return a clone of this instance.
1039     */

1040    public Object JavaDoc clone() {
1041        MessageFormat JavaDoc other = (MessageFormat JavaDoc) super.clone();
1042
1043        // clone arrays. Can't do with utility because of bug in Cloneable
1044
other.formats = (Format JavaDoc[]) formats.clone(); // shallow clone
1045
for (int i = 0; i < formats.length; ++i) {
1046            if (formats[i] != null)
1047                other.formats[i] = (Format JavaDoc)formats[i].clone();
1048        }
1049        // for primitives or immutables, shallow clone is enough
1050
other.offsets = (int[]) offsets.clone();
1051        other.argumentNumbers = (int[]) argumentNumbers.clone();
1052
1053        return other;
1054    }
1055
1056    /**
1057     * Equality comparison between two message format objects
1058     */

1059    public boolean equals(Object JavaDoc obj) {
1060        if (this == obj) // quick check
1061
return true;
1062        if (obj == null || getClass() != obj.getClass())
1063            return false;
1064        MessageFormat JavaDoc other = (MessageFormat JavaDoc) obj;
1065        return (maxOffset == other.maxOffset
1066                && pattern.equals(other.pattern)
1067            && Utility.objectEquals(locale, other.locale) // does null check
1068
&& Utility.arrayEquals(offsets,other.offsets)
1069            && Utility.arrayEquals(argumentNumbers,other.argumentNumbers)
1070            && Utility.arrayEquals(formats,other.formats));
1071    }
1072
1073    /**
1074     * Generates a hash code for the message format object.
1075     */

1076    public int hashCode() {
1077        return pattern.hashCode(); // enough for reasonable distribution
1078
}
1079
1080
1081    /**
1082     * Defines constants that are used as attribute keys in the
1083     * <code>AttributedCharacterIterator</code> returned
1084     * from <code>MessageFormat.formatToCharacterIterator</code>.
1085     *
1086     * @since 1.4
1087     */

1088    public static class Field extends Format.Field JavaDoc {
1089
1090        // Proclaim serial compatibility with 1.4 FCS
1091
private static final long serialVersionUID = 7899943957617360810L;
1092
1093        /**
1094         * Creates a Field with the specified name.
1095         *
1096         * @param name Name of the attribute
1097         */

1098        protected Field(String JavaDoc name) {
1099            super(name);
1100        }
1101
1102        /**
1103         * Resolves instances being deserialized to the predefined constants.
1104         *
1105         * @throws InvalidObjectException if the constant could not be
1106         * resolved.
1107         * @return resolved MessageFormat.Field constant
1108         */

1109        protected Object JavaDoc readResolve() throws InvalidObjectException JavaDoc {
1110            if (this.getClass() != MessageFormat.Field JavaDoc.class) {
1111                throw new InvalidObjectException JavaDoc("subclass didn't correctly implement readResolve");
1112            }
1113
1114            return ARGUMENT;
1115        }
1116
1117        //
1118
// The constants
1119
//
1120

1121        /**
1122         * Constant identifying a portion of a message that was generated
1123         * from an argument passed into <code>formatToCharacterIterator</code>.
1124         * The value associated with the key will be an <code>Integer</code>
1125         * indicating the index in the <code>arguments</code> array of the
1126         * argument from which the text was generated.
1127         */

1128        public final static Field ARGUMENT =
1129                           new Field("message argument field");
1130    }
1131
1132    // ===========================privates============================
1133

1134    /**
1135     * The locale to use for formatting numbers and dates.
1136     * @serial
1137     */

1138    private Locale JavaDoc locale;
1139
1140    /**
1141     * The string that the formatted values are to be plugged into. In other words, this
1142     * is the pattern supplied on construction with all of the {} expressions taken out.
1143     * @serial
1144     */

1145    private String JavaDoc pattern = "";
1146
1147    /** The initially expected number of subformats in the format */
1148    private static final int INITIAL_FORMATS = 10;
1149
1150    /**
1151     * An array of formatters, which are used to format the arguments.
1152     * @serial
1153     */

1154    private Format JavaDoc[] formats = new Format JavaDoc[INITIAL_FORMATS];
1155
1156    /**
1157     * The positions where the results of formatting each argument are to be inserted
1158     * into the pattern.
1159     * @serial
1160     */

1161    private int[] offsets = new int[INITIAL_FORMATS];
1162
1163    /**
1164     * The argument numbers corresponding to each formatter. (The formatters are stored
1165     * in the order they occur in the pattern, not in the order in which the arguments
1166     * are specified.)
1167     * @serial
1168     */

1169    private int[] argumentNumbers = new int[INITIAL_FORMATS];
1170
1171    /**
1172     * One less than the number of entries in <code>offsets</code>. Can also be thought of
1173     * as the index of the highest-numbered element in <code>offsets</code> that is being used.
1174     * All of these arrays should have the same number of elements being used as <code>offsets</code>
1175     * does, and so this variable suffices to tell us how many entries are in all of them.
1176     * @serial
1177     */

1178    private int maxOffset = -1;
1179
1180    /**
1181     * Internal routine used by format. If <code>characterIterators</code> is
1182     * non-null, AttributedCharacterIterator will be created from the
1183     * subformats as necessary. If <code>characterIterators</code> is null
1184     * and <code>fp</code> is non-null and identifies
1185     * <code>Field.MESSAGE_ARGUMENT</code>, the location of
1186     * the first replaced argument will be set in it.
1187     *
1188     * @exception IllegalArgumentException if an argument in the
1189     * <code>arguments</code> array is not of the type
1190     * expected by the format element(s) that use it.
1191     */

1192    private StringBuffer JavaDoc subformat(Object JavaDoc[] arguments, StringBuffer JavaDoc result,
1193                                   FieldPosition JavaDoc fp, List JavaDoc characterIterators) {
1194        // note: this implementation assumes a fast substring & index.
1195
// if this is not true, would be better to append chars one by one.
1196
int lastOffset = 0;
1197        int last = result.length();
1198        for (int i = 0; i <= maxOffset; ++i) {
1199            result.append(pattern.substring(lastOffset, offsets[i]));
1200            lastOffset = offsets[i];
1201            int argumentNumber = argumentNumbers[i];
1202            if (arguments == null || argumentNumber >= arguments.length) {
1203                result.append("{" + argumentNumber + "}");
1204                continue;
1205            }
1206            // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
1207
if (false) { // if (argRecursion == 3){
1208
// prevent loop!!!
1209
result.append('\uFFFD');
1210            } else {
1211                Object JavaDoc obj = arguments[argumentNumber];
1212                String JavaDoc arg = null;
1213                Format JavaDoc subFormatter = null;
1214                if (obj == null) {
1215                    arg = "null";
1216                } else if (formats[i] != null) {
1217                    subFormatter = formats[i];
1218                    if (subFormatter instanceof ChoiceFormat JavaDoc) {
1219                        arg = formats[i].format(obj);
1220                        if (arg.indexOf('{') >= 0) {
1221                            subFormatter = new MessageFormat JavaDoc(arg, locale);
1222                            obj = arguments;
1223                            arg = null;
1224                        }
1225                    }
1226                } else if (obj instanceof Number JavaDoc) {
1227                    // format number if can
1228
subFormatter = NumberFormat.getInstance(locale);
1229                } else if (obj instanceof Date JavaDoc) {
1230                    // format a Date if can
1231
subFormatter = DateFormat.getDateTimeInstance(
1232                             DateFormat.SHORT, DateFormat.SHORT, locale);//fix
1233
} else if (obj instanceof String JavaDoc) {
1234                    arg = (String JavaDoc) obj;
1235
1236                } else {
1237                    arg = obj.toString();
1238                    if (arg == null) arg = "null";
1239                }
1240
1241                // At this point we are in two states, either subFormatter
1242
// is non-null indicating we should format obj using it,
1243
// or arg is non-null and we should use it as the value.
1244

1245                if (characterIterators != null) {
1246                    // If characterIterators is non-null, it indicates we need
1247
// to get the CharacterIterator from the child formatter.
1248
if (last != result.length()) {
1249                        characterIterators.add(
1250                            createAttributedCharacterIterator(result.substring
1251                                                              (last)));
1252                        last = result.length();
1253                    }
1254                    if (subFormatter != null) {
1255                        AttributedCharacterIterator JavaDoc subIterator =
1256                                   subFormatter.formatToCharacterIterator(obj);
1257
1258                        append(result, subIterator);
1259                        if (last != result.length()) {
1260                            characterIterators.add(
1261                                         createAttributedCharacterIterator(
1262                                         subIterator, Field.ARGUMENT,
1263                                         new Integer JavaDoc(argumentNumber)));
1264                            last = result.length();
1265                        }
1266                        arg = null;
1267                    }
1268                    if (arg != null && arg.length() > 0) {
1269                        result.append(arg);
1270                        characterIterators.add(
1271                                 createAttributedCharacterIterator(
1272                                 arg, Field.ARGUMENT,
1273                                 new Integer JavaDoc(argumentNumber)));
1274                        last = result.length();
1275                    }
1276                }
1277                else {
1278                    if (subFormatter != null) {
1279                        arg = subFormatter.format(obj);
1280                    }
1281                    last = result.length();
1282                    result.append(arg);
1283                    if (i == 0 && fp != null && Field.ARGUMENT.equals(
1284                                  fp.getFieldAttribute())) {
1285                        fp.setBeginIndex(last);
1286                        fp.setEndIndex(result.length());
1287                    }
1288                    last = result.length();
1289                }
1290            }
1291        }
1292        result.append(pattern.substring(lastOffset, pattern.length()));
1293        if (characterIterators != null && last != result.length()) {
1294            characterIterators.add(createAttributedCharacterIterator(
1295                                   result.substring(last)));
1296        }
1297        return result;
1298    }
1299
1300    /**
1301     * Convenience method to append all the characters in
1302     * <code>iterator</code> to the StringBuffer <code>result</code>.
1303     */

1304    private void append(StringBuffer JavaDoc result, CharacterIterator JavaDoc iterator) {
1305        if (iterator.first() != CharacterIterator.DONE) {
1306            char aChar;
1307
1308            result.append(iterator.first());
1309            while ((aChar = iterator.next()) != CharacterIterator.DONE) {
1310                result.append(aChar);
1311            }
1312        }
1313    }
1314
1315    private static final String JavaDoc[] typeList =
1316    {"", "", "number", "", "date", "", "time", "", "choice"};
1317    private static final String JavaDoc[] modifierList =
1318    {"", "", "currency", "", "percent", "", "integer"};
1319    private static final String JavaDoc[] dateModifierList =
1320    {"", "", "short", "", "medium", "", "long", "", "full"};
1321
1322    private void makeFormat(int position, int offsetNumber,
1323                            StringBuffer JavaDoc[] segments)
1324    {
1325        // get the argument number
1326
int argumentNumber;
1327        try {
1328            argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
1329
} catch (NumberFormatException JavaDoc e) {
1330            throw new IllegalArgumentException JavaDoc("can't parse argument number " + segments[1]);
1331        }
1332        if (argumentNumber < 0) {
1333            throw new IllegalArgumentException JavaDoc("negative argument number " + argumentNumber);
1334        }
1335
1336        // resize format information arrays if necessary
1337
if (offsetNumber >= formats.length) {
1338            int newLength = formats.length * 2;
1339            Format JavaDoc[] newFormats = new Format JavaDoc[newLength];
1340            int[] newOffsets = new int[newLength];
1341            int[] newArgumentNumbers = new int[newLength];
1342            System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);
1343            System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);
1344            System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);
1345            formats = newFormats;
1346            offsets = newOffsets;
1347            argumentNumbers = newArgumentNumbers;
1348        }
1349        int oldMaxOffset = maxOffset;
1350        maxOffset = offsetNumber;
1351        offsets[offsetNumber] = segments[0].length();
1352        argumentNumbers[offsetNumber] = argumentNumber;
1353
1354        // now get the format
1355
Format JavaDoc newFormat = null;
1356        switch (findKeyword(segments[2].toString(), typeList)) {
1357        case 0:
1358            break;
1359        case 1: case 2:// number
1360
switch (findKeyword(segments[3].toString(), modifierList)) {
1361            case 0: // default;
1362
newFormat = NumberFormat.getInstance(locale);
1363                break;
1364            case 1: case 2:// currency
1365
newFormat = NumberFormat.getCurrencyInstance(locale);
1366                break;
1367            case 3: case 4:// percent
1368
newFormat = NumberFormat.getPercentInstance(locale);
1369                break;
1370            case 5: case 6:// integer
1371
newFormat = NumberFormat.getIntegerInstance(locale);
1372                break;
1373            default: // pattern
1374
newFormat = new DecimalFormat JavaDoc(segments[3].toString(), new DecimalFormatSymbols JavaDoc(locale));
1375                break;
1376            }
1377            break;
1378        case 3: case 4: // date
1379
switch (findKeyword(segments[3].toString(), dateModifierList)) {
1380            case 0: // default
1381
newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
1382                break;
1383            case 1: case 2: // short
1384
newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
1385                break;
1386            case 3: case 4: // medium
1387
newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
1388                break;
1389            case 5: case 6: // long
1390
newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
1391                break;
1392            case 7: case 8: // full
1393
newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
1394                break;
1395            default:
1396                newFormat = new SimpleDateFormat JavaDoc(segments[3].toString(), locale);
1397                break;
1398            }
1399            break;
1400        case 5: case 6:// time
1401
switch (findKeyword(segments[3].toString(), dateModifierList)) {
1402            case 0: // default
1403
newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
1404                break;
1405            case 1: case 2: // short
1406
newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
1407                break;
1408            case 3: case 4: // medium
1409
newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
1410                break;
1411            case 5: case 6: // long
1412
newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale);
1413                break;
1414            case 7: case 8: // full
1415
newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale);
1416                break;
1417            default:
1418                newFormat = new SimpleDateFormat JavaDoc(segments[3].toString(), locale);
1419                break;
1420            }
1421            break;
1422        case 7: case 8:// choice
1423
try {
1424                newFormat = new ChoiceFormat JavaDoc(segments[3].toString());
1425            } catch (Exception JavaDoc e) {
1426                maxOffset = oldMaxOffset;
1427                throw new IllegalArgumentException JavaDoc(
1428                                         "Choice Pattern incorrect");
1429            }
1430            break;
1431        default:
1432            maxOffset = oldMaxOffset;
1433            throw new IllegalArgumentException JavaDoc("unknown format type at ");
1434        }
1435        formats[offsetNumber] = newFormat;
1436        segments[1].setLength(0); // throw away other segments
1437
segments[2].setLength(0);
1438        segments[3].setLength(0);
1439    }
1440
1441    private static final int findKeyword(String JavaDoc s, String JavaDoc[] list) {
1442        s = s.trim().toLowerCase();
1443        for (int i = 0; i < list.length; ++i) {
1444            if (s.equals(list[i]))
1445                return i;
1446        }
1447        return -1;
1448    }
1449
1450    private static final void copyAndFixQuotes(
1451                                               String JavaDoc source, int start, int end, StringBuffer JavaDoc target) {
1452        for (int i = start; i < end; ++i) {
1453            char ch = source.charAt(i);
1454            if (ch == '{') {
1455                target.append("'{'");
1456            } else if (ch == '}') {
1457                target.append("'}'");
1458            } else if (ch == '\'') {
1459                target.append("''");
1460            } else {
1461                target.append(ch);
1462            }
1463        }
1464    }
1465
1466    /**
1467     * After reading an object from the input stream, do a simple verification
1468     * to maintain class invariants.
1469     * @throws InvalidObjectException if the objects read from the stream is invalid.
1470     */

1471    private void readObject(ObjectInputStream JavaDoc in) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
1472        in.defaultReadObject();
1473        boolean isValid = maxOffset >= -1
1474                && formats.length > maxOffset
1475                && offsets.length > maxOffset
1476                && argumentNumbers.length > maxOffset;
1477        if (isValid) {
1478            int lastOffset = pattern.length() + 1;
1479            for (int i = maxOffset; i >= 0; --i) {
1480                if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
1481                    isValid = false;
1482                    break;
1483                } else {
1484                    lastOffset = offsets[i];
1485                }
1486            }
1487        }
1488        if (!isValid) {
1489            throw new InvalidObjectException JavaDoc("Could not reconstruct MessageFormat from corrupt stream.");
1490        }
1491    }
1492}
1493
Popular Tags