FileDocCategorySizeDatePackage
MessageFormat.javaAPI DocAndroid 1.5 API53352Wed May 06 22:41:06 BST 2009java.text

MessageFormat

public class MessageFormat extends Format
Produces concatenated messages in language-neutral way. Use this class to construct messages displayed for end users.

{@code MessageFormat} takes a set of objects, formats them and then inserts the formatted strings into the pattern at the appropriate places.

Note: {@code MessageFormat} differs from the other {@code Format} classes in that you create a {@code MessageFormat} object with one of its constructors (not with a {@code getInstance} style factory method). The factory methods aren't necessary because {@code MessageFormat} itself doesn't implement locale specific behavior. Any locale specific behavior is defined by the pattern that you provide as well as the subformats used for inserted arguments.

Patterns and their interpretation

{@code MessageFormat} uses patterns of the following form:
MessageFormatPattern:
String
MessageFormatPattern FormatElement String
FormatElement:
{ ArgumentIndex }
{ ArgumentIndex , FormatType }
{ ArgumentIndex , FormatType , FormatStyle }
FormatType: one of 
number date time choice
FormatStyle:
short
medium
long
full
integer
currency
percent
SubformatPattern
String:
StringPart<sub>opt</sub>
String StringPart
StringPart:
''
' QuotedString '
UnquotedString
SubformatPattern:
SubformatPatternPart<sub>opt</sub>
SubformatPattern SubformatPatternPart
SubFormatPatternPart:
' QuotedPattern '
UnquotedPattern

Within a String, {@code "''"} represents a single quote. A QuotedString can contain arbitrary characters except single quotes; the surrounding single quotes are removed. An UnquotedString can contain arbitrary characters except single quotes and left curly brackets. Thus, a string that should result in the formatted message "'{0}'" can be written as {@code "'''{'0}''"} or {@code "'''{0}'''"}.

Within a SubformatPattern, different rules apply. A QuotedPattern can contain arbitrary characters except single quotes, but the surrounding single quotes are not removed, so they may be interpreted by the subformat. For example, {@code "{1,number,$'#',##}"} will produce a number format with the hash-sign quoted, with a result such as: "$#31,45". An UnquotedPattern can contain arbitrary characters except single quotes, but curly braces within it must be balanced. For example, {@code "ab {0} de"} and {@code "ab '}' de"} are valid subformat patterns, but {@code "ab {0'}' de"} and {@code "ab } de"} are not.

Warning:
The rules for using quotes within message format patterns unfortunately have shown to be somewhat confusing. In particular, it isn't always obvious to localizers whether single quotes need to be doubled or not. Make sure to inform localizers about the rules, and tell them (for example, by using comments in resource bundle source files) which strings will be processed by {@code MessageFormat}. Note that localizers may need to use single quotes in translated strings where the original version doesn't have them.
Note also that the simplest way to avoid the problem is to use the real apostrophe (single quote) character \u2019 (') for human-readable text, and to use the ASCII apostrophe (\u0027 ' ) only in program syntax, like quoting in {@code MessageFormat}. See the annotations for U+0027 Apostrophe in The Unicode Standard.

The ArgumentIndex value is a non-negative integer written using the digits '0' through '9', and represents an index into the {@code arguments} array passed to the {@code format} methods or the result array returned by the {@code parse} methods.

The FormatType and FormatStyle values are used to create a {@code Format} instance for the format element. The following table shows how the values map to {@code Format} instances. Combinations not shown in the table are illegal. A SubformatPattern must be a valid pattern string for the {@code Format} subclass used.

Format Type Format Style Subformat Created
(none) {@code null}
{@code number} (none) {@code NumberFormat.getInstance(getLocale())}
{@code integer} {@code NumberFormat.getIntegerInstance(getLocale())}
{@code currency} {@code NumberFormat.getCurrencyInstance(getLocale())}
{@code percent} {@code NumberFormat.getPercentInstance(getLocale())}
SubformatPattern {@code new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))}
{@code date} (none) {@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}
{@code short} {@code DateFormat.getDateInstance(DateFormat.SHORT, getLocale())}
{@code medium} {@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}
{@code long} {@code DateFormat.getDateInstance(DateFormat.LONG, getLocale())}
{@code full} {@code DateFormat.getDateInstance(DateFormat.FULL, getLocale())}
SubformatPattern {@code new SimpleDateFormat(subformatPattern, getLocale())}
{@code time} (none) {@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}
{@code short} {@code DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())}
{@code medium} {@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}
{@code long} {@code DateFormat.getTimeInstance(DateFormat.LONG, getLocale())}
{@code full} {@code DateFormat.getTimeInstance(DateFormat.FULL, getLocale())}
SubformatPattern {@code new SimpleDateFormat(subformatPattern, getLocale())}
{@code choice} SubformatPattern {@code new ChoiceFormat(subformatPattern)}

