0001 /*
0002 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 /*
0027 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
0028 * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
0029 *
0030 * The original version of this source code and documentation is copyrighted
0031 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
0032 * materials are provided under terms of a License Agreement between Taligent
0033 * and Sun. This technology is protected by multiple US and International
0034 * patents. This notice and attribution to Taligent may not be removed.
0035 * Taligent is a registered trademark of Taligent, Inc.
0036 *
0037 */
0038
0039 package java.text;
0040
0041 import java.io.InvalidObjectException;
0042 import java.io.IOException;
0043 import java.io.ObjectInputStream;
0044 import java.text.DecimalFormat;
0045 import java.util.ArrayList;
0046 import java.util.Arrays;
0047 import java.util.Date;
0048 import java.util.List;
0049 import java.util.Locale;
0050
0051 /**
0052 * <code>MessageFormat</code> provides a means to produce concatenated
0053 * messages in a language-neutral way. Use this to construct messages
0054 * displayed for end users.
0055 *
0056 * <p>
0057 * <code>MessageFormat</code> takes a set of objects, formats them, then
0058 * inserts the formatted strings into the pattern at the appropriate places.
0059 *
0060 * <p>
0061 * <strong>Note:</strong>
0062 * <code>MessageFormat</code> differs from the other <code>Format</code>
0063 * classes in that you create a <code>MessageFormat</code> object with one
0064 * of its constructors (not with a <code>getInstance</code> style factory
0065 * method). The factory methods aren't necessary because <code>MessageFormat</code>
0066 * itself doesn't implement locale specific behavior. Any locale specific
0067 * behavior is defined by the pattern that you provide as well as the
0068 * subformats used for inserted arguments.
0069 *
0070 * <h4><a name="patterns">Patterns and Their Interpretation</a></h4>
0071 *
0072 * <code>MessageFormat</code> uses patterns of the following form:
0073 * <blockquote><pre>
0074 * <i>MessageFormatPattern:</i>
0075 * <i>String</i>
0076 * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>
0077 *
0078 * <i>FormatElement:</i>
0079 * { <i>ArgumentIndex</i> }
0080 * { <i>ArgumentIndex</i> , <i>FormatType</i> }
0081 * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
0082 *
0083 * <i>FormatType: one of </i>
0084 * number date time choice
0085 *
0086 * <i>FormatStyle:</i>
0087 * short
0088 * medium
0089 * long
0090 * full
0091 * integer
0092 * currency
0093 * percent
0094 * <i>SubformatPattern</i>
0095 *
0096 * <i>String:</i>
0097 * <i>StringPart<sub>opt</sub></i>
0098 * <i>String</i> <i>StringPart</i>
0099 *
0100 * <i>StringPart:</i>
0101 * ''
0102 * ' <i>QuotedString</i> '
0103 * <i>UnquotedString</i>
0104 *
0105 * <i>SubformatPattern:</i>
0106 * <i>SubformatPatternPart<sub>opt</sub></i>
0107 * <i>SubformatPattern</i> <i>SubformatPatternPart</i>
0108 *
0109 * <i>SubFormatPatternPart:</i>
0110 * ' <i>QuotedPattern</i> '
0111 * <i>UnquotedPattern</i>
0112 * </pre></blockquote>
0113 *
0114 * <p>
0115 * Within a <i>String</i>, <code>"''"</code> represents a single
0116 * quote. A <i>QuotedString</i> can contain arbitrary characters
0117 * except single quotes; the surrounding single quotes are removed.
0118 * An <i>UnquotedString</i> can contain arbitrary characters
0119 * except single quotes and left curly brackets. Thus, a string that
0120 * should result in the formatted message "'{0}'" can be written as
0121 * <code>"'''{'0}''"</code> or <code>"'''{0}'''"</code>.
0122 * <p>
0123 * Within a <i>SubformatPattern</i>, different rules apply.
0124 * A <i>QuotedPattern</i> can contain arbitrary characters
0125 * except single quotes; but the surrounding single quotes are
0126 * <strong>not</strong> removed, so they may be interpreted by the
0127 * subformat. For example, <code>"{1,number,$'#',##}"</code> will
0128 * produce a number format with the pound-sign quoted, with a result
0129 * such as: "$#31,45".
0130 * An <i>UnquotedPattern</i> can contain arbitrary characters
0131 * except single quotes, but curly braces within it must be balanced.
0132 * For example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code>
0133 * are valid subformat patterns, but <code>"ab {0'}' de"</code> and
0134 * <code>"ab } de"</code> are not.
0135 * <p>
0136 * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message
0137 * format patterns unfortunately have shown to be somewhat confusing.
0138 * In particular, it isn't always obvious to localizers whether single
0139 * quotes need to be doubled or not. Make sure to inform localizers about
0140 * the rules, and tell them (for example, by using comments in resource
0141 * bundle source files) which strings will be processed by MessageFormat.
0142 * Note that localizers may need to use single quotes in translated
0143 * strings where the original version doesn't have them.
0144 * </dl>
0145 * <p>
0146 * The <i>ArgumentIndex</i> value is a non-negative integer written
0147 * using the digits '0' through '9', and represents an index into the
0148 * <code>arguments</code> array passed to the <code>format</code> methods
0149 * or the result array returned by the <code>parse</code> methods.
0150 * <p>
0151 * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create
0152 * a <code>Format</code> instance for the format element. The following
0153 * table shows how the values map to Format instances. Combinations not
0154 * shown in the table are illegal. A <i>SubformatPattern</i> must
0155 * be a valid pattern string for the Format subclass used.
0156 * <p>
0157 * <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances">
0158 * <tr>
0159 * <th id="ft">Format Type
0160 * <th id="fs">Format Style
0161 * <th id="sc">Subformat Created
0162 * <tr>
0163 * <td headers="ft"><i>(none)</i>
0164 * <td headers="fs"><i>(none)</i>
0165 * <td headers="sc"><code>null</code>
0166 * <tr>
0167 * <td headers="ft" rowspan=5><code>number</code>
0168 * <td headers="fs"><i>(none)</i>
0169 * <td headers="sc"><code>NumberFormat.getInstance(getLocale())</code>
0170 * <tr>
0171 * <td headers="fs"><code>integer</code>
0172 * <td headers="sc"><code>NumberFormat.getIntegerInstance(getLocale())</code>
0173 * <tr>
0174 * <td headers="fs"><code>currency</code>
0175 * <td headers="sc"><code>NumberFormat.getCurrencyInstance(getLocale())</code>
0176 * <tr>
0177 * <td headers="fs"><code>percent</code>
0178 * <td headers="sc"><code>NumberFormat.getPercentInstance(getLocale())</code>
0179 * <tr>
0180 * <td headers="fs"><i>SubformatPattern</i>
0181 * <td headers="sc"><code>new DecimalFormat(subformatPattern, DecimalFormatSymbols.getInstance(getLocale()))</code>
0182 * <tr>
0183 * <td headers="ft" rowspan=6><code>date</code>
0184 * <td headers="fs"><i>(none)</i>
0185 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
0186 * <tr>
0187 * <td headers="fs"><code>short</code>
0188 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
0189 * <tr>
0190 * <td headers="fs"><code>medium</code>
0191 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
0192 * <tr>
0193 * <td headers="fs"><code>long</code>
0194 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
0195 * <tr>
0196 * <td headers="fs"><code>full</code>
0197 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
0198 * <tr>
0199 * <td headers="fs"><i>SubformatPattern</i>
0200 * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code>
0201 * <tr>
0202 * <td headers="ft" rowspan=6><code>time</code>
0203 * <td headers="fs"><i>(none)</i>
0204 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
0205 * <tr>
0206 * <td headers="fs"><code>short</code>
0207 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
0208 * <tr>
0209 * <td headers="fs"><code>medium</code>
0210 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
0211 * <tr>
0212 * <td headers="fs"><code>long</code>
0213 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
0214 * <tr>
0215 * <td headers="fs"><code>full</code>
0216 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
0217 * <tr>
0218 * <td headers="fs"><i>SubformatPattern</i>
0219 * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code>
0220 * <tr>
0221 * <td headers="ft"><code>choice</code>
0222 * <td headers="fs"><i>SubformatPattern</i>
0223 * <td headers="sc"><code>new ChoiceFormat(subformatPattern)</code>
0224 * </table>
0225 * <p>
0226 *
0227 * <h4>Usage Information</h4>
0228 *
0229 * <p>
0230 * Here are some examples of usage.
0231 * In real internationalized programs, the message format pattern and other
0232 * static strings will, of course, be obtained from resource bundles.
0233 * Other parameters will be dynamically determined at runtime.
0234 * <p>
0235 * The first example uses the static method <code>MessageFormat.format</code>,
0236 * which internally creates a <code>MessageFormat</code> for one-time use:
0237 * <blockquote><pre>
0238 * int planet = 7;
0239 * String event = "a disturbance in the Force";
0240 *
0241 * String result = MessageFormat.format(
0242 * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
0243 * planet, new Date(), event);
0244 * </pre></blockquote>
0245 * The output is:
0246 * <blockquote><pre>
0247 * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
0248 * </pre></blockquote>
0249 *
0250 * <p>
0251 * The following example creates a <code>MessageFormat</code> instance that
0252 * can be used repeatedly:
0253 * <blockquote><pre>
0254 * int fileCount = 1273;
0255 * String diskName = "MyDisk";
0256 * Object[] testArgs = {new Long(fileCount), diskName};
0257 *
0258 * MessageFormat form = new MessageFormat(
0259 * "The disk \"{1}\" contains {0} file(s).");
0260 *
0261 * System.out.println(form.format(testArgs));
0262 * </pre></blockquote>
0263 * The output with different values for <code>fileCount</code>:
0264 * <blockquote><pre>
0265 * The disk "MyDisk" contains 0 file(s).
0266 * The disk "MyDisk" contains 1 file(s).
0267 * The disk "MyDisk" contains 1,273 file(s).
0268 * </pre></blockquote>
0269 *
0270 * <p>
0271 * For more sophisticated patterns, you can use a <code>ChoiceFormat</code>
0272 * to produce correct forms for singular and plural:
0273 * <blockquote><pre>
0274 * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
0275 * double[] filelimits = {0,1,2};
0276 * String[] filepart = {"no files","one file","{0,number} files"};
0277 * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
0278 * form.setFormatByArgumentIndex(0, fileform);
0279 *
0280 * int fileCount = 1273;
0281 * String diskName = "MyDisk";
0282 * Object[] testArgs = {new Long(fileCount), diskName};
0283 *
0284 * System.out.println(form.format(testArgs));
0285 * </pre></blockquote>
0286 * The output with different values for <code>fileCount</code>:
0287 * <blockquote><pre>
0288 * The disk "MyDisk" contains no files.
0289 * The disk "MyDisk" contains one file.
0290 * The disk "MyDisk" contains 1,273 files.
0291 * </pre></blockquote>
0292 *
0293 * <p>
0294 * You can create the <code>ChoiceFormat</code> programmatically, as in the
0295 * above example, or by using a pattern. See {@link ChoiceFormat}
0296 * for more information.
0297 * <blockquote><pre>
0298 * form.applyPattern(
0299 * "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");
0300 * </pre></blockquote>
0301 *
0302 * <p>
0303 * <strong>Note:</strong> As we see above, the string produced
0304 * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated as special;
0305 * occurrences of '{' are used to indicate subformats, and cause recursion.
0306 * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
0307 * programmatically (instead of using the string patterns), then be careful not to
0308 * produce a format that recurses on itself, which will cause an infinite loop.
0309 * <p>
0310 * When a single argument is parsed more than once in the string, the last match
0311 * will be the final result of the parsing. For example,
0312 * <blockquote><pre>
0313 * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
0314 * Object[] objs = {new Double(3.1415)};
0315 * String result = mf.format( objs );
0316 * // result now equals "3.14, 3.1"
0317 * objs = null;
0318 * objs = mf.parse(result, new ParsePosition(0));
0319 * // objs now equals {new Double(3.1)}
0320 * </pre></blockquote>
0321 *
0322 * <p>
0323 * Likewise, parsing with a MessageFormat object using patterns containing
0324 * multiple occurrences of the same argument would return the last match. For
0325 * example,
0326 * <blockquote><pre>
0327 * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
0328 * String forParsing = "x, y, z";
0329 * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
0330 * // result now equals {new String("z")}
0331 * </pre></blockquote>
0332 *
0333 * <h4><a name="synchronization">Synchronization</a></h4>
0334 *
0335 * <p>
0336 * Message formats are not synchronized.
0337 * It is recommended to create separate format instances for each thread.
0338 * If multiple threads access a format concurrently, it must be synchronized
0339 * externally.
0340 *
0341 * @see java.util.Locale
0342 * @see Format
0343 * @see NumberFormat
0344 * @see DecimalFormat
0345 * @see ChoiceFormat
0346 * @version 1.69, 05/05/07
0347 * @author Mark Davis
0348 */
0349
0350 public class MessageFormat extends Format {
0351
0352 private static final long serialVersionUID = 6479157306784022952L;
0353
0354 /**
0355 * Constructs a MessageFormat for the default locale and the
0356 * specified pattern.
0357 * The constructor first sets the locale, then parses the pattern and
0358 * creates a list of subformats for the format elements contained in it.
0359 * Patterns and their interpretation are specified in the
0360 * <a href="#patterns">class description</a>.
0361 *
0362 * @param pattern the pattern for this message format
0363 * @exception IllegalArgumentException if the pattern is invalid
0364 */
0365 public MessageFormat(String pattern) {
0366 this .locale = Locale.getDefault();
0367 applyPattern(pattern);
0368 }
0369
0370 /**
0371 * Constructs a MessageFormat for the specified locale and
0372 * pattern.
0373 * The constructor first sets the locale, then parses the pattern and
0374 * creates a list of subformats for the format elements contained in it.
0375 * Patterns and their interpretation are specified in the
0376 * <a href="#patterns">class description</a>.
0377 *
0378 * @param pattern the pattern for this message format
0379 * @param locale the locale for this message format
0380 * @exception IllegalArgumentException if the pattern is invalid
0381 * @since 1.4
0382 */
0383 public MessageFormat(String pattern, Locale locale) {
0384 this .locale = locale;
0385 applyPattern(pattern);
0386 }
0387
0388 /**
0389 * Sets the locale to be used when creating or comparing subformats.
0390 * This affects subsequent calls
0391 * <ul>
0392 * <li>to the {@link #applyPattern applyPattern}
0393 * and {@link #toPattern toPattern} methods if format elements specify
0394 * a format type and therefore have the subformats created in the
0395 * <code>applyPattern</code> method, as well as
0396 * <li>to the <code>format</code> and
0397 * {@link #formatToCharacterIterator formatToCharacterIterator} methods
0398 * if format elements do not specify a format type and therefore have
0399 * the subformats created in the formatting methods.
0400 * </ul>
0401 * Subformats that have already been created are not affected.
0402 *
0403 * @param locale the locale to be used when creating or comparing subformats
0404 */
0405 public void setLocale(Locale locale) {
0406 this .locale = locale;
0407 }
0408
0409 /**
0410 * Gets the locale that's used when creating or comparing subformats.
0411 *
0412 * @return the locale used when creating or comparing subformats
0413 */
0414 public Locale getLocale() {
0415 return locale;
0416 }
0417
0418 /**
0419 * Sets the pattern used by this message format.
0420 * The method parses the pattern and creates a list of subformats
0421 * for the format elements contained in it.
0422 * Patterns and their interpretation are specified in the
0423 * <a href="#patterns">class description</a>.
0424 *
0425 * @param pattern the pattern for this message format
0426 * @exception IllegalArgumentException if the pattern is invalid
0427 */
0428 public void applyPattern(String pattern) {
0429 StringBuffer[] segments = new StringBuffer[4];
0430 for (int i = 0; i < segments.length; ++i) {
0431 segments[i] = new StringBuffer();
0432 }
0433 int part = 0;
0434 int formatNumber = 0;
0435 boolean inQuote = false;
0436 int braceStack = 0;
0437 maxOffset = -1;
0438 for (int i = 0; i < pattern.length(); ++i) {
0439 char ch = pattern.charAt(i);
0440 if (part == 0) {
0441 if (ch == '\'') {
0442 if (i + 1 < pattern.length()
0443 && pattern.charAt(i + 1) == '\'') {
0444 segments[part].append(ch); // handle doubles
0445 ++i;
0446 } else {
0447 inQuote = !inQuote;
0448 }
0449 } else if (ch == '{' && !inQuote) {
0450 part = 1;
0451 } else {
0452 segments[part].append(ch);
0453 }
0454 } else if (inQuote) { // just copy quotes in parts
0455 segments[part].append(ch);
0456 if (ch == '\'') {
0457 inQuote = false;
0458 }
0459 } else {
0460 switch (ch) {
0461 case ',':
0462 if (part < 3)
0463 part += 1;
0464 else
0465 segments[part].append(ch);
0466 break;
0467 case '{':
0468 ++braceStack;
0469 segments[part].append(ch);
0470 break;
0471 case '}':
0472 if (braceStack == 0) {
0473 part = 0;
0474 makeFormat(i, formatNumber, segments);
0475 formatNumber++;
0476 } else {
0477 --braceStack;
0478 segments[part].append(ch);
0479 }
0480 break;
0481 case '\'':
0482 inQuote = true;
0483 // fall through, so we keep quotes in other parts
0484 default:
0485 segments[part].append(ch);
0486 break;
0487 }
0488 }
0489 }
0490 if (braceStack == 0 && part != 0) {
0491 maxOffset = -1;
0492 throw new IllegalArgumentException(
0493 "Unmatched braces in the pattern.");
0494 }
0495 this .pattern = segments[0].toString();
0496 }
0497
0498 /**
0499 * Returns a pattern representing the current state of the message format.
0500 * The string is constructed from internal information and therefore
0501 * does not necessarily equal the previously applied pattern.
0502 *
0503 * @return a pattern representing the current state of the message format
0504 */
0505 public String toPattern() {
0506 // later, make this more extensible
0507 int lastOffset = 0;
0508 StringBuffer result = new StringBuffer();
0509 for (int i = 0; i <= maxOffset; ++i) {
0510 copyAndFixQuotes(pattern, lastOffset, offsets[i], result);
0511 lastOffset = offsets[i];
0512 result.append('{');
0513 result.append(argumentNumbers[i]);
0514 if (formats[i] == null) {
0515 // do nothing, string format
0516 } else if (formats[i] instanceof DecimalFormat) {
0517 if (formats[i].equals(NumberFormat.getInstance(locale))) {
0518 result.append(",number");
0519 } else if (formats[i].equals(NumberFormat
0520 .getCurrencyInstance(locale))) {
0521 result.append(",number,currency");
0522 } else if (formats[i].equals(NumberFormat
0523 .getPercentInstance(locale))) {
0524 result.append(",number,percent");
0525 } else if (formats[i].equals(NumberFormat
0526 .getIntegerInstance(locale))) {
0527 result.append(",number,integer");
0528 } else {
0529 result.append(",number,"
0530 + ((DecimalFormat) formats[i]).toPattern());
0531 }
0532 } else if (formats[i] instanceof SimpleDateFormat) {
0533 if (formats[i].equals(DateFormat.getDateInstance(
0534 DateFormat.DEFAULT, locale))) {
0535 result.append(",date");
0536 } else if (formats[i].equals(DateFormat
0537 .getDateInstance(DateFormat.SHORT, locale))) {
0538 result.append(",date,short");
0539 } else if (formats[i].equals(DateFormat
0540 .getDateInstance(DateFormat.DEFAULT, locale))) {
0541 result.append(",date,medium");
0542 } else if (formats[i].equals(DateFormat
0543 .getDateInstance(DateFormat.LONG, locale))) {
0544 result.append(",date,long");
0545 } else if (formats[i].equals(DateFormat
0546 .getDateInstance(DateFormat.FULL, locale))) {
0547 result.append(",date,full");
0548 } else if (formats[i].equals(DateFormat
0549 .getTimeInstance(DateFormat.DEFAULT, locale))) {
0550 result.append(",time");
0551 } else if (formats[i].equals(DateFormat
0552 .getTimeInstance(DateFormat.SHORT, locale))) {
0553 result.append(",time,short");
0554 } else if (formats[i].equals(DateFormat
0555 .getTimeInstance(DateFormat.DEFAULT, locale))) {
0556 result.append(",time,medium");
0557 } else if (formats[i].equals(DateFormat
0558 .getTimeInstance(DateFormat.LONG, locale))) {
0559 result.append(",time,long");
0560 } else if (formats[i].equals(DateFormat
0561 .getTimeInstance(DateFormat.FULL, locale))) {
0562 result.append(",time,full");
0563 } else {
0564 result.append(",date,"
0565 + ((SimpleDateFormat) formats[i])
0566 .toPattern());
0567 }
0568 } else if (formats[i] instanceof ChoiceFormat) {
0569 result.append(",choice,"
0570 + ((ChoiceFormat) formats[i]).toPattern());
0571 } else {
0572 //result.append(", unknown");
0573 }
0574 result.append('}');
0575 }
0576 copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
0577 return result.toString();
0578 }
0579
0580 /**
0581 * Sets the formats to use for the values passed into
0582 * <code>format</code> methods or returned from <code>parse</code>
0583 * methods. The indices of elements in <code>newFormats</code>
0584 * correspond to the argument indices used in the previously set
0585 * pattern string.
0586 * The order of formats in <code>newFormats</code> thus corresponds to
0587 * the order of elements in the <code>arguments</code> array passed
0588 * to the <code>format</code> methods or the result array returned
0589 * by the <code>parse</code> methods.
0590 * <p>
0591 * If an argument index is used for more than one format element
0592 * in the pattern string, then the corresponding new format is used
0593 * for all such format elements. If an argument index is not used
0594 * for any format element in the pattern string, then the
0595 * corresponding new format is ignored. If fewer formats are provided
0596 * than needed, then only the formats for argument indices less
0597 * than <code>newFormats.length</code> are replaced.
0598 *
0599 * @param newFormats the new formats to use
0600 * @exception NullPointerException if <code>newFormats</code> is null
0601 * @since 1.4
0602 */
0603 public void setFormatsByArgumentIndex(Format[] newFormats) {
0604 for (int i = 0; i <= maxOffset; i++) {
0605 int j = argumentNumbers[i];
0606 if (j < newFormats.length) {
0607 formats[i] = newFormats[j];
0608 }
0609 }
0610 }
0611
0612 /**
0613 * Sets the formats to use for the format elements in the
0614 * previously set pattern string.
0615 * The order of formats in <code>newFormats</code> corresponds to
0616 * the order of format elements in the pattern string.
0617 * <p>
0618 * If more formats are provided than needed by the pattern string,
0619 * the remaining ones are ignored. If fewer formats are provided
0620 * than needed, then only the first <code>newFormats.length</code>
0621 * formats are replaced.
0622 * <p>
0623 * Since the order of format elements in a pattern string often
0624 * changes during localization, it is generally better to use the
0625 * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
0626 * method, which assumes an order of formats corresponding to the
0627 * order of elements in the <code>arguments</code> array passed to
0628 * the <code>format</code> methods or the result array returned by
0629 * the <code>parse</code> methods.
0630 *
0631 * @param newFormats the new formats to use
0632 * @exception NullPointerException if <code>newFormats</code> is null
0633 */
0634 public void setFormats(Format[] newFormats) {
0635 int runsToCopy = newFormats.length;
0636 if (runsToCopy > maxOffset + 1) {
0637 runsToCopy = maxOffset + 1;
0638 }
0639 for (int i = 0; i < runsToCopy; i++) {
0640 formats[i] = newFormats[i];
0641 }
0642 }
0643
0644 /**
0645 * Sets the format to use for the format elements within the
0646 * previously set pattern string that use the given argument
0647 * index.
0648 * The argument index is part of the format element definition and
0649 * represents an index into the <code>arguments</code> array passed
0650 * to the <code>format</code> methods or the result array returned
0651 * by the <code>parse</code> methods.
0652 * <p>
0653 * If the argument index is used for more than one format element
0654 * in the pattern string, then the new format is used for all such
0655 * format elements. If the argument index is not used for any format
0656 * element in the pattern string, then the new format is ignored.
0657 *
0658 * @param argumentIndex the argument index for which to use the new format
0659 * @param newFormat the new format to use
0660 * @since 1.4
0661 */
0662 public void setFormatByArgumentIndex(int argumentIndex,
0663 Format newFormat) {
0664 for (int j = 0; j <= maxOffset; j++) {
0665 if (argumentNumbers[j] == argumentIndex) {
0666 formats[j] = newFormat;
0667 }
0668 }
0669 }
0670
0671 /**
0672 * Sets the format to use for the format element with the given
0673 * format element index within the previously set pattern string.
0674 * The format element index is the zero-based number of the format
0675 * element counting from the start of the pattern string.
0676 * <p>
0677 * Since the order of format elements in a pattern string often
0678 * changes during localization, it is generally better to use the
0679 * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
0680 * method, which accesses format elements based on the argument
0681 * index they specify.
0682 *
0683 * @param formatElementIndex the index of a format element within the pattern
0684 * @param newFormat the format to use for the specified format element
0685 * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
0686 * larger than the number of format elements in the pattern string
0687 */
0688 public void setFormat(int formatElementIndex, Format newFormat) {
0689 formats[formatElementIndex] = newFormat;
0690 }
0691
0692 /**
0693 * Gets the formats used for the values passed into
0694 * <code>format</code> methods or returned from <code>parse</code>
0695 * methods. The indices of elements in the returned array
0696 * correspond to the argument indices used in the previously set
0697 * pattern string.
0698 * The order of formats in the returned array thus corresponds to
0699 * the order of elements in the <code>arguments</code> array passed
0700 * to the <code>format</code> methods or the result array returned
0701 * by the <code>parse</code> methods.
0702 * <p>
0703 * If an argument index is used for more than one format element
0704 * in the pattern string, then the format used for the last such
0705 * format element is returned in the array. If an argument index
0706 * is not used for any format element in the pattern string, then
0707 * null is returned in the array.
0708 *
0709 * @return the formats used for the arguments within the pattern
0710 * @since 1.4
0711 */
0712 public Format[] getFormatsByArgumentIndex() {
0713 int maximumArgumentNumber = -1;
0714 for (int i = 0; i <= maxOffset; i++) {
0715 if (argumentNumbers[i] > maximumArgumentNumber) {
0716 maximumArgumentNumber = argumentNumbers[i];
0717 }
0718 }
0719 Format[] resultArray = new Format[maximumArgumentNumber + 1];
0720 for (int i = 0; i <= maxOffset; i++) {
0721 resultArray[argumentNumbers[i]] = formats[i];
0722 }
0723 return resultArray;
0724 }
0725
0726 /**
0727 * Gets the formats used for the format elements in the
0728 * previously set pattern string.
0729 * The order of formats in the returned array corresponds to
0730 * the order of format elements in the pattern string.
0731 * <p>
0732 * Since the order of format elements in a pattern string often
0733 * changes during localization, it's generally better to use the
0734 * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}
0735 * method, which assumes an order of formats corresponding to the
0736 * order of elements in the <code>arguments</code> array passed to
0737 * the <code>format</code> methods or the result array returned by
0738 * the <code>parse</code> methods.
0739 *
0740 * @return the formats used for the format elements in the pattern
0741 */
0742 public Format[] getFormats() {
0743 Format[] resultArray = new Format[maxOffset + 1];
0744 System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);
0745 return resultArray;
0746 }
0747
0748 /**
0749 * Formats an array of objects and appends the <code>MessageFormat</code>'s
0750 * pattern, with format elements replaced by the formatted objects, to the
0751 * provided <code>StringBuffer</code>.
0752 * <p>
0753 * The text substituted for the individual format elements is derived from
0754 * the current subformat of the format element and the
0755 * <code>arguments</code> element at the format element's argument index
0756 * as indicated by the first matching line of the following table. An
0757 * argument is <i>unavailable</i> if <code>arguments</code> is
0758 * <code>null</code> or has fewer than argumentIndex+1 elements.
0759 * <p>
0760 * <table border=1 summary="Examples of subformat,argument,and formatted text">
0761 * <tr>
0762 * <th>Subformat
0763 * <th>Argument
0764 * <th>Formatted Text
0765 * <tr>
0766 * <td><i>any</i>
0767 * <td><i>unavailable</i>
0768 * <td><code>"{" + argumentIndex + "}"</code>
0769 * <tr>
0770 * <td><i>any</i>
0771 * <td><code>null</code>
0772 * <td><code>"null"</code>
0773 * <tr>
0774 * <td><code>instanceof ChoiceFormat</code>
0775 * <td><i>any</i>
0776 * <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br>
0777 * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :
0778 * subformat.format(argument)</code>
0779 * <tr>
0780 * <td><code>!= null</code>
0781 * <td><i>any</i>
0782 * <td><code>subformat.format(argument)</code>
0783 * <tr>
0784 * <td><code>null</code>
0785 * <td><code>instanceof Number</code>
0786 * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
0787 * <tr>
0788 * <td><code>null</code>
0789 * <td><code>instanceof Date</code>
0790 * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>
0791 * <tr>
0792 * <td><code>null</code>
0793 * <td><code>instanceof String</code>
0794 * <td><code>argument</code>
0795 * <tr>
0796 * <td><code>null</code>
0797 * <td><i>any</i>
0798 * <td><code>argument.toString()</code>
0799 * </table>
0800 * <p>
0801 * If <code>pos</code> is non-null, and refers to
0802 * <code>Field.ARGUMENT</code>, the location of the first formatted
0803 * string will be returned.
0804 *
0805 * @param arguments an array of objects to be formatted and substituted.
0806 * @param result where text is appended.
0807 * @param pos On input: an alignment field, if desired.
0808 * On output: the offsets of the alignment field.
0809 * @exception IllegalArgumentException if an argument in the
0810 * <code>arguments</code> array is not of the type
0811 * expected by the format element(s) that use it.
0812 */
0813 public final StringBuffer format(Object[] arguments,
0814 StringBuffer result, FieldPosition pos) {
0815 return subformat(arguments, result, pos, null);
0816 }
0817
0818 /**
0819 * Creates a MessageFormat with the given pattern and uses it
0820 * to format the given arguments. This is equivalent to
0821 * <blockquote>
0822 * <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>
0823 * </blockquote>
0824 *
0825 * @exception IllegalArgumentException if the pattern is invalid,
0826 * or if an argument in the <code>arguments</code> array
0827 * is not of the type expected by the format element(s)
0828 * that use it.
0829 */
0830 public static String format(String pattern, Object... arguments) {
0831 MessageFormat temp = new MessageFormat(pattern);
0832 return temp.format(arguments);
0833 }
0834
0835 // Overrides
0836 /**
0837 * Formats an array of objects and appends the <code>MessageFormat</code>'s
0838 * pattern, with format elements replaced by the formatted objects, to the
0839 * provided <code>StringBuffer</code>.
0840 * This is equivalent to
0841 * <blockquote>
0842 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
0843 * </blockquote>
0844 *
0845 * @param arguments an array of objects to be formatted and substituted.
0846 * @param result where text is appended.
0847 * @param pos On input: an alignment field, if desired.
0848 * On output: the offsets of the alignment field.
0849 * @exception IllegalArgumentException if an argument in the
0850 * <code>arguments</code> array is not of the type
0851 * expected by the format element(s) that use it.
0852 */
0853 public final StringBuffer format(Object arguments,
0854 StringBuffer result, FieldPosition pos) {
0855 return subformat((Object[]) arguments, result, pos, null);
0856 }
0857
0858 /**
0859 * Formats an array of objects and inserts them into the
0860 * <code>MessageFormat</code>'s pattern, producing an
0861 * <code>AttributedCharacterIterator</code>.
0862 * You can use the returned <code>AttributedCharacterIterator</code>
0863 * to build the resulting String, as well as to determine information
0864 * about the resulting String.
0865 * <p>
0866 * The text of the returned <code>AttributedCharacterIterator</code> is
0867 * the same that would be returned by
0868 * <blockquote>
0869 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
0870 * </blockquote>
0871 * <p>
0872 * In addition, the <code>AttributedCharacterIterator</code> contains at
0873 * least attributes indicating where text was generated from an
0874 * argument in the <code>arguments</code> array. The keys of these attributes are of
0875 * type <code>MessageFormat.Field</code>, their values are
0876 * <code>Integer</code> objects indicating the index in the <code>arguments</code>
0877 * array of the argument from which the text was generated.
0878 * <p>
0879 * The attributes/value from the underlying <code>Format</code>
0880 * instances that <code>MessageFormat</code> uses will also be
0881 * placed in the resulting <code>AttributedCharacterIterator</code>.
0882 * This allows you to not only find where an argument is placed in the
0883 * resulting String, but also which fields it contains in turn.
0884 *
0885 * @param arguments an array of objects to be formatted and substituted.
0886 * @return AttributedCharacterIterator describing the formatted value.
0887 * @exception NullPointerException if <code>arguments</code> is null.
0888 * @exception IllegalArgumentException if an argument in the
0889 * <code>arguments</code> array is not of the type
0890 * expected by the format element(s) that use it.
0891 * @since 1.4
0892 */
0893 public AttributedCharacterIterator formatToCharacterIterator(
0894 Object arguments) {
0895 StringBuffer result = new StringBuffer();
0896 ArrayList iterators = new ArrayList();
0897
0898 if (arguments == null) {
0899 throw new NullPointerException(
0900 "formatToCharacterIterator must be passed non-null object");
0901 }
0902 subformat((Object[]) arguments, result, null, iterators);
0903 if (iterators.size() == 0) {
0904 return createAttributedCharacterIterator("");
0905 }
0906 return createAttributedCharacterIterator((AttributedCharacterIterator[]) iterators
0907 .toArray(new AttributedCharacterIterator[iterators
0908 .size()]));
0909 }
0910
0911 /**
0912 * Parses the string.
0913 *
0914 * <p>Caveats: The parse may fail in a number of circumstances.
0915 * For example:
0916 * <ul>
0917 * <li>If one of the arguments does not occur in the pattern.
0918 * <li>If the format of an argument loses information, such as
0919 * with a choice format where a large number formats to "many".
0920 * <li>Does not yet handle recursion (where
0921 * the substituted strings contain {n} references.)
0922 * <li>Will not always find a match (or the correct match)
0923 * if some part of the parse is ambiguous.
0924 * For example, if the pattern "{1},{2}" is used with the
0925 * string arguments {"a,b", "c"}, it will format as "a,b,c".
0926 * When the result is parsed, it will return {"a", "b,c"}.
0927 * <li>If a single argument is parsed more than once in the string,
0928 * then the later parse wins.
0929 * </ul>
0930 * When the parse fails, use ParsePosition.getErrorIndex() to find out
0931 * where in the string the parsing failed. The returned error
0932 * index is the starting offset of the sub-patterns that the string
0933 * is comparing with. For example, if the parsing string "AAA {0} BBB"
0934 * is comparing against the pattern "AAD {0} BBB", the error index is
0935 * 0. When an error occurs, the call to this method will return null.
0936 * If the source is null, return an empty array.
0937 */
0938 public Object[] parse(String source, ParsePosition pos) {
0939 if (source == null) {
0940 Object[] empty = {};
0941 return empty;
0942 }
0943
0944 int maximumArgumentNumber = -1;
0945 for (int i = 0; i <= maxOffset; i++) {
0946 if (argumentNumbers[i] > maximumArgumentNumber) {
0947 maximumArgumentNumber = argumentNumbers[i];
0948 }
0949 }
0950 Object[] resultArray = new Object[maximumArgumentNumber + 1];
0951
0952 int patternOffset = 0;
0953 int sourceOffset = pos.index;
0954 ParsePosition tempStatus = new ParsePosition(0);
0955 for (int i = 0; i <= maxOffset; ++i) {
0956 // match up to format
0957 int len = offsets[i] - patternOffset;
0958 if (len == 0
0959 || pattern.regionMatches(patternOffset, source,
0960 sourceOffset, len)) {
0961 sourceOffset += len;
0962 patternOffset += len;
0963 } else {
0964 pos.errorIndex = sourceOffset;
0965 return null; // leave index as is to signal error
0966 }
0967
0968 // now use format
0969 if (formats[i] == null) { // string format
0970 // if at end, use longest possible match
0971 // otherwise uses first match to intervening string
0972 // does NOT recursively try all possibilities
0973 int tempLength = (i != maxOffset) ? offsets[i + 1]
0974 : pattern.length();
0975
0976 int next;
0977 if (patternOffset >= tempLength) {
0978 next = source.length();
0979 } else {
0980 next = source.indexOf(pattern.substring(
0981 patternOffset, tempLength), sourceOffset);
0982 }
0983
0984 if (next < 0) {
0985 pos.errorIndex = sourceOffset;
0986 return null; // leave index as is to signal error
0987 } else {
0988 String strValue = source.substring(sourceOffset,
0989 next);
0990 if (!strValue
0991 .equals("{" + argumentNumbers[i] + "}"))
0992 resultArray[argumentNumbers[i]] = source
0993 .substring(sourceOffset, next);
0994 sourceOffset = next;
0995 }
0996 } else {
0997 tempStatus.index = sourceOffset;
0998 resultArray[argumentNumbers[i]] = formats[i]
0999 .parseObject(source, tempStatus);
1000 if (tempStatus.index == sourceOffset) {
1001 pos.errorIndex = sourceOffset;
1002 return null; // leave index as is to signal error
1003 }
1004 sourceOffset = tempStatus.index; // update
1005 }
1006 }
1007 int len = pattern.length() - patternOffset;
1008 if (len == 0
1009 || pattern.regionMatches(patternOffset, source,
1010 sourceOffset, len)) {
1011 pos.index = sourceOffset + len;
1012 } else {
1013 pos.errorIndex = sourceOffset;
1014 return null; // leave index as is to signal error
1015 }
1016 return resultArray;
1017 }
1018
1019 /**
1020 * Parses text from the beginning of the given string to produce an object
1021 * array.
1022 * The method may not use the entire text of the given string.
1023 * <p>
1024 * See the {@link #parse(String, ParsePosition)} method for more information
1025 * on message parsing.
1026 *
1027 * @param source A <code>String</code> whose beginning should be parsed.
1028 * @return An <code>Object</code> array parsed from the string.
1029 * @exception ParseException if the beginning of the specified string
1030 * cannot be parsed.
1031 */
1032 public Object[] parse(String source) throws ParseException {
1033 ParsePosition pos = new ParsePosition(0);
1034 Object[] result = parse(source, pos);
1035 if (pos.index == 0) // unchanged, returned object is null
1036 throw new ParseException("MessageFormat parse error!",
1037 pos.errorIndex);
1038
1039 return result;
1040 }
1041
1042 /**
1043 * Parses text from a string to produce an object array.
1044 * <p>
1045 * The method attempts to parse text starting at the index given by
1046 * <code>pos</code>.
1047 * If parsing succeeds, then the index of <code>pos</code> is updated
1048 * to the index after the last character used (parsing does not necessarily
1049 * use all characters up to the end of the string), and the parsed
1050 * object array is returned. The updated <code>pos</code> can be used to
1051 * indicate the starting point for the next call to this method.
1052 * If an error occurs, then the index of <code>pos</code> is not
1053 * changed, the error index of <code>pos</code> is set to the index of
1054 * the character where the error occurred, and null is returned.
1055 * <p>
1056 * See the {@link #parse(String, ParsePosition)} method for more information
1057 * on message parsing.
1058 *
1059 * @param source A <code>String</code>, part of which should be parsed.
1060 * @param pos A <code>ParsePosition</code> object with index and error
1061 * index information as described above.
1062 * @return An <code>Object</code> array parsed from the string. In case of
1063 * error, returns null.
1064 * @exception NullPointerException if <code>pos</code> is null.
1065 */
1066 public Object parseObject(String source, ParsePosition pos) {
1067 return parse(source, pos);
1068 }
1069
1070 /**
1071 * Creates and returns a copy of this object.
1072 *
1073 * @return a clone of this instance.
1074 */
1075 public Object clone() {
1076 MessageFormat other = (MessageFormat) super .clone();
1077
1078 // clone arrays. Can't do with utility because of bug in Cloneable
1079 other.formats = (Format[]) formats.clone(); // shallow clone
1080 for (int i = 0; i < formats.length; ++i) {
1081 if (formats[i] != null)
1082 other.formats[i] = (Format) formats[i].clone();
1083 }
1084 // for primitives or immutables, shallow clone is enough
1085 other.offsets = (int[]) offsets.clone();
1086 other.argumentNumbers = (int[]) argumentNumbers.clone();
1087
1088 return other;
1089 }
1090
1091 /**
1092 * Equality comparison between two message format objects
1093 */
1094 public boolean equals(Object obj) {
1095 if (this == obj) // quick check
1096 return true;
1097 if (obj == null || getClass() != obj.getClass())
1098 return false;
1099 MessageFormat other = (MessageFormat) obj;
1100 return (maxOffset == other.maxOffset
1101 && pattern.equals(other.pattern)
1102 && ((locale != null && locale.equals(other.locale)) || (locale == null && other.locale == null))
1103 && Arrays.equals(offsets, other.offsets)
1104 && Arrays
1105 .equals(argumentNumbers, other.argumentNumbers) && Arrays
1106 .equals(formats, other.formats));
1107 }
1108
1109 /**
1110 * Generates a hash code for the message format object.
1111 */
1112 public int hashCode() {
1113 return pattern.hashCode(); // enough for reasonable distribution
1114 }
1115
1116 /**
1117 * Defines constants that are used as attribute keys in the
1118 * <code>AttributedCharacterIterator</code> returned
1119 * from <code>MessageFormat.formatToCharacterIterator</code>.
1120 *
1121 * @since 1.4
1122 */
1123 public static class Field extends Format.Field {
1124
1125 // Proclaim serial compatibility with 1.4 FCS
1126 private static final long serialVersionUID = 7899943957617360810L;
1127
1128 /**
1129 * Creates a Field with the specified name.
1130 *
1131 * @param name Name of the attribute
1132 */
1133 protected Field(String name) {
1134 super (name);
1135 }
1136
1137 /**
1138 * Resolves instances being deserialized to the predefined constants.
1139 *
1140 * @throws InvalidObjectException if the constant could not be
1141 * resolved.
1142 * @return resolved MessageFormat.Field constant
1143 */
1144 protected Object readResolve() throws InvalidObjectException {
1145 if (this .getClass() != MessageFormat.Field.class) {
1146 throw new InvalidObjectException(
1147 "subclass didn't correctly implement readResolve");
1148 }
1149
1150 return ARGUMENT;
1151 }
1152
1153 //
1154 // The constants
1155 //
1156
1157 /**
1158 * Constant identifying a portion of a message that was generated
1159 * from an argument passed into <code>formatToCharacterIterator</code>.
1160 * The value associated with the key will be an <code>Integer</code>
1161 * indicating the index in the <code>arguments</code> array of the
1162 * argument from which the text was generated.
1163 */
1164 public final static Field ARGUMENT = new Field(
1165 "message argument field");
1166 }
1167
1168 // ===========================privates============================
1169
1170 /**
1171 * The locale to use for formatting numbers and dates.
1172 * @serial
1173 */
1174 private Locale locale;
1175
1176 /**
1177 * The string that the formatted values are to be plugged into. In other words, this
1178 * is the pattern supplied on construction with all of the {} expressions taken out.
1179 * @serial
1180 */
1181 private String pattern = "";
1182
1183 /** The initially expected number of subformats in the format */
1184 private static final int INITIAL_FORMATS = 10;
1185
1186 /**
1187 * An array of formatters, which are used to format the arguments.
1188 * @serial
1189 */
1190 private Format[] formats = new Format[INITIAL_FORMATS];
1191
1192 /**
1193 * The positions where the results of formatting each argument are to be inserted
1194 * into the pattern.
1195 * @serial
1196 */
1197 private int[] offsets = new int[INITIAL_FORMATS];
1198
1199 /**
1200 * The argument numbers corresponding to each formatter. (The formatters are stored
1201 * in the order they occur in the pattern, not in the order in which the arguments
1202 * are specified.)
1203 * @serial
1204 */
1205 private int[] argumentNumbers = new int[INITIAL_FORMATS];
1206
1207 /**
1208 * One less than the number of entries in <code>offsets</code>. Can also be thought of
1209 * as the index of the highest-numbered element in <code>offsets</code> that is being used.
1210 * All of these arrays should have the same number of elements being used as <code>offsets</code>
1211 * does, and so this variable suffices to tell us how many entries are in all of them.
1212 * @serial
1213 */
1214 private int maxOffset = -1;
1215
1216 /**
1217 * Internal routine used by format. If <code>characterIterators</code> is
1218 * non-null, AttributedCharacterIterator will be created from the
1219 * subformats as necessary. If <code>characterIterators</code> is null
1220 * and <code>fp</code> is non-null and identifies
1221 * <code>Field.MESSAGE_ARGUMENT</code>, the location of
1222 * the first replaced argument will be set in it.
1223 *
1224 * @exception IllegalArgumentException if an argument in the
1225 * <code>arguments</code> array is not of the type
1226 * expected by the format element(s) that use it.
1227 */
1228 private StringBuffer subformat(Object[] arguments,
1229 StringBuffer result, FieldPosition fp,
1230 List characterIterators) {
1231 // note: this implementation assumes a fast substring & index.
1232 // if this is not true, would be better to append chars one by one.
1233 int lastOffset = 0;
1234 int last = result.length();
1235 for (int i = 0; i <= maxOffset; ++i) {
1236 result.append(pattern.substring(lastOffset, offsets[i]));
1237 lastOffset = offsets[i];
1238 int argumentNumber = argumentNumbers[i];
1239 if (arguments == null || argumentNumber >= arguments.length) {
1240 result.append("{" + argumentNumber + "}");
1241 continue;
1242 }
1243 // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
1244 if (false) { // if (argRecursion == 3){
1245 // prevent loop!!!
1246 result.append('\uFFFD');
1247 } else {
1248 Object obj = arguments[argumentNumber];
1249 String arg = null;
1250 Format subFormatter = null;
1251 if (obj == null) {
1252 arg = "null";
1253 } else if (formats[i] != null) {
1254 subFormatter = formats[i];
1255 if (subFormatter instanceof ChoiceFormat) {
1256 arg = formats[i].format(obj);
1257 if (arg.indexOf('{') >= 0) {
1258 subFormatter = new MessageFormat(arg,
1259 locale);
1260 obj = arguments;
1261 arg = null;
1262 }
1263 }
1264 } else if (obj instanceof Number) {
1265 // format number if can
1266 subFormatter = NumberFormat.getInstance(locale);
1267 } else if (obj instanceof Date) {
1268 // format a Date if can
1269 subFormatter = DateFormat.getDateTimeInstance(
1270 DateFormat.SHORT, DateFormat.SHORT, locale);//fix
1271 } else if (obj instanceof String) {
1272 arg = (String) obj;
1273
1274 } else {
1275 arg = obj.toString();
1276 if (arg == null)
1277 arg = "null";
1278 }
1279
1280 // At this point we are in two states, either subFormatter
1281 // is non-null indicating we should format obj using it,
1282 // or arg is non-null and we should use it as the value.
1283
1284 if (characterIterators != null) {
1285 // If characterIterators is non-null, it indicates we need
1286 // to get the CharacterIterator from the child formatter.
1287 if (last != result.length()) {
1288 characterIterators
1289 .add(createAttributedCharacterIterator(result
1290 .substring(last)));
1291 last = result.length();
1292 }
1293 if (subFormatter != null) {
1294 AttributedCharacterIterator subIterator = subFormatter
1295 .formatToCharacterIterator(obj);
1296
1297 append(result, subIterator);
1298 if (last != result.length()) {
1299 characterIterators
1300 .add(createAttributedCharacterIterator(
1301 subIterator,
1302 Field.ARGUMENT,
1303 new Integer(argumentNumber)));
1304 last = result.length();
1305 }
1306 arg = null;
1307 }
1308 if (arg != null && arg.length() > 0) {
1309 result.append(arg);
1310 characterIterators
1311 .add(createAttributedCharacterIterator(
1312 arg, Field.ARGUMENT,
1313 new Integer(argumentNumber)));
1314 last = result.length();
1315 }
1316 } else {
1317 if (subFormatter != null) {
1318 arg = subFormatter.format(obj);
1319 }
1320 last = result.length();
1321 result.append(arg);
1322 if (i == 0
1323 && fp != null
1324 && Field.ARGUMENT.equals(fp
1325 .getFieldAttribute())) {
1326 fp.setBeginIndex(last);
1327 fp.setEndIndex(result.length());
1328 }
1329 last = result.length();
1330 }
1331 }
1332 }
1333 result.append(pattern.substring(lastOffset, pattern.length()));
1334 if (characterIterators != null && last != result.length()) {
1335 characterIterators
1336 .add(createAttributedCharacterIterator(result
1337 .substring(last)));
1338 }
1339 return result;
1340 }
1341
1342 /**
1343 * Convenience method to append all the characters in
1344 * <code>iterator</code> to the StringBuffer <code>result</code>.
1345 */
1346 private void append(StringBuffer result, CharacterIterator iterator) {
1347 if (iterator.first() != CharacterIterator.DONE) {
1348 char aChar;
1349
1350 result.append(iterator.first());
1351 while ((aChar = iterator.next()) != CharacterIterator.DONE) {
1352 result.append(aChar);
1353 }
1354 }
1355 }
1356
1357 private static final String[] typeList = { "", "", "number", "",
1358 "date", "", "time", "", "choice" };
1359 private static final String[] modifierList = { "", "", "currency",
1360 "", "percent", "", "integer" };
1361 private static final String[] dateModifierList = { "", "", "short",
1362 "", "medium", "", "long", "", "full" };
1363
1364 private void makeFormat(int position, int offsetNumber,
1365 StringBuffer[] segments) {
1366 // get the argument number
1367 int argumentNumber;
1368 try {
1369 argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
1370 } catch (NumberFormatException e) {
1371 throw new IllegalArgumentException(
1372 "can't parse argument number: " + segments[1]);
1373 }
1374 if (argumentNumber < 0) {
1375 throw new IllegalArgumentException(
1376 "negative argument number: " + argumentNumber);
1377 }
1378
1379 // resize format information arrays if necessary
1380 if (offsetNumber >= formats.length) {
1381 int newLength = formats.length * 2;
1382 Format[] newFormats = new Format[newLength];
1383 int[] newOffsets = new int[newLength];
1384 int[] newArgumentNumbers = new int[newLength];
1385 System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);
1386 System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);
1387 System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0,
1388 maxOffset + 1);
1389 formats = newFormats;
1390 offsets = newOffsets;
1391 argumentNumbers = newArgumentNumbers;
1392 }
1393 int oldMaxOffset = maxOffset;
1394 maxOffset = offsetNumber;
1395 offsets[offsetNumber] = segments[0].length();
1396 argumentNumbers[offsetNumber] = argumentNumber;
1397
1398 // now get the format
1399 Format newFormat = null;
1400 switch (findKeyword(segments[2].toString(), typeList)) {
1401 case 0:
1402 break;
1403 case 1:
1404 case 2:// number
1405 switch (findKeyword(segments[3].toString(), modifierList)) {
1406 case 0: // default;
1407 newFormat = NumberFormat.getInstance(locale);
1408 break;
1409 case 1:
1410 case 2:// currency
1411 newFormat = NumberFormat.getCurrencyInstance(locale);
1412 break;
1413 case 3:
1414 case 4:// percent
1415 newFormat = NumberFormat.getPercentInstance(locale);
1416 break;
1417 case 5:
1418 case 6:// integer
1419 newFormat = NumberFormat.getIntegerInstance(locale);
1420 break;
1421 default: // pattern
1422 newFormat = new DecimalFormat(segments[3].toString(),
1423 DecimalFormatSymbols.getInstance(locale));
1424 break;
1425 }
1426 break;
1427 case 3:
1428 case 4: // date
1429 switch (findKeyword(segments[3].toString(),
1430 dateModifierList)) {
1431 case 0: // default
1432 newFormat = DateFormat.getDateInstance(
1433 DateFormat.DEFAULT, locale);
1434 break;
1435 case 1:
1436 case 2: // short
1437 newFormat = DateFormat.getDateInstance(
1438 DateFormat.SHORT, locale);
1439 break;
1440 case 3:
1441 case 4: // medium
1442 newFormat = DateFormat.getDateInstance(
1443 DateFormat.DEFAULT, locale);
1444 break;
1445 case 5:
1446 case 6: // long
1447 newFormat = DateFormat.getDateInstance(DateFormat.LONG,
1448 locale);
1449 break;
1450 case 7:
1451 case 8: // full
1452 newFormat = DateFormat.getDateInstance(DateFormat.FULL,
1453 locale);
1454 break;
1455 default:
1456 newFormat = new SimpleDateFormat(
1457 segments[3].toString(), locale);
1458 break;
1459 }
1460 break;
1461 case 5:
1462 case 6:// time
1463 switch (findKeyword(segments[3].toString(),
1464 dateModifierList)) {
1465 case 0: // default
1466 newFormat = DateFormat.getTimeInstance(
1467 DateFormat.DEFAULT, locale);
1468 break;
1469 case 1:
1470 case 2: // short
1471 newFormat = DateFormat.getTimeInstance(
1472 DateFormat.SHORT, locale);
1473 break;
1474 case 3:
1475 case 4: // medium
1476 newFormat = DateFormat.getTimeInstance(
1477 DateFormat.DEFAULT, locale);
1478 break;
1479 case 5:
1480 case 6: // long
1481 newFormat = DateFormat.getTimeInstance(DateFormat.LONG,
1482 locale);
1483 break;
1484 case 7:
1485 case 8: // full
1486 newFormat = DateFormat.getTimeInstance(DateFormat.FULL,
1487 locale);
1488 break;
1489 default:
1490 newFormat = new SimpleDateFormat(
1491 segments[3].toString(), locale);
1492 break;
1493 }
1494 break;
1495 case 7:
1496 case 8:// choice
1497 try {
1498 newFormat = new ChoiceFormat(segments[3].toString());
1499 } catch (Exception e) {
1500 maxOffset = oldMaxOffset;
1501 throw new IllegalArgumentException(
1502 "Choice Pattern incorrect");
1503 }
1504 break;
1505 default:
1506 maxOffset = oldMaxOffset;
1507 throw new IllegalArgumentException("unknown format type: "
1508 + segments[2].toString());
1509 }
1510 formats[offsetNumber] = newFormat;
1511 segments[1].setLength(0); // throw away other segments
1512 segments[2].setLength(0);
1513 segments[3].setLength(0);
1514 }
1515
1516 private static final int findKeyword(String s, String[] list) {
1517 s = s.trim().toLowerCase();
1518 for (int i = 0; i < list.length; ++i) {
1519 if (s.equals(list[i]))
1520 return i;
1521 }
1522 return -1;
1523 }
1524
1525 private static final void copyAndFixQuotes(String source,
1526 int start, int end, StringBuffer target) {
1527 for (int i = start; i < end; ++i) {
1528 char ch = source.charAt(i);
1529 if (ch == '{') {
1530 target.append("'{'");
1531 } else if (ch == '}') {
1532 target.append("'}'");
1533 } else if (ch == '\'') {
1534 target.append("''");
1535 } else {
1536 target.append(ch);
1537 }
1538 }
1539 }
1540
1541 /**
1542 * After reading an object from the input stream, do a simple verification
1543 * to maintain class invariants.
1544 * @throws InvalidObjectException if the objects read from the stream is invalid.
1545 */
1546 private void readObject(ObjectInputStream in) throws IOException,
1547 ClassNotFoundException {
1548 in.defaultReadObject();
1549 boolean isValid = maxOffset >= -1 && formats.length > maxOffset
1550 && offsets.length > maxOffset
1551 && argumentNumbers.length > maxOffset;
1552 if (isValid) {
1553 int lastOffset = pattern.length() + 1;
1554 for (int i = maxOffset; i >= 0; --i) {
1555 if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
1556 isValid = false;
1557 break;
1558 } else {
1559 lastOffset = offsets[i];
1560 }
1561 }
1562 }
1563 if (!isValid) {
1564 throw new InvalidObjectException(
1565 "Could not reconstruct MessageFormat from corrupt stream.");
1566 }
1567 }
1568 }
|