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