Usage Information

Here are some examples of usage:

Object[] arguments = {
new Integer(7), new Date(System.currentTimeMillis()),
"a disturbance in the Force"};
String result = MessageFormat.format(
"At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
arguments);

Output:

At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.

Typically, the message format will come from resources, and the arguments will be dynamically set at runtime.

Example 2:

Object[] testArgs = {new Long(3), "MyDisk"};
MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0} file(s).");
System.out.println(form.format(testArgs));

Output with different testArgs:

The disk "MyDisk" contains 0 file(s).
The disk "MyDisk" contains 1 file(s).
The disk "MyDisk" contains 1,273 file(s).

For more sophisticated patterns, you can use a {@code ChoiceFormat} to get output such as:

MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
double[] filelimits = {0,1,2};
String[] filepart = {"no files","one file","{0,number} files"};
ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
form.setFormatByArgumentIndex(0, fileform);
Object[] testArgs = {new Long(12373), "MyDisk"};
System.out.println(form.format(testArgs));

Output (with different testArgs):

The disk "MyDisk" contains no files.
The disk "MyDisk" contains one file.
The disk "MyDisk" contains 1,273 files.
You can either do this programmatically, as in the above example, or by using a pattern (see {@link ChoiceFormat} for more information) as in:
form.applyPattern("There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");

Note: As we see above, the string produced by a {@code ChoiceFormat} in {@code MessageFormat} is treated specially; occurances of '{' are used to indicated subformats, and cause recursion. If you create both a {@code MessageFormat} and {@code ChoiceFormat} programmatically (instead of using the string patterns), then be careful not to produce a format that recurses on itself, which will cause an infinite loop.

When a single argument is parsed more than once in the string, the last match will be the final result of the parsing. For example:

MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
Object[] objs = {new Double(3.1415)};
String result = mf.format(objs);
// result now equals "3.14, 3.1"
objs = null;
objs = mf.parse(result, new ParsePosition(0));
// objs now equals {new Double(3.1)}

Likewise, parsing with a {@code MessageFormat} object using patterns containing multiple occurrences of the same argument would return the last match. For example:

MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
String forParsing = "x, y, z";
Object[] objs = mf.parse(forParsing, new ParsePosition(0));
// result now equals {new String("z")}

Synchronization

Message formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

see
java.util.Locale
see
Format
see
NumberFormat
see
DecimalFormat
see
ChoiceFormat
since
Android 1.0

Fields Summary
private static final long
serialVersionUID
private Locale
locale
private transient String[]
strings
private int[]
argumentNumbers
private Format[]
formats
private int
maxOffset
private transient int
maxArgumentIndex
private static final ObjectStreamField[]
serialPersistentFields
Constructors Summary
public MessageFormat(String template, Locale locale)
Constructs a new {@code MessageFormat} using the specified pattern and the specified locale for formats.

param
template the pattern.
param
locale the locale.
exception
IllegalArgumentException if the pattern cannot be parsed.
since
Android 1.0


                                                                                 
         
        this.locale = locale;
        applyPattern(template);
    
public MessageFormat(String template)
Constructs a new {@code MessageFormat} using the specified pattern and the default locale for formats.

param
template the pattern.
exception
IllegalArgumentException if the pattern cannot be parsed.
since
Android 1.0

        applyPattern(template);
    
Methods Summary
private voidappendQuoted(java.lang.StringBuffer buffer, java.lang.String string)

        int length = string.length();
        for (int i = 0; i < length; i++) {
            char ch = string.charAt(i);
            if (ch == '{" || ch == '}") {
                buffer.append('\'");
                buffer.append(ch);
                buffer.append('\'");
            } else {
                buffer.append(ch);
            }
        }
    
public voidapplyPattern(java.lang.String template)
Changes this {@code MessageFormat} to use the specified pattern.

param
template the new pattern.
exception
IllegalArgumentException if the pattern cannot be parsed.
since
Android 1.0

        int length = template.length();
        StringBuffer buffer = new StringBuffer();
        ParsePosition position = new ParsePosition(0);
        Vector<String> localStrings = new Vector<String>();
        int argCount = 0;
        int[] args = new int[10];
        int maxArg = -1;
        Vector<Format> localFormats = new Vector<Format>();
        while (position.getIndex() < length) {
            if (Format.upTo(template, position, buffer, '{")) {
                byte arg;
                int offset = position.getIndex();
                if (offset >= length
                        || (arg = (byte) Character.digit(template
                                .charAt(offset++), 10)) == -1) {
                    // text.19=Invalid argument number
                    throw new IllegalArgumentException(Messages
                            .getString("text.19")); //$NON-NLS-1$
                }
                position.setIndex(offset);
                localFormats.addElement(parseVariable(template, position));
                if (argCount >= args.length) {
                    int[] newArgs = new int[args.length * 2];
                    System.arraycopy(args, 0, newArgs, 0, args.length);
                    args = newArgs;
                }
                args[argCount++] = arg;
                if (arg > maxArg) {
                    maxArg = arg;
                }
            }
            localStrings.addElement(buffer.toString());
            buffer.setLength(0);
        }
        this.strings = new String[localStrings.size()];
        for (int i = 0; i < localStrings.size(); i++) {
            this.strings[i] = localStrings.elementAt(i);
        }
        argumentNumbers = args;
        this.formats = new Format[argCount];
        for (int i = 0; i < argCount; i++) {
            this.formats[i] = localFormats.elementAt(i);
        }
        maxOffset = argCount - 1;
        maxArgumentIndex = maxArg;
    
public java.lang.Objectclone()
Returns a new instance of {@code MessageFormat} with the same pattern and formats as this {@code MessageFormat}.

return
a shallow copy of this {@code MessageFormat}.
see
java.lang.Cloneable
since
Android 1.0

        MessageFormat clone = (MessageFormat) super.clone();
        Format[] array = new Format[formats.length];
        for (int i = formats.length; --i >= 0;) {
            if (formats[i] != null) {
                array[i] = (Format) formats[i].clone();
            }
        }
        clone.formats = array;
        return clone;
    
private java.lang.StringdecodeDecimalFormat(java.lang.StringBuffer buffer, java.text.Format format)

        buffer.append(",number"); //$NON-NLS-1$
        if (format.equals(NumberFormat.getNumberInstance(locale))) {
            // Empty block
        } else if (format.equals(NumberFormat.getIntegerInstance(locale))) {
            buffer.append(",integer"); //$NON-NLS-1$
        } else if (format.equals(NumberFormat.getCurrencyInstance(locale))) {
            buffer.append(",currency"); //$NON-NLS-1$
        } else if (format.equals(NumberFormat.getPercentInstance(locale))) {
            buffer.append(",percent"); //$NON-NLS-1$
        } else {
            buffer.append(',");
            return ((DecimalFormat) format).toPattern();
        }
        return null;
    
private java.lang.StringdecodeSimpleDateFormat(java.lang.StringBuffer buffer, java.text.Format format)

        if (format.equals(DateFormat
                .getTimeInstance(DateFormat.DEFAULT, locale))) {
            buffer.append(",time"); //$NON-NLS-1$
        } else if (format.equals(DateFormat.getDateInstance(DateFormat.DEFAULT,
                locale))) {
            buffer.append(",date"); //$NON-NLS-1$
        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.SHORT,
                locale))) {
            buffer.append(",time,short"); //$NON-NLS-1$
        } else if (format.equals(DateFormat.getDateInstance(DateFormat.SHORT,
                locale))) {
            buffer.append(",date,short"); //$NON-NLS-1$
        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.LONG,
                locale))) {
            buffer.append(",time,long"); //$NON-NLS-1$
        } else if (format.equals(DateFormat.getDateInstance(DateFormat.LONG,
                locale))) {
            buffer.append(",date,long"); //$NON-NLS-1$
        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.FULL,
                locale))) {
            buffer.append(",time,full"); //$NON-NLS-1$
        } else if (format.equals(DateFormat.getDateInstance(DateFormat.FULL,
                locale))) {
            buffer.append(",date,full"); //$NON-NLS-1$
        } else {
            buffer.append(",date,"); //$NON-NLS-1$
            return ((SimpleDateFormat) format).toPattern();
        }
        return null;
    
public booleanequals(java.lang.Object object)
Compares the specified object to this {@code MessageFormat} and indicates if they are equal. In order to be equal, {@code object} must be an instance of {@code MessageFormat} and have the same pattern.

param
object the object to compare with this object.
return
{@code true} if the specified object is equal to this {@code MessageFormat}; {@code false} otherwise.
see
#hashCode
since
Android 1.0

        if (this == object) {
            return true;
        }
        if (!(object instanceof MessageFormat)) {
            return false;
        }
        MessageFormat format = (MessageFormat) object;
        if (maxOffset != format.maxOffset) {
            return false;
        }
        // Must use a loop since the lengths may be different due
        // to serialization cross-loading
        for (int i = 0; i <= maxOffset; i++) {
            if (argumentNumbers[i] != format.argumentNumbers[i]) {
                return false;
            }
        }
        return locale.equals(format.locale)
                && Arrays.equals(strings, format.strings)
                && Arrays.equals(formats, format.formats);
    
public final java.lang.StringBufferformat(java.lang.Object object, java.lang.StringBuffer buffer, java.text.FieldPosition field)
Converts the specified objects into a string which it appends to the specified string buffer using the pattern of this message format.

If the {@code field} member of the specified {@code FieldPosition} is {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of this field position is set to the location of the first occurrence of a message format argument. Otherwise, the {@code FieldPosition} is ignored.

Calling this method is equivalent to calling

format((Object[])object, buffer, field)

param
object the object to format, must be an array of {@code Object}.
param
buffer the target string buffer to append the formatted message to.
param
field on input: an optional alignment field; on output: the offsets of the alignment field in the formatted text.
return
the string buffer.
throws
ClassCastException if {@code object} is not an array of {@code Object}.
since
Android 1.0

        return format((Object[]) object, buffer, field);
    
public static java.lang.Stringformat(java.lang.String template, java.lang.Object objects)
Formats the supplied objects using the specified message format pattern.

param
template the pattern to use for formatting.
param
objects the array of objects to format.
return
the formatted result.
exception
IllegalArgumentException if the pattern cannot be parsed.
since
Android 1.0

        // BEGIN android-note
        // changed parameter type from array to varargs.
        // END android-note
        return new MessageFormat(template).format(objects);
    
public final java.lang.StringBufferformat(java.lang.Object[] objects, java.lang.StringBuffer buffer, java.text.FieldPosition field)
Converts the specified objects into a string which it appends to the specified string buffer using the pattern of this message format.

If the {@code field} member of the specified {@code FieldPosition} is {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of this field position is set to the location of the first occurrence of a message format argument. Otherwise, the {@code FieldPosition} is ignored.

param
objects the array of objects to format.
param
buffer the target string buffer to append the formatted message to.
param
field on input: an optional alignment field; on output: the offsets of the alignment field in the formatted text.
return
the string buffer.
since
Android 1.0

        return formatImpl(objects, buffer, field, null);
    
private java.lang.StringBufferformatImpl(java.lang.Object[] objects, java.lang.StringBuffer buffer, java.text.FieldPosition position, java.util.Vector fields)

        FieldPosition passedField = new FieldPosition(0);
        for (int i = 0; i <= maxOffset; i++) {
            buffer.append(strings[i]);
            int begin = buffer.length();
            Object arg;
            if (objects != null && argumentNumbers[i] < objects.length) {
                arg = objects[argumentNumbers[i]];
            } else {
                buffer.append('{");
                buffer.append(argumentNumbers[i]);
                buffer.append('}");
                handleArgumentField(begin, buffer.length(), argumentNumbers[i],
                        position, fields);
                continue;
            }
            Format format = formats[i];
            if (format == null || arg == null) {
                if (arg instanceof Number) {
                    format = NumberFormat.getInstance();
                } else if (arg instanceof Date) {
                    format = DateFormat.getInstance();
                } else {
                    buffer.append(arg);
                    handleArgumentField(begin, buffer.length(),
                            argumentNumbers[i], position, fields);
                    continue;
                }
            }
            if (format instanceof ChoiceFormat) {
                String result = format.format(arg);
                MessageFormat mf = new MessageFormat(result);
                mf.setLocale(locale);
                mf.format(objects, buffer, passedField);
                handleArgumentField(begin, buffer.length(), argumentNumbers[i],
                        position, fields);
                handleformat(format, arg, begin, fields);
            } else {
                format.format(arg, buffer, passedField);
                handleArgumentField(begin, buffer.length(), argumentNumbers[i],
                        position, fields);
                handleformat(format, arg, begin, fields);
            }
        }
        if (maxOffset + 1 < strings.length) {
            buffer.append(strings[maxOffset + 1]);
        }
        return buffer;
    
public java.text.AttributedCharacterIteratorformatToCharacterIterator(java.lang.Object object)
Formats the specified object using the rules of this message format and returns an {@code AttributedCharacterIterator} with the formatted message and attributes. The {@code AttributedCharacterIterator} returned also includes the attributes from the formats of this message format.

param
object the object to format.
return
an {@code AttributedCharacterIterator} with the formatted message and attributes.
exception
IllegalArgumentException if the arguments in the object array cannot be formatted by this message format.
since
Android 1.0

        if (object == null) {
            throw new NullPointerException();
        }

        StringBuffer buffer = new StringBuffer();
        Vector<FieldContainer> fields = new Vector<FieldContainer>();

        // format the message, and find fields
        formatImpl((Object[]) object, buffer, new FieldPosition(0), fields);

        // create an AttributedString with the formatted buffer
        AttributedString as = new AttributedString(buffer.toString());

        // add MessageFormat field attributes and values to the AttributedString
        for (int i = 0; i < fields.size(); i++) {
            FieldContainer fc = fields.elementAt(i);
            as.addAttribute(fc.attribute, fc.value, fc.start, fc.end);
        }

        // return the CharacterIterator from AttributedString
        return as.getIterator();
    
public java.text.Format[]getFormats()
Returns the {@code Format} instances used by this message format.

return
an array of {@code Format} instances.
since
Android 1.0

        return formats.clone();
    
public java.text.Format[]getFormatsByArgumentIndex()
Returns the formats used for each argument index. If an argument is placed more than once in the pattern string, then this returns the format of the last one.

return
an array of formats, ordered by argument index.
since
Android 1.0

        Format[] answer = new Format[maxArgumentIndex + 1];
        for (int i = 0; i < maxOffset + 1; i++) {
            answer[argumentNumbers[i]] = formats[i];
        }
        return answer;
    
public java.util.LocalegetLocale()
Returns the locale used when creating formats.

return
the locale used to create formats.
since
Android 1.0

        return locale;
    
private voidhandleArgumentField(int begin, int end, int argnumber, java.text.FieldPosition position, java.util.Vector fields)
Adds a new FieldContainer with MessageFormat.Field.ARGUMENT field, argnumber, begin and end index to the fields vector, or sets the position's begin and end index if it has MessageFormat.Field.ARGUMENT as its field attribute.

param
begin
param
end
param
argnumber
param
position
param
fields

        if (fields != null) {
            fields.add(new FieldContainer(begin, end, Field.ARGUMENT,
                    new Integer(argnumber)));
        } else {
            if (position != null
                    && position.getFieldAttribute() == Field.ARGUMENT
                    && position.getEndIndex() == 0) {
                position.setBeginIndex(begin);
                position.setEndIndex(end);
            }
        }
    
private voidhandleformat(java.text.Format format, java.lang.Object arg, int begin, java.util.Vector fields)
If fields vector is not null, find and add the fields of this format to the fields vector by iterating through its AttributedCharacterIterator

param
format the format to find fields for
param
arg object to format
param
begin the index where the string this format has formatted begins
param
fields fields vector, each entry in this vector are of type FieldContainer.

        if (fields != null) {
            AttributedCharacterIterator iterator = format
                    .formatToCharacterIterator(arg);
            while (iterator.getIndex() != iterator.getEndIndex()) {
                int start = iterator.getRunStart();
                int end = iterator.getRunLimit();

                Iterator<?> it = iterator.getAttributes().keySet().iterator();
                while (it.hasNext()) {
                    AttributedCharacterIterator.Attribute attribute = (AttributedCharacterIterator.Attribute) it
                            .next();
                    Object value = iterator.getAttribute(attribute);
                    fields.add(new FieldContainer(begin + start, begin + end,
                            attribute, value));
                }
                iterator.setIndex(end);
            }
        }
    
public inthashCode()

        int hashCode = 0;
        for (int i = 0; i <= maxOffset; i++) {
            hashCode += argumentNumbers[i] + strings[i].hashCode();
            if (formats[i] != null) {
                hashCode += formats[i].hashCode();
            }
        }
        if (maxOffset + 1 < strings.length) {
            hashCode += strings[maxOffset + 1].hashCode();
        }
        if (locale != null) {
            return hashCode + locale.hashCode();
        }
        return hashCode;
    
private intmatch(java.lang.String string, java.text.ParsePosition position, boolean last, java.lang.String[] tokens)

        int length = string.length(), offset = position.getIndex(), token = -1;
        while (offset < length && Character.isWhitespace(string.charAt(offset))) {
            offset++;
        }
        for (int i = tokens.length; --i >= 0;) {
            if (string.regionMatches(true, offset, tokens[i], 0, tokens[i]
                    .length())) {
                token = i;
                break;
            }
        }
        if (token == -1) {
            return -1;
        }
        offset += tokens[token].length();
        while (offset < length && Character.isWhitespace(string.charAt(offset))) {
            offset++;
        }
        char ch;
        if (offset < length
                && ((ch = string.charAt(offset)) == '}" || (!last && ch == ',"))) {
            position.setIndex(offset + 1);
            return token;
        }
        return -1;
    
public java.lang.Object[]parse(java.lang.String string)
Parses the message arguments from the specified string using the rules of this message format.

param
string the string to parse.
return
the array of {@code Object} arguments resulting from the parse.
exception
ParseException if an error occurs during parsing.
since
Android 1.0

        ParsePosition position = new ParsePosition(0);
        Object[] result = parse(string, position);
        if (position.getErrorIndex() != -1 || position.getIndex() == 0) {
            throw new ParseException(null, position.getErrorIndex());
        }
        return result;
    
public java.lang.Object[]parse(java.lang.String string, java.text.ParsePosition position)
Parses the message argument from the specified string starting at the index specified by {@code position}. If the string is successfully parsed then the index of the {@code ParsePosition} is updated to the index following the parsed text. On error, the index is unchanged and the error index of {@code ParsePosition} is set to the index where the error occurred.

param
string the string to parse.
param
position input/output parameter, specifies the start index in {@code string} from where to start parsing. If parsing is successful, it is updated with the index following the parsed text; on error, the index is unchanged and the error index is set to the index where the error occurred.
return
the array of objects resulting from the parse, or {@code null} if there is an error.
since
Android 1.0

        if (string == null) {
            return new Object[0];
        }
        ParsePosition internalPos = new ParsePosition(0);
        int offset = position.getIndex();
        Object[] result = new Object[maxArgumentIndex + 1];
        for (int i = 0; i <= maxOffset; i++) {
            String sub = strings[i];
            if (!string.startsWith(sub, offset)) {
                position.setErrorIndex(offset);
                return null;
            }
            offset += sub.length();
            Object parse;
            Format format = formats[i];
            if (format == null) {
                if (i + 1 < strings.length) {
                    int next = string.indexOf(strings[i + 1], offset);
                    if (next == -1) {
                        position.setErrorIndex(offset);
                        return null;
                    }
                    parse = string.substring(offset, next);
                    offset = next;
                } else {
                    parse = string.substring(offset);
                    offset = string.length();
                }
            } else {
                internalPos.setIndex(offset);
                parse = format.parseObject(string, internalPos);
                if (internalPos.getErrorIndex() != -1) {
                    position.setErrorIndex(offset);
                    return null;
                }
                offset = internalPos.getIndex();
            }
            result[argumentNumbers[i]] = parse;
        }
        if (maxOffset + 1 < strings.length) {
            String sub = strings[maxOffset + 1];
            if (!string.startsWith(sub, offset)) {
                position.setErrorIndex(offset);
                return null;
            }
            offset += sub.length();
        }
        position.setIndex(offset);
        return result;
    
public java.lang.ObjectparseObject(java.lang.String string, java.text.ParsePosition position)
Parses the message argument from the specified string starting at the index specified by {@code position}. If the string is successfully parsed then the index of the {@code ParsePosition} is updated to the index following the parsed text. On error, the index is unchanged and the error index of {@code ParsePosition} is set to the index where the error occurred.

param
string the string to parse.
param
position input/output parameter, specifies the start index in {@code string} from where to start parsing. If parsing is successful, it is updated with the index following the parsed text; on error, the index is unchanged and the error index is set to the index where the error occurred.
return
the array of objects resulting from the parse, or {@code null} if there is an error.
since
Android 1.0

        return parse(string, position);
    
private java.text.FormatparseVariable(java.lang.String string, java.text.ParsePosition position)

        int length = string.length(), offset = position.getIndex();
        char ch;
        if (offset >= length
                || ((ch = string.charAt(offset++)) != '}" && ch != ',")) {
            // text.15=Missing element format
            throw new IllegalArgumentException(Messages.getString("text.15")); //$NON-NLS-1$
        }
        position.setIndex(offset);
        if (ch == '}") {
            return null;
        }
        int type = match(string, position, false, new String[] { "time", //$NON-NLS-1$
                "date", "number", "choice" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        if (type == -1) {
            // text.16=Unknown element format
            throw new IllegalArgumentException(Messages.getString("text.16")); //$NON-NLS-1$
        }
        StringBuffer buffer = new StringBuffer();
        ch = string.charAt(position.getIndex() - 1);
        switch (type) {
            case 0: // time
            case 1: // date
                if (ch == '}") {
                    return type == 1 ? DateFormat.getDateInstance(
                            DateFormat.DEFAULT, locale) : DateFormat
                            .getTimeInstance(DateFormat.DEFAULT, locale);
                }
                int dateStyle = match(string, position, true, new String[] {
                        "full", "long", "medium", "short" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
                if (dateStyle == -1) {
                    Format.upToWithQuotes(string, position, buffer, '}", '{");
                    return new SimpleDateFormat(buffer.toString(), locale);
                }
                switch (dateStyle) {
                    case 0:
                        dateStyle = DateFormat.FULL;
                        break;
                    case 1:
                        dateStyle = DateFormat.LONG;
                        break;
                    case 2:
                        dateStyle = DateFormat.MEDIUM;
                        break;
                    case 3:
                        dateStyle = DateFormat.SHORT;
                        break;
                }
                return type == 1 ? DateFormat
                        .getDateInstance(dateStyle, locale) : DateFormat
                        .getTimeInstance(dateStyle, locale);
            case 2: // number
                if (ch == '}") {
                    // BEGIN android-changed
                    return NumberFormat.getInstance(locale);
                    // END android-changed
                }
                int numberStyle = match(string, position, true, new String[] {
                        "currency", "percent", "integer" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                if (numberStyle == -1) {
                    Format.upToWithQuotes(string, position, buffer, '}", '{");
                    return new DecimalFormat(buffer.toString(),
                            new DecimalFormatSymbols(locale));
                }
                switch (numberStyle) {
                    case 0: // currency
                        return NumberFormat.getCurrencyInstance(locale);
                    case 1: // percent
                        return NumberFormat.getPercentInstance(locale);
                }
                return NumberFormat.getIntegerInstance(locale);
        }
        // choice
        try {
            Format.upToWithQuotes(string, position, buffer, '}", '{");
        } catch (IllegalArgumentException e) {
            // ignored
        }
        return new ChoiceFormat(buffer.toString());
    
private voidreadObject(java.io.ObjectInputStream stream)

        ObjectInputStream.GetField fields = stream.readFields();
        argumentNumbers = (int[]) fields.get("argumentNumbers", null); //$NON-NLS-1$
        formats = (Format[]) fields.get("formats", null); //$NON-NLS-1$
        locale = (Locale) fields.get("locale", null); //$NON-NLS-1$
        maxOffset = fields.get("maxOffset", 0); //$NON-NLS-1$
        int[] offsets = (int[]) fields.get("offsets", null); //$NON-NLS-1$
        String pattern = (String) fields.get("pattern", null); //$NON-NLS-1$
        int length;
        if (maxOffset < 0) {
            length = pattern.length() > 0 ? 1 : 0;
        } else {
            length = maxOffset
                    + (offsets[maxOffset] == pattern.length() ? 1 : 2);
        }
        strings = new String[length];
        int last = 0;
        for (int i = 0; i <= maxOffset; i++) {
            strings[i] = pattern.substring(last, offsets[i]);
            last = offsets[i];
        }
        if (maxOffset + 1 < strings.length) {
            strings[strings.length - 1] = pattern.substring(last, pattern
                    .length());
        }
    
public voidsetFormat(int offset, java.text.Format format)
Sets the specified format used by this message format.

param
offset the index of the format to change.
param
format the {@code Format} that replaces the old format.
since
Android 1.0

        formats[offset] = format;
    
public voidsetFormatByArgumentIndex(int argIndex, java.text.Format format)
Sets the format used for the argument at index {@code argIndex} to {@code format}.

param
argIndex the index of the format to set.
param
format the format that will be set at index {@code argIndex}.
since
Android 1.0

        for (int i = 0; i < maxOffset + 1; i++) {
            if (argumentNumbers[i] == argIndex) {
                formats[i] = format;
            }
        }
    
public voidsetFormats(java.text.Format[] formats)
Sets the formats used by this message format.

param
formats an array of {@code Format}.
since
Android 1.0

        int min = this.formats.length;
        if (formats.length < min) {
            min = formats.length;
        }
        for (int i = 0; i < min; i++) {
            this.formats[i] = formats[i];
        }
    
public voidsetFormatsByArgumentIndex(java.text.Format[] formats)
Sets the formats used for each argument. The {@code formats} array elements should be in the order of the argument indices.

param
formats the formats in an array.
since
Android 1.0

        for (int j = 0; j < formats.length; j++) {
            for (int i = 0; i < maxOffset + 1; i++) {
                if (argumentNumbers[i] == j) {
                    this.formats[i] = formats[j];
                }
            }
        }
    
public voidsetLocale(java.util.Locale locale)
Sets the locale to use when creating {@code Format} instances. Changing the locale may change the behavior of {@code applyPattern}, {@code toPattern}, {@code format} and {@code formatToCharacterIterator}.

param
locale the new locale.
since
Android 1.0

        this.locale = locale;
        for (int i = 0; i <= maxOffset; i++) {
            Format format = formats[i];
            // BEGIN android-removed
            //if (format instanceof DecimalFormat) {
            //     formats[i] = new DecimalFormat(((DecimalFormat) format)
            //             .toPattern(), new DecimalFormatSymbols(locale));                
            //} else if (format instanceof SimpleDateFormat) {
            //     formats[i] = new SimpleDateFormat(((SimpleDateFormat) format)
            //             .toPattern(), locale);
            //}
            // END android-removed
            // BEGIN android-added
            // java specification undefined for null argument, change into 
            // a more tolerant implementation
            if (format instanceof DecimalFormat) {
                try {
                    formats[i] = new DecimalFormat(((DecimalFormat) format)
                            .toPattern(), new DecimalFormatSymbols(locale));
                } catch (NullPointerException npe){
                    formats[i] = null;
                }
            } else if (format instanceof SimpleDateFormat) {
                try {
                    formats[i] = new SimpleDateFormat(((SimpleDateFormat) format)
                            .toPattern(), locale);
                } catch (NullPointerException npe) {
                    formats[i] = null;
                }
            }
            // END android-added
        }
    
public java.lang.StringtoPattern()
Returns the pattern of this message format.

return
the pattern.
since
Android 1.0

        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i <= maxOffset; i++) {
            appendQuoted(buffer, strings[i]);
            buffer.append('{");
            buffer.append(argumentNumbers[i]);
            Format format = formats[i];
            String pattern = null;
            if (format instanceof ChoiceFormat) {
                buffer.append(",choice,"); //$NON-NLS-1$
                pattern = ((ChoiceFormat) format).toPattern();
            } else if (format instanceof DecimalFormat) {
                pattern = decodeDecimalFormat(buffer, format);
            } else if (format instanceof SimpleDateFormat) {
                pattern = decodeSimpleDateFormat(buffer, format);
            } else if (format != null) {
                // text.17=Unknown format
                throw new IllegalArgumentException(Messages
                        .getString("text.17")); //$NON-NLS-1$
            }
            if (pattern != null) {
                boolean quote = false;
                int index = 0, length = pattern.length(), count = 0;
                while (index < length) {
                    char ch = pattern.charAt(index++);
                    if (ch == '\'") {
                        quote = !quote;
                    }
                    if (!quote) {
                        if (ch == '{") {
                            count++;
                        }
                        if (ch == '}") {
                            if (count > 0) {
                                count--;
                            } else {
                                buffer.append("'}"); //$NON-NLS-1$
                                ch = '\'";
                            }
                        }
                    }
                    buffer.append(ch);
                }
            }
            buffer.append('}");
        }
        if (maxOffset + 1 < strings.length) {
            appendQuoted(buffer, strings[maxOffset + 1]);
        }
        return buffer.toString();
    
private voidwriteObject(java.io.ObjectOutputStream stream)

 //$NON-NLS-1$

          
        ObjectOutputStream.PutField fields = stream.putFields();
        fields.put("argumentNumbers", argumentNumbers); //$NON-NLS-1$
        Format[] compatibleFormats = formats;
        fields.put("formats", compatibleFormats); //$NON-NLS-1$
        fields.put("locale", locale); //$NON-NLS-1$
        fields.put("maxOffset", maxOffset); //$NON-NLS-1$
        int offset = 0;
        int offsetsLength = maxOffset + 1;
        int[] offsets = new int[offsetsLength];
        StringBuffer pattern = new StringBuffer();
        for (int i = 0; i <= maxOffset; i++) {
            offset += strings[i].length();
            offsets[i] = offset;
            pattern.append(strings[i]);
        }
        if (maxOffset + 1 < strings.length) {
            pattern.append(strings[maxOffset + 1]);
        }
        fields.put("offsets", offsets); //$NON-NLS-1$
        fields.put("pattern", pattern.toString()); //$NON-NLS-1$
        stream.writeFields();