// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
/*
**********************************************************************
* Copyright (c) 2004-2016, International Business Machines
* Corporation and others.  All Rights Reserved.
**********************************************************************
* Author: Alan Liu
* Created: April 6, 2004
* Since: ICU 3.0
**********************************************************************
*/
package com.ibm.icu.dev.test.format;

import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.ChoiceFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.MessagePattern;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.text.UFormat;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;

@RunWith(JUnit4.class)
public class TestMessageFormat extends TestFmwk {
    @Test
    public void TestBug3()
    {
        double myNumber = -123456;
        DecimalFormat form = null;
        Locale locale[] = {
            new Locale("ar", "", ""),
            new Locale("be", "", ""),
            new Locale("bg", "", ""),
            new Locale("ca", "", ""),
            new Locale("cs", "", ""),
            new Locale("da", "", ""),
            new Locale("de", "", ""),
            new Locale("de", "AT", ""),
            new Locale("de", "CH", ""),
            new Locale("el", "", ""),       // 10
            new Locale("en", "CA", ""),
            new Locale("en", "GB", ""),
            new Locale("en", "IE", ""),
            new Locale("en", "US", ""),
            new Locale("es", "", ""),
            new Locale("et", "", ""),
            new Locale("fi", "", ""),
            new Locale("fr", "", ""),
            new Locale("fr", "BE", ""),
            new Locale("fr", "CA", ""),     // 20
            new Locale("fr", "CH", ""),
            new Locale("he", "", ""),
            new Locale("hr", "", ""),
            new Locale("hu", "", ""),
            new Locale("is", "", ""),
            new Locale("it", "", ""),
            new Locale("it", "CH", ""),
            new Locale("ja", "", ""),
            new Locale("ko", "", ""),
            new Locale("lt", "", ""),       // 30
            new Locale("lv", "", ""),
            new Locale("mk", "", ""),
            new Locale("nl", "", ""),
            new Locale("nl", "BE", ""),
            new Locale("no", "", ""),
            new Locale("pl", "", ""),
            new Locale("pt", "", ""),
            new Locale("ro", "", ""),
            new Locale("ru", "", ""),
            new Locale("sh", "", ""),       // 40
            new Locale("sk", "", ""),
            new Locale("sl", "", ""),
            new Locale("sq", "", ""),
            new Locale("sr", "", ""),
            new Locale("sv", "", ""),
            new Locale("tr", "", ""),
            new Locale("uk", "", ""),
            new Locale("zh", "", ""),
            new Locale("zh", "TW", "")      // 49
        };
        StringBuffer buffer = new StringBuffer();
        ParsePosition parsePos = new ParsePosition(0);
        int i;
        for (i= 0; i < 49; i++) {
    //        form = (DecimalFormat)NumberFormat.getCurrencyInstance(locale[i]);
            form = (DecimalFormat)NumberFormat.getInstance(locale[i]);
            if (form == null) {
                errln("Number format creation failed for " + locale[i].getDisplayName());
                continue;
            }
            FieldPosition pos = new FieldPosition(FieldPosition_DONT_CARE);
            buffer.setLength(0);
            form.format(myNumber, buffer, pos);
            parsePos.setIndex(0);
            Object result = form.parse(buffer.toString(), parsePos);
            logln(locale[i].getDisplayName() + " -> " + result);
            if (parsePos.getIndex() != buffer.length()) {
                errln("Number format parse failed.");
            }
        }
    }

    @Test
    public void TestBug1()
    {
        final double limit[] = {0.0, 1.0, 2.0};
        final String formats[] = {"0.0<=Arg<1.0",
                                  "1.0<=Arg<2.0",
                                  "2.0<-Arg"};
        ChoiceFormat cf = new ChoiceFormat(limit, formats);
        assertEquals("ChoiceFormat.format", formats[1], cf.format(1));
    }

    @Test
    public void TestBug2()
    {
        // {sfb} use double format in pattern, so result will match (not strictly necessary)
        final String pattern = "There {0,choice,0.0#are no files|1.0#is one file|1.0<are {0, number} files} on disk {1}. ";
        logln("The input pattern : " + pattern);
        try {
            MessageFormat fmt = new MessageFormat(pattern);
            assertEquals("toPattern", pattern, fmt.toPattern());
        } catch (IllegalArgumentException e) {
            errln("MessageFormat pattern creation failed.");
        }
    }

    @Test
    public void TestPattern() // aka PatternTest()
    {
        Object testArgs[] = {
            new Double(1), new Double(3456),
            "Disk", new Date(1000000000L)
        };
        String testCases[] = {
           "Quotes '', '{', 'a' {0} '{0}'",
           "Quotes '', '{', 'a' {0,number} '{0}'",
           "'{'1,number,'#',##} {1,number,'#',##}",
           "There are {1} files on {2} at {3}.",
           "On {2}, there are {1} files, with {0,number,currency}.",
           "'{1,number,percent}', {1,number,percent},",
           "'{1,date,full}', {1,date,full},",
           "'{3,date,full}', {3,date,full},",
           "'{1,number,#,##}' {1,number,#,##}",
        };

        // ICU 4.8 returns the original pattern (testCases)
        // rather than toPattern() reconstituting a new, equivalent pattern string (testResultPatterns).
        /*String testResultPatterns[] = {
            "Quotes '', '{', a {0} '{'0}",
            "Quotes '', '{', a {0,number} '{'0}",
            "'{'1,number,#,##} {1,number,'#'#,##}",
            "There are {1} files on {2} at {3}.",
            "On {2}, there are {1} files, with {0,number,currency}.",
            "'{'1,number,percent}, {1,number,percent},",
            "'{'1,date,full}, {1,date,full},",
            "'{'3,date,full}, {3,date,full},",
            "'{'1,number,#,##} {1,number,#,##}"
        };*/

        String testResultStrings[] = {
            "Quotes ', {, 'a' 1 {0}",
            "Quotes ', {, 'a' 1 {0}",
            "{1,number,'#',##} #34,56",
            "There are 3,456 files on Disk at 1/12/70, 5:46 AM.",
            "On Disk, there are 3,456 files, with $1.00.",
            "{1,number,percent}, 345,600%,",
            "{1,date,full}, Wednesday, December 31, 1969,",
            "{3,date,full}, Monday, January 12, 1970,",
            "{1,number,#,##} 34,56"
        };

        for (int i = 0; i < 9; ++i) {
            //it_out << "\nPat in:  " << testCases[i]);

            //String buffer;
            MessageFormat form = null;
            try {
                form = new MessageFormat(testCases[i], Locale.US);
            } catch (IllegalArgumentException e1) {
                errln("MessageFormat for " + testCases[i] + " creation failed.");
                continue;
            }
            // ICU 4.8 returns the original pattern (testCases)
            // rather than toPattern() reconstituting a new, equivalent pattern string (testResultPatterns).
            // assertEquals("\"" + testCases[i] + "\".toPattern()", testResultPatterns[i], form.toPattern());
            assertEquals("\"" + testCases[i] + "\".toPattern()", testCases[i], form.toPattern());
            // Note: An alternative test would be to build MessagePattern objects for
            // both the input and output patterns and compare them, taking SKIP_SYNTAX etc.
            // into account.
            // (Too much trouble...)

            //it_out << "Pat out: " << form.toPattern(buffer));
            StringBuffer result = new StringBuffer();
            FieldPosition fieldpos = new FieldPosition(FieldPosition_DONT_CARE);
            form.format(testArgs, result, fieldpos);
            assertEquals("format", testResultStrings[i], result.toString());

            //it_out << "Result:  " << result);
    //        /* TODO: Look at this test and see if this is still a valid test */
    //        logln("---------------- test parse ----------------");
    //
    //        int count = 4;
    //        form.toPattern(buffer);
    //        logln("MSG pattern for parse: " + buffer);
    //
    //        int parseCount = 0;
    //        Formattable* values = form.parse(result, parseCount, success);
    //        if (U_FAILURE(success)) {
    //            errln("MessageFormat failed test #5");
    //            logln(String("MessageFormat failed test #5 with error code ")+(int)success);
    //        } else if (parseCount != count) {
    //            errln("MSG count not %d as expected. Got %d", count, parseCount);
    //        }
    //        UBool failed = FALSE;
    //        for (int j = 0; j < parseCount; ++j) {
    //             if (values == 0 || testArgs[j] != values[j]) {
    //                errln(((String)"MSG testargs[") + j + "]: " + toString(testArgs[j]));
    //                errln(((String)"MSG values[") + j + "]  : " + toString(values[j]));
    //                failed = TRUE;
    //             }
    //        }
    //        if (failed)
    //            errln("MessageFormat failed test #6");
        }
    }

    @Test
    public void TestSample() // aka sample()
    {
        MessageFormat form = null;
        StringBuffer buffer2 = new StringBuffer();
        try {
            form = new MessageFormat("There are {0} files on {1}");
        } catch (IllegalArgumentException e1) {
            errln("Sample message format creation failed.");
            return;
        }
        Object testArgs1[] = { "abc", "def" };
        FieldPosition fieldpos = new FieldPosition(FieldPosition_DONT_CARE);
        assertEquals("format",
                     "There are abc files on def",
                     form.format(testArgs1, buffer2, fieldpos).toString());
    }

    @Test
    public void TestStaticFormat()
    {
        Object arguments[] = {
            new Integer(7),
            new Date(871068000000L),
            "a disturbance in the Force"
        };

        assertEquals("format",
            "At 12:20:00 PM on Aug 8, 1997, there was a disturbance in the Force on planet 7.",
            MessageFormat.format("At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
                                 arguments));
    }

    static final int FieldPosition_DONT_CARE = -1;

    @Test
    public void TestSimpleFormat()
    {
        Object testArgs1[] = {new Integer(0), "MyDisk"};
        Object testArgs2[] = {new Integer(1), "MyDisk"};
        Object testArgs3[] = {new Integer(12), "MyDisk"};

        MessageFormat form = new MessageFormat(
            "The disk \"{1}\" contains {0} file(s).");

        StringBuffer string = new StringBuffer();
        FieldPosition ignore = new FieldPosition(FieldPosition_DONT_CARE);
        form.format(testArgs1, string, ignore);
        assertEquals("format",
                     "The disk \"MyDisk\" contains 0 file(s).",
                     string.toString());

        string.setLength(0);
        form.format(testArgs2, string, ignore);
        assertEquals("format",
                     "The disk \"MyDisk\" contains 1 file(s).",
                     string.toString());

        string.setLength(0);
        form.format(testArgs3, string, ignore);
        assertEquals("format",
                     "The disk \"MyDisk\" contains 12 file(s).",
                     string.toString());
    }

    @Test
    public void TestMsgFormatChoice()
    {
        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.setFormat(1, fileform); // NOT zero, see below

        FieldPosition ignore = new FieldPosition(FieldPosition_DONT_CARE);
        StringBuffer string = new StringBuffer();
        Object testArgs1[] = {new Integer(0), "MyDisk"};
        form.format(testArgs1, string, ignore);
        assertEquals("format#1",
                     "The disk \"MyDisk\" contains no files.",
                     string.toString());

        string.setLength(0);
        Object testArgs2[] = {new Integer(1), "MyDisk"};
        form.format(testArgs2, string, ignore);
        assertEquals("format#2",
                     "The disk \"MyDisk\" contains one file.",
                     string.toString());

        string.setLength(0);
        Object testArgs3[] = {new Integer(1273), "MyDisk"};
        form.format(testArgs3, string, ignore);
        assertEquals("format#3",
                     "The disk \"MyDisk\" contains 1,273 files.",
                     string.toString());
    }

    //---------------------------------
    //  API Tests
    //---------------------------------

    @Test
    public void TestClone()
    {
        MessageFormat x = new MessageFormat("There are {0} files on {1}");
        MessageFormat z = new MessageFormat("There are {0} files on {1} created");
        MessageFormat y = null;
        y = (MessageFormat)x.clone();
        if (x.equals(y) &&
            !x.equals(z) &&
            !y.equals(z) )
            logln("First test (operator ==): Passed!");
        else {
            errln("First test (operator ==): Failed!");
        }
        if ((x.equals(y) && y.equals(x)) &&
            (!x.equals(z) && !z.equals(x)) &&
            (!y.equals(z) && !z.equals(y)) )
            logln("Second test (equals): Passed!");
        else {
            errln("Second test (equals): Failed!");
        }

    }

    @Test
    public void TestEquals()
    {
        MessageFormat x = new MessageFormat("There are {0} files on {1}");
        MessageFormat y = new MessageFormat("There are {0} files on {1}");
        if (!x.equals(y)) {
            errln("First test (operator ==): Failed!");
        }

    }

    @Test
    public void TestNotEquals()
    {
        MessageFormat x = new MessageFormat("There are {0} files on {1}");
        MessageFormat y = new MessageFormat("There are {0} files on {1}");
        y.setLocale(Locale.FRENCH);
        if (x.equals(y)) {
            errln("First test (operator !=): Failed!");
        }
        y = new MessageFormat("There are {0} files on {1}");
        y.applyPattern("There are {0} files on {1} the disk");
        if (x.equals(y)) {
            errln("Second test (operator !=): Failed!");
        }
    }

    @Test
    public void TestHashCode()
    {
        ULocale save = ULocale.getDefault();
        ULocale.setDefault(ULocale.US);

        MessageFormat x = new MessageFormat("There are {0} files on {1}");
        MessageFormat z = new MessageFormat("There are {0} files on {1}");
        MessageFormat y = null;
        y = (MessageFormat)x.clone();
        if (x.hashCode() != y.hashCode())
            errln("FAIL: identical objects have different hashcodes");
        if (x.hashCode() != z.hashCode())
            errln("FAIL: identical objects have different hashcodes");

    /* These are not errors
        y.setLocale(ULocale.FRENCH);
        if (x.hashCode() == y.hashCode())
            errln("FAIL: different objects have same hashcodes. Locale ignored");

        z.applyPattern("There are {0} files on {1} the disk");
        if (x.hashCode() == z.hashCode())
            errln("FAIL: different objects have same hashcodes. Pattern ignored");
    */

        ULocale.setDefault(save);
    }

    @Test
    public void TestSetLocale()
    {
        Object arguments[] = {
            new Double(456.83),
            new Date(871068000000L),
            "deposit"
            };

        StringBuffer result = new StringBuffer();

        //String formatStr = "At {1,time} on {1,date}, you made a {2} of {0,number,currency}.";
        String formatStr = "At <time> on {1,date}, you made a {2} of {0,number,currency}.";
        // {sfb} to get $, would need Locale::US, not Locale::ENGLISH
        // Just use unlocalized currency symbol.
        // ICU 62: use the unknown currency symbol XXX.
        //String compareStrEng = "At <time> on Aug 8, 1997, you made a deposit of $456.83.";
        String compareStrEng = "At <time> on Aug 8, 1997, you made a deposit of ";
        compareStrEng += "\u00a4";
        compareStrEng += "456.83.";
        // {sfb} to get DM, would need Locale::GERMANY, not Locale::GERMAN
        // Just use unlocalized currency symbol.
        //String compareStrGer = "At <time> on 08.08.1997, you made a deposit of 456,83 DM.";
        String compareStrGer = "At <time> on 08.08.1997, you made a deposit of ";
        compareStrGer += "456,83\u00a0";
        compareStrGer += "XXX";
        compareStrGer += ".";

        MessageFormat msg = new MessageFormat(formatStr, Locale.ENGLISH);
        result.setLength(0);
        FieldPosition pos = new FieldPosition(FieldPosition_DONT_CARE);
        result = msg.format(
            arguments,
            result,
            pos);
        assertEquals("format", compareStrEng, result.toString());

        msg.setLocale(Locale.ENGLISH);
        assertEquals("getLocale", Locale.ENGLISH, msg.getLocale());

        msg.setLocale(Locale.GERMAN);
        assertEquals("getLocale", Locale.GERMAN, msg.getLocale());

        msg.applyPattern(formatStr);
        result.setLength(0);
        result = msg.format(
            arguments,
            result,
            pos);
        assertEquals("format", compareStrGer, result.toString());

        //Cover getULocale()
        logln("Testing set/get ULocale ...");
        msg.setLocale(ULocale.ENGLISH);
        assertEquals("getULocale", ULocale.ENGLISH, msg.getULocale());

        msg.setLocale(ULocale.GERMAN);
        assertEquals("getULocale", ULocale.GERMAN, msg.getULocale());

        msg.applyPattern(formatStr);
        result.setLength(0);
        result = msg.format(
            arguments,
            result,
            pos);
        assertEquals("format", compareStrGer, result.toString());
    }

    @SuppressWarnings("static-access")
    @Test
    public void TestFormat()
    {
        final Object ft_arr[] =
        {
            new Date(871068000000L)
        };

        StringBuffer result = new StringBuffer();

        //String formatStr = "At {1,time} on {1,date}, you made a {2} of {0,number,currency}.";
        String formatStr = "On {0,date}, it began.";
        String compareStr = "On Aug 8, 1997, it began.";

        MessageFormat msg = new MessageFormat(formatStr);
        FieldPosition fp = new FieldPosition(FieldPosition_DONT_CARE);

        try {
            msg.format(new Date(871068000000L),
                       result,
                       fp);
            errln("*** MSG format without expected error code.");
        } catch (Exception e1) {
        }

        result.setLength(0);
        result = msg.format(
            ft_arr,
            result,
            fp);
        assertEquals("format", compareStr, result.toString());

        Map<String,Object> map = new HashMap<String,Object>();
        try{
            msg.format("", map);
        } catch(Exception e){
            errln("MessageFormat.format(String,Map) was not suppose to return " +
                    "an exception.");
        }
    }

    @Test
    public void TestParse()
    {
        String msgFormatString = "{0} =sep= {1}";
        MessageFormat msg = new MessageFormat(msgFormatString);
        String source = "abc =sep= def";

        try {
            Object[] fmt_arr = msg.parse(source);
            if (fmt_arr.length != 2) {
                errln("*** MSG parse (ustring, count, err) count err.");
            } else {
                // TODO: This if statement seems to be redundant. [tschumann]
                if (fmt_arr.length != 2) {
                    errln("*** MSG parse (ustring, parsepos., count) count err.");
                } else {
                    assertEquals("parse()[0]", "abc", fmt_arr[0]);
                    assertEquals("parse()[1]", "def", fmt_arr[1]);
                }
            }
        } catch (ParseException e1) {
            errln("*** MSG parse (ustring, count, err) error.");
        }

        ParsePosition pp = new ParsePosition(0);

        Object[] fmt_arr = msg.parse(source, pp);
        if (pp.getIndex()==0 || fmt_arr==null) {
            errln("*** MSG parse (ustring, parsepos., count) error.");
        } else {
            if (fmt_arr.length != 2) {
                errln("*** MSG parse (ustring, parsepos., count) count err.");
            } else {
                assertEquals("parse()[0]", "abc", fmt_arr[0]);
                assertEquals("parse()[1]", "def", fmt_arr[1]);
            }
        }

        pp.setIndex(0);
        Object[] fmta;

        fmta = (Object[]) msg.parseObject( source, pp );
        if (pp.getIndex() == 0) {
            errln("*** MSG parse (ustring, Object, parsepos ) error.");
        } else {
            if (fmta.length != 2) {
                errln("*** MSG parse (ustring, count, err) count err.");
            } else {
                // TODO: Don't we want to check fmta?
                //       In this case this if statement would be redundant, too.
                //       [tschumann]
                if (fmt_arr.length != 2) {
                    errln("*** MSG parse (ustring, parsepos., count) count err.");
                } else {
                    // TODO: Don't we want to check fmta? [tschumann]
                    assertEquals("parse()[0]", "abc", fmt_arr[0]);
                    assertEquals("parse()[1]", "def", fmt_arr[1]);
                }
            }
        }
    }

    /**
     * Of course, in Java there is no adopt, but we retain the same
     * method name. [alan]
     */
    @Test
    public void TestAdopt()
    {
        String formatStr = "{0,date},{1},{2,number}";
        String formatStrChange = "{0,number},{1,number},{2,date}";
        MessageFormat msg = new MessageFormat(formatStr);
        MessageFormat msgCmp = new MessageFormat(formatStr);
        Format[] formats = msg.getFormats();
        Format[] formatsCmp = msgCmp.getFormats();
        Format[] formatsChg = null;
        Format[] formatsAct = null;
        Format a = null;
        Format b = null;
        Format[] formatsToAdopt = null;

        if (formats==null || formatsCmp==null || (formats.length <= 0) || (formats.length != formatsCmp.length)) {
            errln("Error getting Formats");
            return;
        }

        int i;

        for (i = 0; i < formats.length; i++) {
            a = formats[i];
            b = formatsCmp[i];
            if ((a != null) && (b != null)) {
                if (!a.equals(b)) {
                    errln("a != b");
                    return;
                }
            } else if ((a != null) || (b != null)) {
                errln("(a != null) || (b != null)");
                return;
            }
        }

        msg.applyPattern( formatStrChange ); //set msg formats to something different
        formatsChg = msg.getFormats(); // tested function
        if (formatsChg==null || (formatsChg.length != formats.length)) {
            errln("Error getting Formats");
            return;
        }

        boolean diff;
        diff = true;
        for (i = 0; i < formats.length; i++) {
            a = formatsChg[i];
            b = formatsCmp[i];
            if ((a != null) && (b != null)) {
                if (a.equals(b)) {
                    logln("formatsChg == formatsCmp at index " + i);
                    diff = false;
                }
            }
        }
        if (!diff) {
            errln("*** MSG getFormats diff err.");
            return;
        }

        logln("MSG getFormats tested.");

        msg.setFormats( formatsCmp ); //tested function

        formatsAct = msg.getFormats();
        if (formatsAct==null || (formatsAct.length <=0) || (formatsAct.length != formatsCmp.length)) {
            errln("Error getting Formats");
            return;
        }

        assertEquals("msgCmp.toPattern()", formatStr, msgCmp.toPattern());
        // ICU 4.8 does not support toPattern() when there are custom formats (from setFormat() etc.).
        // assertEquals("msg.toPattern()", formatStr, msg.toPattern());
        try {
            msg.toPattern();
            errln("msg.setFormat().toPattern() does not throw an IllegalStateException");
        } catch(IllegalStateException e) {
            // ok
        }

        for (i = 0; i < formatsAct.length; i++) {
            a = formatsAct[i];
            b = formatsCmp[i];
            if ((a != null) && (b != null)) {
                if (!a.equals(b)) {
                    errln("formatsAct != formatsCmp at index " + i);
                    return;
                }
            } else if ((a != null) || (b != null)) {
                errln("(a != null) || (b != null)");
                return;
            }
        }
        logln("MSG setFormats tested.");

        //----

        msg.applyPattern( formatStrChange ); //set msg formats to something different

        formatsToAdopt = new Format[formatsCmp.length];
        if (formatsToAdopt==null) {
            errln("memory allocation error");
            return;
        }

        for (i = 0; i < formatsCmp.length; i++) {
            if (formatsCmp[i] == null) {
                formatsToAdopt[i] = null;
            } else {
                formatsToAdopt[i] = (Format) formatsCmp[i].clone();
                if (formatsToAdopt[i]==null) {
                    errln("Can't clone format at index " + i);
                    return;
                }
            }
        }
        msg.setFormats( formatsToAdopt ); // function to test

        assertEquals("msgCmp.toPattern()", formatStr, msgCmp.toPattern());
        // ICU 4.8 does not support toPattern() when there are custom formats (from setFormat() etc.).
        // assertEquals("msg.toPattern()", formatStr, msg.toPattern());

        formatsAct = msg.getFormats();
        if (formatsAct==null || (formatsAct.length <=0) || (formatsAct.length != formatsCmp.length)) {
            errln("Error getting Formats");
            return;
        }

        for (i = 0; i < formatsAct.length; i++) {
            a = formatsAct[i];
            b = formatsCmp[i];
            if ((a != null) && (b != null)) {
                if (!a.equals(b)) {
                    errln("a != b");
                    return;
                }
            } else if ((a != null) || (b != null)) {
                errln("(a != null) || (b != null)");
                return;
            }
        }
        logln("MSG adoptFormats tested.");

        //---- adoptFormat

        msg.applyPattern( formatStrChange ); //set msg formats to something different

        formatsToAdopt = new Format[formatsCmp.length];
        if (formatsToAdopt==null) {
            errln("memory allocation error");
            return;
        }

        for (i = 0; i < formatsCmp.length; i++) {
            if (formatsCmp[i] == null) {
                formatsToAdopt[i] = null;
            } else {
                formatsToAdopt[i] = (Format) formatsCmp[i].clone();
                if (formatsToAdopt[i]==null) {
                    errln("Can't clone format at index " + i);
                    return;
                }
            }
        }

        for ( i = 0; i < formatsCmp.length; i++ ) {
            msg.setFormat( i, formatsToAdopt[i] ); // function to test
        }

        assertEquals("msgCmp.toPattern()", formatStr, msgCmp.toPattern());
        // ICU 4.8 does not support toPattern() when there are custom formats (from setFormat() etc.).
        // assertEquals("msg.toPattern()", formatStr, msg.toPattern());

        formatsAct = msg.getFormats();
        if (formatsAct==null || (formatsAct.length <=0) || (formatsAct.length != formatsCmp.length)) {
            errln("Error getting Formats");
            return;
        }

        for (i = 0; i < formatsAct.length; i++) {
            a = formatsAct[i];
            b = formatsCmp[i];
            if ((a != null) && (b != null)) {
                if (!a.equals(b)) {
                    errln("a != b");
                    return;
                }
            } else if ((a != null) || (b != null)) {
                errln("(a != null) || (b != null)");
                return;
            }
        }
        logln("MSG adoptFormat tested.");
    }

    /**
     * Verify that MessageFormat accomodates more than 10 arguments and
     * more than 10 subformats.
     */
    @Test
    public void TestUnlimitedArgsAndSubformats() {
        final String pattern =
            "On {0,date} (aka {0,date,short}, aka {0,date,long}) "+
            "at {0,time} (aka {0,time,short}, aka {0,time,long}) "+
            "there were {1,number} werjes "+
            "(a {3,number,percent} increase over {2,number}) "+
            "despite the {4}''s efforts "+
            "and to delight of {5}, {6}, {7}, {8}, {9}, and {10} {11}.";
        try {
            MessageFormat msg = new MessageFormat(pattern);

            final Object ARGS[] = {
                new Date(10000000000000L),
                new Integer(1303),
                new Integer(1202),
                new Double(1303.0/1202 - 1),
                "Glimmung",
                "the printers",
                "Nick",
                "his father",
                "his mother",
                "the spiddles",
                "of course",
                "Horace"
            };

            String expected =
                "On Nov 20, 2286 (aka 11/20/86, aka November 20, 2286) "+
                "at 9:46:40 AM (aka 9:46 AM, aka 9:46:40 AM PST) "+
                "there were 1,303 werjes "+
                "(a 8% increase over 1,202) "+
                "despite the Glimmung's efforts "+
                "and to delight of the printers, Nick, his father, "+
                "his mother, the spiddles, and of course Horace.";
            assertEquals("format", expected, msg.format(ARGS));
        } catch (IllegalArgumentException e1) {
            errln("FAIL: constructor failed");
        }
    }

    // test RBNF extensions to message format
    @Test
    public void TestRBNF() {
        // WARNING: this depends on the RBNF formats for en_US
        Locale locale = Locale.US;
        String[] values = {
            // decimal values do not format completely for ordinal or duration, and
            // do not always parse, so do not include them
            "0", "1", "12", "100", "123", "1001", "123,456", "-17",
        };
        String[] formats = {
            "There are {0,spellout} files to search.",
            "There are {0,spellout,%simplified} files to search.",
            "The bogus spellout {0,spellout,%BOGUS} files behaves like the default.",
            "This is the {0,ordinal} file to search.", // TODO fix bug, ordinal does not parse
            "Searching this file will take {0,duration} to complete.",
            "Searching this file will take {0,duration,%with-words} to complete.",
        };
        final NumberFormat numFmt = NumberFormat.getInstance(locale);
        Object[] args = new Object[1];
        Number num = null;
        for (int i = 0; i < formats.length; ++i) {
            MessageFormat fmt = new MessageFormat(formats[i], locale);
            logln("Testing format pattern: '" + formats[i] + "'");
            for (int j = 0; j < values.length; ++j) {
                try {
                    num = numFmt.parse(values[j]);
                }
                catch (Exception e) {
                    throw new IllegalStateException("failed to parse test argument");
                }
                args[0] = num;
                String result = fmt.format(args);
                logln("value: " + num + " --> " + result);

                if (i != 3) { // TODO: fix this, for now skip ordinal parsing (format string at index 3)
                    try {
                        Object[] parsedArgs = fmt.parse(result);
                        if (parsedArgs.length != 1) {
                            errln("parse returned " + parsedArgs.length + " args");
                        } else if (!parsedArgs[0].equals(num)) {
                            errln("parsed argument " + parsedArgs[0] + " != " + num);
                        }
                    }
                    catch (ParseException e) {
                        errln("parse of '" + result + "' returned exception: "
                                + e.getMessage() + " " + e.getErrorOffset());
                    }
                }
            }
        }
    }

    @Test
    public void TestSetGetFormats()
    {
        Object arguments[] = {
            new Double(456.83),
            new Date(871068000000L),
            "deposit"
            };

        StringBuffer result = new StringBuffer();

        String formatStr = "At <time> on {1,date}, you made a {2} of {0,number,currency}.";
        // original expected format result
        String compareStr = "At <time> on Aug 8, 1997, you made a deposit of $456.83.";
        // the date being German-style, but the currency being English-style
        String compareStr2 = "At <time> on 08.08.1997, you made a deposit of ";
        compareStr2 += "\u00A4";
        compareStr2 += "456.83.";
        // both date and currency formats are German-style
        String compareStr3 = "At <time> on 08.08.1997, you made a deposit of ";
        compareStr3 += "456,83\u00a0";
        compareStr3 += "XXX";
        compareStr3 += ".";

        MessageFormat msg = new MessageFormat(formatStr, ULocale.US);
        result.setLength(0);
        FieldPosition pos = new FieldPosition(FieldPosition_DONT_CARE);
        result = msg.format(
            arguments,
            result,
            pos);
        assertEquals("format", compareStr, result.toString());

        // constructs a Format array with a English-style Currency formatter
        //                            and a German-style Date formatter
        //      might not meaningful, just for testing setFormatsByArgIndex
        Format[] fmts = new Format[] {
            NumberFormat.getCurrencyInstance(ULocale.ENGLISH),
            DateFormat.getDateInstance(DateFormat.DEFAULT, ULocale.GERMAN)
            };

        msg.setFormatsByArgumentIndex(fmts);
        result.setLength(0);
        pos = new FieldPosition(FieldPosition_DONT_CARE);
        result = msg.format(
            arguments,
            result,
            pos);
        assertEquals("format", compareStr2, result.toString());

        // Construct a German-style Currency formatter, replace the corresponding one
        // Thus both formatters should format objects with German-style
        Format newFmt = NumberFormat.getCurrencyInstance(ULocale.GERMAN);
        msg.setFormatByArgumentIndex(0, newFmt);
        result.setLength(0);
        pos = new FieldPosition(FieldPosition_DONT_CARE);
        result = msg.format(
            arguments,
            result,
            pos);
        assertEquals("format", compareStr3, result.toString());

        // verify getFormatsByArgumentIndex
        //   you should got three formats by that
        //          - DecimalFormat     locale: de
        //          - SimpleDateFormat  locale: de
        //          - null
        Format[] fmts2 = msg.getFormatsByArgumentIndex();
        assertEquals("1st subformmater: Format Class", "com.ibm.icu.text.DecimalFormat", fmts2[0].getClass().getName());
        assertEquals("1st subformmater: its Locale", ULocale.GERMAN, ((UFormat)fmts2[0]).getLocale(ULocale.VALID_LOCALE));
        assertEquals("2nd subformatter: Format Class", "com.ibm.icu.text.SimpleDateFormat", fmts2[1].getClass().getName());
        assertEquals("2nd subformmater: its Locale", ULocale.GERMAN, ((UFormat)fmts2[1]).getLocale(ULocale.VALID_LOCALE));
        assertTrue("The third subFormatter is null", null == fmts2[2]);
    }

    // Test the fix pattern api
    @Test
    public void TestAutoQuoteApostrophe() {
        final String[] patterns = { // new pattern, expected pattern
            "'", "''",
            "''", "''",
            "'{", "'{'",
            "' {", "'' {",
            "'a", "''a",
            "'{'a", "'{'a",
            "'{a'", "'{a'",
            "'{}", "'{}'",
            "{'", "{'",
            "{'a", "{'a",
            "{'a{}'a}'a", "{'a{}'a}''a",
            "'}'", "'}'",
            "'} '{'}'", "'} '{'}''",
            "'} {{{''", "'} {{{'''",
        };
        for (int i = 0; i < patterns.length; i += 2) {
            assertEquals("[" + (i/2) + "] \"" + patterns[i] + "\"", patterns[i+1], MessageFormat.autoQuoteApostrophe(patterns[i]));
        }
    }

    // This tests passing named arguments instead of numbers to format().
    @Test
    public void testFormatNamedArguments() {
        Map arguments = new HashMap();
        arguments.put("startDate", new Date(871068000000L));

        StringBuffer result = new StringBuffer();

        String formatStr = "On {startDate,date}, it began.";
        String compareStr = "On Aug 8, 1997, it began.";

        MessageFormat msg = new MessageFormat(formatStr);
        FieldPosition fp = new FieldPosition(FieldPosition_DONT_CARE);

        try {
            msg.format(arguments.get("startDate"), result, fp);
            errln("*** MSG format without expected error code.");
        } catch (Exception e1) {
        }

        result.setLength(0);
        result = msg.format(
            arguments,
            result,
            fp);
        assertEquals("format", compareStr, result.toString());
    }

    // This tests parsing formatted messages with named arguments instead of
    // numbers.
    @Test
    public void testParseNamedArguments() {
        String msgFormatString = "{foo} =sep= {bar}";
        MessageFormat msg = new MessageFormat(msgFormatString);
        String source = "abc =sep= def";

        try {
            Map fmt_map = msg.parseToMap(source);
            if (fmt_map.keySet().size() != 2) {
                errln("*** MSG parse (ustring, count, err) count err.");
            } else {
                assertEquals("parse()[0]", "abc", fmt_map.get("foo"));
                assertEquals("parse()[1]", "def", fmt_map.get("bar"));
            }
        } catch (ParseException e1) {
            errln("*** MSG parse (ustring, count, err) error.");
        }

        ParsePosition pp = new ParsePosition(0);
        Map fmt_map = msg.parseToMap(source, pp);
        if (pp.getIndex()==0 || fmt_map==null) {
            errln("*** MSG parse (ustring, parsepos., count) error.");
        } else {
            if (fmt_map.keySet().size() != 2) {
                errln("*** MSG parse (ustring, parsepos., count) count err.");
            } else {
                assertEquals("parse()[0]", "abc", fmt_map.get("foo"));
                assertEquals("parse()[1]", "def", fmt_map.get("bar"));
            }
        }

        pp.setIndex(0);

        Map fmta = (Map) msg.parseObject( source, pp );
        if (pp.getIndex() == 0) {
            errln("*** MSG parse (ustring, Object, parsepos ) error.");
        } else {
            if (fmta.keySet().size() != 2) {
                errln("*** MSG parse (ustring, count, err) count err.");
            } else {
                assertEquals("parse()[0]", "abc", fmta.get("foo"));
                assertEquals("parse()[1]", "def", fmta.get("bar"));
            }
        }
    }

    // Ensure that methods designed for numeric arguments only, will throw
    // an exception when called on MessageFormat objects created with
    // named arguments.
    @Test
    public void testNumericOnlyMethods() {
        MessageFormat msg = new MessageFormat("Number of files: {numfiles}");
        boolean gotException = false;
        try {
            Format fmts[] = {new DecimalFormat()};
            msg.setFormatsByArgumentIndex(fmts);
        } catch (IllegalArgumentException e) {
            gotException = true;
        }
        if (!gotException) {
            errln("MessageFormat.setFormatsByArgumentIndex() should throw an " +
                  "IllegalArgumentException when called on formats with " +
                  "named arguments but did not!");
        }

        gotException = false;
        try {
            msg.setFormatByArgumentIndex(0, new DecimalFormat());
        } catch (IllegalArgumentException e) {
            gotException = true;
        }
        if (!gotException) {
            errln("MessageFormat.setFormatByArgumentIndex() should throw an " +
                  "IllegalArgumentException when called on formats with " +
                  "named arguments but did not!");
        }

        gotException = false;
        try {
            msg.getFormatsByArgumentIndex();
        } catch (IllegalArgumentException e) {
            gotException = true;
        }
        if (!gotException) {
            errln("MessageFormat.getFormatsByArgumentIndex() should throw an " +
                  "IllegalArgumentException when called on formats with " +
                  "named arguments but did not!");
        }

        gotException = false;
        try {
            Object args[] = {new Long(42)};
            msg.format(args, new StringBuffer(), new FieldPosition(FieldPosition_DONT_CARE));
        } catch (IllegalArgumentException e) {
            gotException = true;
        }
        if (!gotException) {
            errln("MessageFormat.format(Object[], StringBuffer, FieldPosition) " +
                  "should throw an IllegalArgumentException when called on " +
                  "formats with named arguments but did not!");
        }

        gotException = false;
        try {
            Object args[] = {new Long(42)};
            msg.format((Object) args, new StringBuffer(), new FieldPosition(FieldPosition_DONT_CARE));
        } catch (IllegalArgumentException e) {
            gotException = true;
        }
        if (!gotException) {
            errln("MessageFormat.format(Object, StringBuffer, FieldPosition) " +
                  "should throw an IllegalArgumentException when called with " +
                  "non-Map object as argument on formats with named " +
                  "arguments but did not!");
        }

        gotException = false;
        try {
            msg.parse("Number of files: 5", new ParsePosition(0));
        } catch (IllegalArgumentException e) {
            gotException = true;
        }
        if (!gotException) {
            errln("MessageFormat.parse(String, ParsePosition) " +
                  "should throw an IllegalArgumentException when called with " +
                  "non-Map object as argument on formats with named " +
                  "arguments but did not!");
        }

        gotException = false;
        try {
            msg.parse("Number of files: 5");
        } catch (IllegalArgumentException e) {
            gotException = true;
        } catch (ParseException e) {
            errln("Wrong exception thrown.");
        }
        if (!gotException) {
            errln("MessageFormat.parse(String) " +
                  "should throw an IllegalArgumentException when called with " +
                  "non-Map object as argument on formats with named " +
                  "arguments but did not!");
        }
    }

    @Test
    public void testNamedArguments() {
        // ICU 4.8 allows mixing named and numbered arguments.
        assertTrue(
                "has some named arguments",
                new MessageFormat("Number of files in folder {0}: {numfiles}").usesNamedArguments());
        assertTrue(
                "has some named arguments",
                new MessageFormat("Number of files in folder {folder}: {1}").usesNamedArguments());

        // Test named arguments.
        MessageFormat mf = new MessageFormat("Number of files in folder {folder}: {numfiles}");
        if (!mf.usesNamedArguments()) {
            errln("message format 1 should have used named arguments");
        }
        mf = new MessageFormat("Wavelength:  {\u028EValue\uFF14}");
        if (!mf.usesNamedArguments()) {
            errln("message format 2 should have used named arguments");
        }

        // Test argument names with invalid start characters.
        // Modified: ICU 4.8 allows all characters except for Pattern_White_Space and Pattern_Syntax.
        try {
            new MessageFormat("Wavelength:  {^\u028EValue\uFF14}");
            errln("Creating a MessageFormat with invalid argument names " +
            "should throw an IllegalArgumentException but did not!");
        } catch (IllegalArgumentException e) {}

        try {
            new MessageFormat("Wavelength:  {\uFE45\u028EValue}");
            errln("Creating a MessageFormat with invalid argument names " +
            "should throw an IllegalArgumentException but did not!");
        } catch (IllegalArgumentException e) {}

        // Test argument names with invalid continue characters.
        // Modified: ICU 4.8 allows all characters except for Pattern_White_Space and Pattern_Syntax.
        try {
            new MessageFormat("Wavelength:  {Value@\uFF14}");
            errln("Creating a MessageFormat with invalid argument names " +
            "should throw an IllegalArgumentException but did not!");
        } catch (IllegalArgumentException e) {}

        try {
            new MessageFormat("Wavelength:  {Value(\uFF14)}");
            errln("Creating a MessageFormat with invalid argument names " +
            "should throw an IllegalArgumentException but did not!");
        } catch (IllegalArgumentException e) {}
    }

    @Test
    public void testNumericFormatWithMap() {
        MessageFormat mf = new MessageFormat("X:{2} Y:{1}");
        if (mf.usesNamedArguments()) {
            errln("should not use named arguments");
        }

        Map map12 = new HashMap();
        map12.put("1", "one");
        map12.put("2", "two");

        String target = "X:two Y:one";
        String result = mf.format(map12);
        if (!target.equals(result)) {
            errln("expected '" + target + "' but got '" + result + "'");
        }

        try {
            Map mapResult = mf.parseToMap(target);
            if (!map12.equals(mapResult)) {
                errln("expected " + map12 + " but got " + mapResult);
            }
        } catch (ParseException e) {
            errln("unexpected exception: " + e.getMessage());
        }

        Map map10 = new HashMap();
        map10.put("1", "one");
        map10.put("0", "zero");
        target = "X:{2} Y:one";
        result = mf.format(map10);
        if (!target.equals(result)) {
            errln("expected '" + target + "' but got '" + result + "'");
        }

        DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
        DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.MEDIUM);
        Map fmtMap = new HashMap();
        fmtMap.put("1", dateFormat);
        fmtMap.put("2", timeFormat);
        mf.setFormatsByArgumentName(fmtMap);
        Date date = new Date(661439820000L);

        try {
            result = mf.format(map12); // should fail, wrong argument type
            fail("expected exception but got '" + result + "'");
        } catch (IllegalArgumentException e) {
            // expect this
        }

        Map argMap = new HashMap();
        argMap.put("1", date);
        argMap.put("2", date);
        target = "X:5:17:00 AM Y:Dec 17, 1990";
        result = mf.format(argMap);
        if (!target.equals(result)) {
            errln("expected '" + target + "' but got '" + result + "'");
        }
    }

    // This tests nested Formats inside PluralFormat.
    @Test
    public void testNestedFormatsInPluralFormat() {
        try {
            MessageFormat msgFmt = new MessageFormat(
                    "{0, plural, one {{0, number,C''est #,##0.0# fichier}} " +
                    "other {Ce sont # fichiers}} dans la liste.",
                    new ULocale("fr"));
            Object objArray[] = {new Long(0)};
            HashMap objMap = new HashMap();
            objMap.put("argument", objArray[0]);
            String result = msgFmt.format(objArray);
            if (!result.equals("C'est 0,0 fichier dans la liste.")) {
                errln("PluralFormat produced wrong message string.");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    // This tests PluralFormats used inside MessageFormats.
    @Test
    public void testPluralFormat() {
        {
            MessageFormat mfNum = new MessageFormat(
                    "{0, plural, one{C''est # fichier} other " +
                      "{Ce sont # fichiers}} dans la liste.",
                    new ULocale("fr"));
            MessageFormat mfAlpha = new MessageFormat(
                    "{argument, plural, one{C''est # fichier} other {Ce " +
                      "sont # fichiers}} dans la liste.",
                    new ULocale("fr"));
            Object objArray[] = {new Long(0)};
            HashMap objMap = new HashMap();
            objMap.put("argument", objArray[0]);
            String result = mfNum.format(objArray);
            if (!result.equals(mfAlpha.format(objMap))) {
                errln("PluralFormat's output differs when using named " +
                        "arguments instead of numbers!");
            }
            if (!result.equals("C'est 0 fichier dans la liste.")) {
                errln("PluralFormat produced wrong message string.");
            }
        }
        {
            MessageFormat mfNum = new MessageFormat (
                    "There {0, plural, one{is # zavod}few{are {0, " +
                      "number,###.0} zavoda} other{are # zavodov}} in the " +
                      "directory.",
                    new ULocale("uk"));
            MessageFormat mfAlpha = new MessageFormat (
                    "There {argument, plural, one{is # zavod}few{" +
                      "are {argument, number,###.0} zavoda} other{are # " +
                      "zavodov}} in the directory.",
                    new ULocale("uk"));
            Object objArray[] = {new Long(4)};
            HashMap objMap = new HashMap();
            objMap.put("argument", objArray[0]);
            String result = mfNum.format(objArray);
            if (!result.equals(mfAlpha.format(objMap))) {
                errln("PluralFormat's output differs when using named " +
                        "arguments instead of numbers!");
            }
            if (!result.equals("There are 4,0 zavoda in the directory.")) {
                errln("PluralFormat produced wrong message string.");
            }
        }
    }

    @Test
    public void testApostropheInPluralAndSelect() {
        MessageFormat fmt = new MessageFormat(
                "abc_{0,plural,other{#'#'#'{'#''}}_def_{1,select,other{sel'}'ect''}}_xyz",
                Locale.ENGLISH);
        String expected = "abc_3#3{3'_def_sel}ect'_xyz";
        String result = fmt.format(new Object[] { 3, "x" });
        if (!result.equals(expected)) {
            errln("MessageFormat with apostrophes in plural/select arguments failed:\n" +
                  "Expected "+expected+"\n" +
                  "Got      "+result);
        }
    }

  // Test toPattern when there is a PluralFormat
    @Test
  public void testPluralFormatToPattern() {
    String[] patterns = {
      "Beware of vicious {0, plural, one {hamster} other {hamsters}}.",
      "{0, plural, one {{0, number,C''''est #,##0.0# fichier}} other {Ce sont # fichiers}} dans la liste.",
      "{0, plural, one {C''est # fichier} other {Ce sont # fichiers}} dans la liste.",
    };

    for (int i = 0; i < patterns.length; ++i) {
      String pattern = patterns[i];
      MessageFormat mf = new MessageFormat(pattern);
      MessageFormat mf2 = new MessageFormat(mf.toPattern());
      if (!mf.equals(mf2)) {
        errln("message formats not equal for pattern:\n*** '" + pattern + "'\n*** '" +
              mf.toPattern() + "'");
      }
    }
  }

    /**
     * This tests SelectFormats used inside MessageFormats.
     */
    @Test
    public void testSelectFormat() {
        String pattern = null;
        MessageFormat msgFmt = null ;

        //Create the MessageFormat with simple French pattern
        pattern = "{0} est {1, select, female {all\\u00E9e} other {all\\u00E9}} \\u00E0 Paris.";
        msgFmt = new MessageFormat(pattern);
        assertNotNull( "ERROR:Failure in constructing with simple French pattern", msgFmt);

        //Format
        Object testArgs[][] ={
            {"Kirti","female"} ,
            {"Victor","other"} ,
            {"Ash","unknown"} ,
        };
        String exp[] = {
            "Kirti est all\\u00E9e \\u00E0 Paris." ,
            "Victor est all\\u00E9 \\u00E0 Paris.",
            "Ash est all\\u00E9 \\u00E0 Paris."
        };
        for ( int i=0; i< 3; i++){
            assertEquals("ERROR:Failure in format with simple French Pattern" ,
                      exp[i] , msgFmt.format(testArgs[i]) );
        }

        //Create the MessageFormat with Quoted French Pattern
        pattern = "{0} est {1, select, female {all\\u00E9e c''est} other {all\\u00E9 c''est}} \\u00E0 Paris.";
        msgFmt = new MessageFormat(pattern);
        assertNotNull( "ERROR:Failure in constructing with quoted French pattern", msgFmt);

        //Format
        Object testArgs1[][] ={
            {"Kirti","female"} ,
            {"Victor","other"} ,
            {"Ash","male"} ,
        };
        String exp1[] = {
            "Kirti est all\\u00E9e c'est \\u00E0 Paris." ,
            "Victor est all\\u00E9 c'est \\u00E0 Paris.",
            "Ash est all\\u00E9 c'est \\u00E0 Paris."
        };
        for ( int i=0; i< 3; i++){
            assertEquals("ERROR:Failure in format with quoted French Pattern" ,
                          exp1[i] , msgFmt.format(testArgs1[i]) );
        }

        //Nested patterns with plural, number ,choice ,select format etc.
        //Select Format with embedded number format
        pattern = "{0} est {1, select, female {{2,number,integer} all\\u00E9e} other {all\\u00E9}} \\u00E0 Paris.";
        msgFmt = new MessageFormat(pattern);
        assertNotNull( "ERROR:Failure in constructing with nested pattern 1", msgFmt);

        //Format
        Object testArgs3[][] ={
            {"Kirti", "female", 6} ,
            {"Kirti", "female", 100.100} ,
            {"Kirti", "other", 6} ,
        };
        String exp3[] = {
            "Kirti est 6 all\\u00E9e \\u00E0 Paris." ,
            "Kirti est 100 all\\u00E9e \\u00E0 Paris.",
            "Kirti est all\\u00E9 \\u00E0 Paris."
        };

        for ( int i=0; i< 3; i++){
            assertEquals("ERROR:Failure in format with nested Pattern 1" ,
                          exp3[i] , msgFmt.format(testArgs3[i]) );
        }

        //Plural format with embedded select format
        pattern = "{0} {1, plural, one {est {2, select, female {all\\u00E9e} other {all\\u00E9}}} other {sont {2, select, female {all\\u00E9es} other {all\\u00E9s}}}} \\u00E0 Paris.";
        msgFmt = new MessageFormat(pattern);
        assertNotNull( "ERROR:Failure in constructing with nested pattern 2", msgFmt);

        //Format
        Object testArgs4[][] ={
            {"Kirti",6,"female"},
            {"Kirti",1,"female"},
            {"Ash",1,"other"},
            {"Ash",5,"other"},
        };
        String exp4[] = {
            "Kirti sont all\\u00E9es \\u00E0 Paris." ,
            "Kirti est all\\u00E9e \\u00E0 Paris.",
            "Ash est all\\u00E9 \\u00E0 Paris.",
            "Ash sont all\\u00E9s \\u00E0 Paris."
        };
        for ( int i=0; i< 4; i++){
            assertEquals("ERROR:Failure in format with nested Pattern 2" ,
                          exp4[i] , msgFmt.format(testArgs4[i]) );
        }

        //Select, plural, and number formats heavily nested
        pattern = "{0} und {1, select, female {{2, plural, one {{3, select, female {ihre Freundin} other {ihr Freund}} } other {ihre {2, number, integer} {3, select, female {Freundinnen} other {Freunde}} } }} other{{2, plural, one {{3, select, female {seine Freundin} other {sein Freund}}} other {seine {2, number, integer} {3, select, female {Freundinnen} other {Freunde}}}}} } gingen nach Paris.";
        msgFmt = new MessageFormat(pattern);
        assertNotNull( "ERROR:Failure in constructing with nested pattern 3", msgFmt);

        //Format
        Object testArgs5[][] ={
            {"Kirti","other",1,"other"},
            {"Kirti","other",6,"other"},
            {"Kirti","other",1,"female"},
            {"Kirti","other",3,"female"},
            {"Kirti","female",1,"female"},
            {"Kirti","female",5,"female"},
            {"Kirti","female",1,"other"},
            {"Kirti","female",5,"other"},
            {"Kirti","mixed",1,"mixed"},
            {"Kirti","mixed",1,"other"},
            {"Kirti","female",1,"mixed"},
            {"Kirti","mixed",5,"mixed"},
            {"Kirti","mixed",5,"other"},
            {"Kirti","female",5,"mixed"},
        };
        String exp5[] = {
            "Kirti und sein Freund gingen nach Paris." ,
            "Kirti und seine 6 Freunde gingen nach Paris." ,
            "Kirti und seine Freundin gingen nach Paris.",
            "Kirti und seine 3 Freundinnen gingen nach Paris.",
            "Kirti und ihre Freundin  gingen nach Paris.",
            "Kirti und ihre 5 Freundinnen  gingen nach Paris.",
            "Kirti und ihr Freund  gingen nach Paris.",
            "Kirti und ihre 5 Freunde  gingen nach Paris.",
            "Kirti und sein Freund gingen nach Paris.",
            "Kirti und sein Freund gingen nach Paris.",
            "Kirti und ihr Freund  gingen nach Paris.",
            "Kirti und seine 5 Freunde gingen nach Paris." ,
            "Kirti und seine 5 Freunde gingen nach Paris." ,
            "Kirti und ihre 5 Freunde  gingen nach Paris."
        };
        //Format
        for ( int i=0; i< 14; i++){
            assertEquals("ERROR:Failure in format with nested Pattern 3" ,
                          exp5[i] , msgFmt.format(testArgs5[i]) );
        }
    }

    /**
     * Test toPattern when there is a SelectFormat
     */
    @Test
    public void testSelectFormatToPattern() {
        String[] patterns = {
          //Pattern with some text at start and at end
          "{0} est {1,select, female {all\\u00E9e} other {all\\u00E9}} \\u00E0 Paris.",
          //Pattern with some text at start
          "{0} est {1,select, female {all\\u00E9e} other {all\\u00E9}}",
          //Pattern with some text at end
          "{1, select,female {all\\u00E9e} other {all\\u00E9}} \\u00E0 Paris.",
          //Pattern with no text at any  end
          "{1, select,female {all\\u00E9e} other {all\\u00E9}}.",
          //Quoted French pattern
          "{0} est {1,select, female {all\\u00E9e c''est} other {all\\u00E9 c''est}} \\u00E0 Paris.",
        };

        for (int i = 0; i < patterns.length; ++i) {
            String pattern = patterns[i];
            MessageFormat mf = new MessageFormat(pattern);
            MessageFormat mf2 = new MessageFormat(mf.toPattern());
            if (!mf.equals(mf2)) {
                errln("message formats not equal for pattern:\n*** '"
                     + pattern + "'\n*** '" + mf.toPattern() + "'");
            }
        }
    }

    // Test case for null arguments.
    // Ticket#6361
    @Test
    public void TestNullArgs() {
        MessageFormat msgfmt = new MessageFormat("{0} - {1}");
        Object[][] TEST_CASES = {
            {null,                          "{0} - {1}"},
            {new Object[] {null},           "null - {1}"},
            {new Object[] {null, null},     "null - null"},
            {new Object[] {"one"},          "one - {1}"},
            {new Object[] {"one", null},    "one - null"},
            {new Object[] {null, "two"},    "null - two"},
        };

        for (int i = 0; i < TEST_CASES.length; i++) {
            String text = msgfmt.format(TEST_CASES[i][0]);
            if (!text.equals(TEST_CASES[i][1])) {
                errln("FAIL: Returned[" + text + "] Expected[" + TEST_CASES[i][1] + "]");
            }
        }
    }

    @Test
    public void TestSetFormat() {
        MessageFormat ms = new MessageFormat("{number} {date}", ULocale.ENGLISH);
        final DecimalFormat decimalFormat = new DecimalFormat("000.000", DecimalFormatSymbols.getInstance(ULocale.ENGLISH));
        ms.setFormatByArgumentName("number", decimalFormat);
        final SimpleDateFormat dateFormat = new SimpleDateFormat("'year:'yy 'month:'MM 'day:'dd");
        dateFormat.setTimeZone(TimeZone.getTimeZone("Etc/GMT"));
        ms.setFormatByArgumentName("date", dateFormat);
        Map map = new HashMap();
        map.put("number", new Integer(1234));
        map.put("date", new Date(0,0,0));
        String result = ms.format(map);
        assertEquals("setFormatByArgumentName", "1234.000 year:99 month:12 day:31", result);
        Set formatNames = ms.getArgumentNames();
        assertEquals("Format Names match", formatNames, map.keySet());
        assertEquals("Decimal", decimalFormat, ms.getFormatByArgumentName("number"));
        assertEquals("Date", dateFormat, ms.getFormatByArgumentName("date"));
    }

    // Test case for formatToCharacterIterator
    @Test
    public void TestFormatToCharacterIterator() {
        MessageFormat[] msgfmts = {
                new MessageFormat(
                        "The {3,ordinal} folder ''{0}'' contains {2,number} file(s), created at {1,time} on {1,date}."),
                new MessageFormat(
                        "The {arg3,ordinal} folder ''{arg0}'' contains {arg2,number} file(s), created at {arg1,time} on {arg1,date}."), // same
                                                                                                                                        // as
                                                                                                                                        // above,
                                                                                                                                        // but
                                                                                                                                        // named
                                                                                                                                        // args
                new MessageFormat("The folder contains {0}.") };

        double filelimits[] = { 0, 1, 2 };
        String filepart[] = { "no files", "one file", "{0,number} files" };
        ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
        msgfmts[2].setFormat(0, fileform);

        Object[] args0 = new Object[] { "tmp", new Date(1184777888000L), new Integer(15), new Integer(2) };

        HashMap args1 = new HashMap();
        args1.put("arg0", "tmp");
        args1.put("arg1", new Date(1184777888000L));
        args1.put("arg2", new Integer(15));
        args1.put("arg3", new Integer(2));

        Object[] args2 = new Object[] { new Integer(34) };

        Object[] args = { args0, args1, args2 };

        String[] expectedStrings = {
                "The 2nd folder 'tmp' contains 15 file(s), created at 9:58:08 AM on Jul 18, 2007.",
                "The 2nd folder 'tmp' contains 15 file(s), created at 9:58:08 AM on Jul 18, 2007.",
                "The folder contains 34 files." };

        AttributedString[] expectedAttributedStrings = { new AttributedString(expectedStrings[0]),
                new AttributedString(expectedStrings[1]), new AttributedString(expectedStrings[2]) };

        // Add expected attributes to the expectedAttributedStrings[0]
        expectedAttributedStrings[0].addAttribute(MessageFormat.Field.ARGUMENT, new Integer(3), 4, 7);
        expectedAttributedStrings[0].addAttribute(MessageFormat.Field.ARGUMENT, new Integer(0), 16, 19);
        expectedAttributedStrings[0].addAttribute(MessageFormat.Field.ARGUMENT, new Integer(2), 30, 32);
        expectedAttributedStrings[0].addAttribute(NumberFormat.Field.INTEGER, NumberFormat.Field.INTEGER, 30, 32);
        expectedAttributedStrings[0].addAttribute(MessageFormat.Field.ARGUMENT, new Integer(1), 53, 63);
        expectedAttributedStrings[0].addAttribute(DateFormat.Field.HOUR1, DateFormat.Field.HOUR1, 53, 54);
        //expectedAttributedStrings[0].addAttribute(DateFormat.Field.TIME_SEPARATOR, DateFormat.Field.TIME_SEPARATOR, 54, 55);
        expectedAttributedStrings[0].addAttribute(DateFormat.Field.MINUTE, DateFormat.Field.MINUTE, 55, 57);
        //expectedAttributedStrings[0].addAttribute(DateFormat.Field.TIME_SEPARATOR, DateFormat.Field.TIME_SEPARATOR, 57, 58);
        expectedAttributedStrings[0].addAttribute(DateFormat.Field.SECOND, DateFormat.Field.SECOND, 58, 60);
        expectedAttributedStrings[0].addAttribute(DateFormat.Field.AM_PM, DateFormat.Field.AM_PM, 61, 63);
        expectedAttributedStrings[0].addAttribute(MessageFormat.Field.ARGUMENT, new Integer(1), 67, 79);
        expectedAttributedStrings[0].addAttribute(DateFormat.Field.MONTH, DateFormat.Field.MONTH, 67, 70);
        expectedAttributedStrings[0].addAttribute(DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.DAY_OF_MONTH, 71, 73);
        expectedAttributedStrings[0].addAttribute(DateFormat.Field.YEAR, DateFormat.Field.YEAR, 75, 79);

        // Add expected attributes to the expectedAttributedStrings[1]
        expectedAttributedStrings[1].addAttribute(MessageFormat.Field.ARGUMENT, "arg3", 4, 7);
        expectedAttributedStrings[1].addAttribute(MessageFormat.Field.ARGUMENT, "arg0", 16, 19);
        expectedAttributedStrings[1].addAttribute(MessageFormat.Field.ARGUMENT, "arg2", 30, 32);
        expectedAttributedStrings[1].addAttribute(NumberFormat.Field.INTEGER, NumberFormat.Field.INTEGER, 30, 32);
        expectedAttributedStrings[1].addAttribute(MessageFormat.Field.ARGUMENT, "arg1", 53, 63);
        expectedAttributedStrings[1].addAttribute(DateFormat.Field.HOUR1, DateFormat.Field.HOUR1, 53, 54);
        //expectedAttributedStrings[1].addAttribute(DateFormat.Field.TIME_SEPARATOR, DateFormat.Field.TIME_SEPARATOR, 54, 55);
        expectedAttributedStrings[1].addAttribute(DateFormat.Field.MINUTE, DateFormat.Field.MINUTE, 55, 57);
        //expectedAttributedStrings[1].addAttribute(DateFormat.Field.TIME_SEPARATOR, DateFormat.Field.TIME_SEPARATOR, 57, 58);
        expectedAttributedStrings[1].addAttribute(DateFormat.Field.SECOND, DateFormat.Field.SECOND, 58, 60);
        expectedAttributedStrings[1].addAttribute(DateFormat.Field.AM_PM, DateFormat.Field.AM_PM, 61, 63);
        expectedAttributedStrings[1].addAttribute(MessageFormat.Field.ARGUMENT, "arg1", 67, 79);
        expectedAttributedStrings[1].addAttribute(DateFormat.Field.MONTH, DateFormat.Field.MONTH, 67, 70);
        expectedAttributedStrings[1].addAttribute(DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.DAY_OF_MONTH, 71, 73);
        expectedAttributedStrings[1].addAttribute(DateFormat.Field.YEAR, DateFormat.Field.YEAR, 75, 79);

        // Add expected attributes to the expectedAttributedStrings[2]
        expectedAttributedStrings[2].addAttribute(MessageFormat.Field.ARGUMENT, new Integer(0), 20, 28);
        expectedAttributedStrings[2].addAttribute(NumberFormat.Field.INTEGER, NumberFormat.Field.INTEGER, 20, 22);

        for (int i = 0; i < msgfmts.length; i++) {
            AttributedCharacterIterator acit = msgfmts[i].formatToCharacterIterator(args[i]);
            AttributedCharacterIterator expectedAcit = expectedAttributedStrings[i].getIterator();

            // Check available attributes
            Set attrSet = acit.getAllAttributeKeys();
            Set expectedAttrSet = expectedAcit.getAllAttributeKeys();
            if (attrSet.size() != expectedAttrSet.size()) {
                errln("FAIL: Number of attribute keys is " + attrSet.size() + " expected: " + expectedAttrSet.size());
            }
            Iterator attrIterator = attrSet.iterator();
            while (attrIterator.hasNext()) {
                AttributedCharacterIterator.Attribute attr = (AttributedCharacterIterator.Attribute) attrIterator
                        .next();
                if (!expectedAttrSet.contains(attr)) {
                    errln("FAIL: The attribute " + attr + " is not expected.");
                }
            }

            StringBuffer buf = new StringBuffer();
            int index = acit.getBeginIndex();
            int end = acit.getEndIndex();
            int indexExp = expectedAcit.getBeginIndex();
            int expectedLen = expectedAcit.getEndIndex() - indexExp;
            if (end - index != expectedLen) {
                errln("FAIL: Length of the result attributed string is " + (end - index) + " expected: " + expectedLen);
            } else {
                // Check attributes associated with each character
                while (index < end) {
                    char c = acit.setIndex(index);
                    buf.append(c);
                    expectedAcit.setIndex(indexExp);

                    Map attrs = acit.getAttributes();
                    Map attrsExp = expectedAcit.getAttributes();
                    if (attrs.size() != attrsExp.size()) {
                        errln("FAIL: Number of attributes associated with index " + index + " is " + attrs.size()
                                + " expected: " + attrsExp.size());
                    } else {
                        // Check all attributes at the index
                        Iterator entryIterator = attrsExp.entrySet().iterator();
                        while (entryIterator.hasNext()) {
                            Map.Entry entry = (Map.Entry) entryIterator.next();
                            if (attrs.containsKey(entry.getKey())) {
                                Object value = attrs.get(entry.getKey());
                                assertEquals("Attribute value at index " + index, entry.getValue(), value);
                            } else {
                                errln("FAIL: Attribute " + entry.getKey() + " is missing at index " + index);
                            }
                        }
                    }
                    index++;
                    indexExp++;
                }
                assertEquals("AttributedString contents", expectedStrings[i], buf.toString());
            }
        }

        // Tests when "if (arguments == null)" is true
        try {
            MessageFormat mf = new MessageFormat("");
            mf.formatToCharacterIterator(null);
            errln("MessageFormat.formatToCharacterIterator(Object) was suppose "
                    + "to return an exception when null is passed.");
        } catch (Exception e) {
        }
    }

    /*
     * Tests the method public Format getFormatByArgumentName(String argumentName)
     */
    @Test
    public void TestGetFormatByArgumentName() {
        MessageFormat mf = new MessageFormat("");
        if (mf.getFormatByArgumentName("") != null) {
            errln("MessageFormat.getFormatByArgumentName(String) was suppose "
                    + "to return an null if argumentName was not found.");
        }
    }

    public String getPatternAndSkipSyntax(MessagePattern pattern) {
        StringBuilder sb = new StringBuilder(pattern.getPatternString());
        int count = pattern.countParts();
        for (int i = count; i > 0;) {
            MessagePattern.Part part = pattern.getPart(--i);
            if (part.getType() == MessagePattern.Part.Type.SKIP_SYNTAX) {
                sb.delete(part.getIndex(), part.getLimit());
            }
        }
        return sb.toString();
    }

    @Test
    public void TestApostropheMode() {
        MessagePattern ado_mp = new MessagePattern(MessagePattern.ApostropheMode.DOUBLE_OPTIONAL);
        MessagePattern adr_mp = new MessagePattern(MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
        assertEquals("wrong value",
                MessagePattern.ApostropheMode.DOUBLE_OPTIONAL,
                ado_mp.getApostropheMode());
        assertEquals("wrong value",
                MessagePattern.ApostropheMode.DOUBLE_REQUIRED,
                adr_mp.getApostropheMode());
        assertNotEquals("MessagePatterns with different ApostropheMode (no pattern)", ado_mp, adr_mp);
        assertNotEquals("MessagePatterns with different ApostropheMode (a)",
                ado_mp.parse("a"), adr_mp.parse("a"));

        String[] tuples = new String[] {
            // Desired output
            // DOUBLE_OPTIONAL pattern
            // DOUBLE_REQUIRED pattern (null=same as DOUBLE_OPTIONAL)
            "I see {many}", "I see '{many}'", null,
            "I said {'Wow!'}", "I said '{''Wow!''}'", null,
            "I dont know", "I dont know", "I don't know",
            "I don't know", "I don't know", "I don''t know",
            "I don't know", "I don''t know", "I don''t know",
        };
        for (int i = 0; i < tuples.length; i += 3) {
            String desired = tuples[i];
            String ado_pattern = tuples[i + 1];
            assertEquals("DOUBLE_OPTIONAL failure", desired,
                    getPatternAndSkipSyntax(ado_mp.parse(ado_pattern)));
            String adr_pattern = tuples[i + 2];
            if (adr_pattern == null) {
                adr_pattern = ado_pattern;
            }
            assertEquals("DOUBLE_REQUIRED failure", desired,
                    getPatternAndSkipSyntax(adr_mp.parse(adr_pattern)));
        }
    }

    // Compare behavior of JDK and ICU's DOUBLE_REQUIRED compatibility mode.
    @Test
    public void TestCompatibleApostrophe() {
        // Message with choice argument which does not contain another argument.
        // The JDK performs only one apostrophe-quoting pass on this pattern.
        String pattern = "ab{0,choice,0#1'2''3'''4''''.}yz";
        java.text.MessageFormat jdkMsg =
            new java.text.MessageFormat(pattern, Locale.ENGLISH);

        MessageFormat compMsg = new MessageFormat("", Locale.ENGLISH);
        compMsg.applyPattern(pattern, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
        assertEquals("wrong value",
                MessagePattern.ApostropheMode.DOUBLE_REQUIRED,
                compMsg.getApostropheMode());

        MessageFormat icuMsg = new MessageFormat("", Locale.ENGLISH);
        icuMsg.applyPattern(pattern, MessagePattern.ApostropheMode.DOUBLE_OPTIONAL);
        assertEquals("wrong value",
                MessagePattern.ApostropheMode.DOUBLE_OPTIONAL,
                icuMsg.getApostropheMode());

        Object[] zero0 = new Object[] { 0 };
        assertEquals("unexpected JDK MessageFormat apostrophe behavior",
                "ab12'3'4''.yz",
                jdkMsg.format(zero0));
        assertEquals("incompatible ICU MessageFormat compatibility-apostrophe behavior",
                "ab12'3'4''.yz",
                compMsg.format(zero0));
        assertEquals("unexpected ICU MessageFormat double-apostrophe-optional behavior",
                "ab1'2'3''4''.yz",
                icuMsg.format(zero0));

        // Message with choice argument which contains a nested simple argument.
        // The JDK performs two apostrophe-quoting passes.
        pattern = "ab{0,choice,0#1'2''3'''4''''.{0,number,'#x'}}yz";
        jdkMsg.applyPattern(pattern);
        compMsg.applyPattern(pattern);
        icuMsg.applyPattern(pattern);
        assertEquals("unexpected JDK MessageFormat apostrophe behavior",
                "ab1234'.0xyz",
                jdkMsg.format(zero0));
        assertEquals("incompatible ICU MessageFormat compatibility-apostrophe behavior",
                "ab1234'.0xyz",
                compMsg.format(zero0));
        assertEquals("unexpected ICU MessageFormat double-apostrophe-optional behavior",
                "ab1'2'3''4''.#x0yz",
                icuMsg.format(zero0));

        // Message with choice argument which contains a nested choice argument.
        // The JDK fails to parse this pattern.
        // jdkMsg.applyPattern("cd{0,choice,0#ef{0,choice,0#1'2''3'''4''''.}uv}wx");
        // For lack of comparison, we do not test ICU with this pattern.

        // The JDK ChoiceFormat itself always performs one apostrophe-quoting pass.
        ChoiceFormat choice = new ChoiceFormat("0#1'2''3'''4''''.");
        assertEquals("unexpected JDK ChoiceFormat apostrophe behavior",
                "12'3'4''.",
                choice.format(0));
        choice.applyPattern("0#1'2''3'''4''''.{0,number,'#x'}");
        assertEquals("unexpected JDK ChoiceFormat apostrophe behavior",
                "12'3'4''.{0,number,#x}",
                choice.format(0));
    }

    @Test
    public void TestTrimArgumentName() {
        // ICU 4.8 allows and ignores white space around argument names and numbers.
        MessageFormat m = new MessageFormat("a { 0 , number , '#,#'#.0 } z", Locale.ENGLISH);
        assertEquals("trim-numbered-arg format() failed", "a  #,#2.0  z", m.format(new Object[] { 2 }));

        m.applyPattern("x { _oOo_ , number , integer } y");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("_oOo_", new Integer(3));
        StringBuffer result = new StringBuffer();
        assertEquals("trim-named-arg format() failed", "x 3 y",
                     m.format(map, result, new FieldPosition(FieldPosition_DONT_CARE)).toString());
    }

    @Test
    public void TestSelectOrdinal() {
        // Test plural & ordinal together,
        // to make sure that we get the correct cached PluralSelector for each.
        MessageFormat m = new MessageFormat(
            "{0,plural,one{1 file}other{# files}}, " +
            "{0,selectordinal,one{#st file}two{#nd file}few{#rd file}other{#th file}}",
            ULocale.ENGLISH);
        Object[] args = new Object[] { 21 };
        FieldPosition ignore = null;
        StringBuffer result = new StringBuffer();
        assertEquals("plural-and-ordinal format(21)", "21 files, 21st file",
                     m.format(args, result, ignore).toString());

        args[0] = 2;
        result.delete(0, result.length());
        assertEquals("plural-and-ordinal format(2) failed", "2 files, 2nd file",
                     m.format(args, result, ignore).toString());

        args[0] = 1;
        result.delete(0, result.length());
        assertEquals("plural-and-ordinal format(1) failed", "1 file, 1st file",
                     m.format(args, result, ignore).toString());

        args[0] = 3;
        result.delete(0, result.length());
        assertEquals("plural-and-ordinal format(3) failed", "3 files, 3rd file",
                     m.format(args, result, ignore).toString());
    }

    @Test
    public void TestDecimals() {
        // Simple number replacement.
        MessageFormat m = new MessageFormat(
                "{0,plural,one{one meter}other{# meters}}",
                ULocale.ENGLISH);
        Object[] args = new Object[] { 1 };
        FieldPosition ignore = null;
        StringBuffer result = new StringBuffer();
        assertEquals("simple format(1)", "one meter",
                m.format(args, result, ignore).toString());

        args[0] = 1.5;
        result.delete(0, result.length());
        assertEquals("simple format(1.5)", "1.5 meters",
                m.format(args, result, ignore).toString());

        // Simple but explicit.
        MessageFormat m0 = new MessageFormat(
                "{0,plural,one{one meter}other{{0} meters}}",
                ULocale.ENGLISH);
        args[0] = 1;
        result.delete(0, result.length());
        assertEquals("explicit format(1)", "one meter",
                m0.format(args, result, ignore).toString());

        args[0] = 1.5;
        result.delete(0, result.length());
        assertEquals("explicit format(1.5)", "1.5 meters",
                m0.format(args, result, ignore).toString());

        // With offset and specific simple format with optional decimals.
        MessageFormat m1 = new MessageFormat(
                "{0,plural,offset:1 one{another meter}other{{0,number,00.#} meters}}",
                ULocale.ENGLISH);
        args[0] = 1;
        result.delete(0, result.length());
        assertEquals("offset format(1)", "01 meters",
                m1.format(args, result, ignore).toString());

        args[0] = 2;
        result.delete(0, result.length());
        assertEquals("offset format(1)", "another meter",
                m1.format(args, result, ignore).toString());

        args[0] = 2.5;
        result.delete(0, result.length());
        assertEquals("offset format(1)", "02.5 meters",
                m1.format(args, result, ignore).toString());

        // With offset and specific simple format with forced decimals.
        MessageFormat m2 = new MessageFormat(
                "{0,plural,offset:1 one{another meter}other{{0,number,0.0} meters}}",
                ULocale.ENGLISH);
        args[0] = 1;
        result.delete(0, result.length());
        assertEquals("offset-decimals format(1)", "1.0 meters",
                m2.format(args, result, ignore).toString());

        args[0] = 2;
        result.delete(0, result.length());
        assertEquals("offset-decimals format(1)", "2.0 meters",
                m2.format(args, result, ignore).toString());

        args[0] = 2.5;
        result.delete(0, result.length());
        assertEquals("offset-decimals format(1)", "2.5 meters",
                m2.format(args, result, ignore).toString());
    }

    @Test
    public void TestArgIsPrefixOfAnother() {
        // Ticket #11952
        MessageFormat mf1 = new MessageFormat(
                "{0,select,a{A}ab{AB}abc{ABC}other{?}}", ULocale.ENGLISH);
        assertEquals("a", "A", mf1.format(new Object[] { "a" }));
        assertEquals("ab", "AB", mf1.format(new Object[] { "ab" }));
        assertEquals("abc", "ABC", mf1.format(new Object[] { "abc" }));

        // Ticket #12172
        MessageFormat mf2 = new MessageFormat("{a} {aa} {aaa}", ULocale.ENGLISH);
        Map<String, Object> args = new TreeMap<String, Object>();
        args.put("a", "A");
        args.put("aa", "AB");
        args.put("aaa", "ABC");
        assertEquals("a aa aaa", "A AB ABC", mf2.format(args, new StringBuffer(), null).toString());

        // Ticket #12172
        MessageFormat mf3 = new MessageFormat("{aa} {aaa}", ULocale.ENGLISH);
        assertEquals("aa aaa", "AB ABC", mf3.format(args, new StringBuffer(), null).toString());
    }

    @Test
    public void TestMessagePatternAutoQuoteApostropheDeep() {
        // Example input & output taken from API docs.
        MessagePattern pattern = new MessagePattern(
                "I don't '{know}' {gender,select,female{h''er}other{h'im}}.");
        assertEquals("autoQuoteApostropheDeep()",
                "I don''t '{know}' {gender,select,female{h''er}other{h''im}}.",
                pattern.autoQuoteApostropheDeep());
    }

    @Test
    public void TestMessagePatternFreezable() {
        MessagePattern pattern = new MessagePattern();
        assertFalse("just constructed, not yet frozen", pattern.isFrozen());
        pattern.parse("fee");
        assertTrue("parsed, not empty", pattern.countParts() > 0);
        pattern.freeze();
        assertTrue("just frozen", pattern.isFrozen());
        try {
            pattern.parse("fi");
            fail("MessagePattern.freeze().parse() did not fail");
        } catch (Exception expected) {
        }
        assertEquals("frozen+parse: no change", "fee", pattern.autoQuoteApostropheDeep());
        MessagePattern thawed = pattern.cloneAsThawed();
        assertFalse("thawed", thawed.isFrozen());
        assertTrue("still frozen", pattern.isFrozen());
        assertTrue("frozen!=thawed", pattern != thawed);
        thawed.parse("fo");
        assertEquals("thawed+parse", "fo", thawed.autoQuoteApostropheDeep());
    }

    @Test
    public void TestMessagePatternNamedAndNumberedArguments() {
        MessagePattern pattern = new MessagePattern();
        pattern.parse("fee");
        assertFalse("fee no named args", pattern.hasNamedArguments());
        assertFalse("fee no numbered args", pattern.hasNumberedArguments());
        pattern.parse("fi {0}");
        assertFalse("fi {0} no named args", pattern.hasNamedArguments());
        assertTrue("fi {0} has numbered args", pattern.hasNumberedArguments());
        pattern.parse("fo {name}");
        assertTrue("fo {name} has named args", pattern.hasNamedArguments());
        assertFalse("fo {name} no numbered args", pattern.hasNumberedArguments());
        pattern.parse("fum {0} {name}");
        assertTrue("fum {0} {name} has named args", pattern.hasNamedArguments());
        assertTrue("fum {0} {name} no numbered args", pattern.hasNumberedArguments());
    }

    @Test
    public void TestMessagePatternPartCoverage() {
        MessagePattern pattern = new MessagePattern("ab{17}c");
        assertEquals("msg start { arg number } msg limit", 5, pattern.countParts());
        MessagePattern.Part arg = pattern.getPart(2);
        assertEquals("arg number", MessagePattern.Part.Type.ARG_NUMBER, arg.getType());
        assertEquals("arg number start", 3, arg.getIndex());
        assertEquals("arg number length", 2, arg.getLength());
        assertEquals("arg number limit", 5, arg.getLimit());
        assertEquals("arg number 17", 17, arg.getValue());
    }

    @Test
    public void TestMessagePatternParseChoiceStyle() {
        // This would be tested by ChoiceFormat if ICU4J had its own version of that,
        // like ICU4C does.
        // Instead, there is only java.text.ChoiceFormat.
        // Most of the implementation gets covered by testing with a MessageFormat
        // that contains a nested ChoiceFormat pattern,
        // but that does not call this public API method.
        MessagePattern pattern = new MessagePattern();
        // Example string from java.text.ChoiceFormat class docs.
        pattern.parseChoiceStyle(
                "-1#is negative| 0#is zero or fraction | 1#is one |" +
                "1.0<is 1+ |2#is two |2<is more than 2.");
        // Only simple API coverage. The parser implementation is tested via MessageFormat.
        assertTrue("many parts", pattern.countParts() > 10);
    }

    // This is mostly a code coverage test with verification minimized to what can be plausibly assumed: different
    // hash values for distinctly different objects.
    @Test
    public void TestDateFormatHashCode() {
        DateFormat testDF1 = DateFormat.getDateInstance(DateFormat.DEFAULT, ULocale.GERMAN);
        DateFormat testDF2 = DateFormat.getDateInstance(DateFormat.DEFAULT, ULocale.FRENCH);

        int actualHashResult1 = testDF1.hashCode();
        int actualHashResult2 = testDF2.hashCode();
        assertNotEquals("DateFormat hashCode() test: really the same hashcode?", actualHashResult1, actualHashResult2);
    }

    @Test
    public void TestMessageFormatNumberSkeleton() {
        Object[][] cases = new Object[][] {
                { "{0,number,::percent}", ULocale.ENGLISH, 50, "50%" },
                { "{0,number,::percent scale/100}", ULocale.ENGLISH, 0.5, "50%" },
                { "{0,number,   ::   percent   scale/100   }", ULocale.ENGLISH, 0.5, "50%" },
                { "{0,number,::currency/USD}", ULocale.ENGLISH, 23, "$23.00" },
                { "{0,number,::precision-integer}", ULocale.ENGLISH, 514.23, "514" },
                { "{0,number,::.000}", ULocale.ENGLISH, 514.23, "514.230" },
                { "{0,number,::.}", ULocale.ENGLISH, 514.23, "514" },
                { "{0,number,::}", ULocale.FRENCH, 514.23, "514,23" },
                { "Cost: {0,number,::currency/EUR}.", ULocale.ENGLISH, 4.3, "Cost: €4.30." },
                { "{0,number,'::'0.00}", ULocale.ENGLISH, 50, "::50.00" }, // pattern literal
        };

        for (Object[] cas : cases) {
            String messagePattern = (String) cas[0];
            ULocale locale = (ULocale) cas[1];
            Number arg = (Number) cas[2];
            String expected = (String) cas[3];

            MessageFormat msgf = new MessageFormat(messagePattern, locale);
            StringBuffer sb = new StringBuffer();
            FieldPosition fpos = new FieldPosition(FieldPosition_DONT_CARE);
            msgf.format(new Object[] { arg }, sb, fpos);

            assertEquals(messagePattern, expected, sb.toString());
        }
    }

    private static void doTheRealDateTimeSkeletonTesting(Date date, String messagePattern, ULocale locale, String expected) {

        MessageFormat msgf = new MessageFormat(messagePattern, locale);
        StringBuffer sb = new StringBuffer();
        FieldPosition fpos = new FieldPosition(FieldPosition_DONT_CARE);
        msgf.format(new Object[] { date }, sb, fpos);

        assertEquals(messagePattern, expected, sb.toString());
    }

    @Test
    public void TestMessageFormatDateSkeleton() {
        Date date = new GregorianCalendar(2021, Calendar.NOVEMBER, 23, 16, 42, 55).getTime();

        doTheRealDateTimeSkeletonTesting(date, "{0,date,::MMMMd}", ULocale.ENGLISH, "November 23");
        doTheRealDateTimeSkeletonTesting(date, "{0,date,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021, 4:42 PM");
        doTheRealDateTimeSkeletonTesting(date, "{0,date,   ::   yMMMMd   }", ULocale.ENGLISH, "November 23, 2021");
        doTheRealDateTimeSkeletonTesting(date, "{0,date,::yMMMMd}", ULocale.FRENCH, "23 novembre 2021");
        doTheRealDateTimeSkeletonTesting(date, "Expiration: {0,date,::yMMM}!", ULocale.ENGLISH, "Expiration: Nov 2021!");
        doTheRealDateTimeSkeletonTesting(date, "{0,date,'::'yMMMMd}", ULocale.ENGLISH, "::2021November23"); // pattern literal
    }

    @Test
    public void TestMessageFormatTimeSkeleton() {
        Date date = new GregorianCalendar(2021, Calendar.NOVEMBER, 23, 16, 42, 55).getTime();

        doTheRealDateTimeSkeletonTesting(date, "{0,time,::MMMMd}", ULocale.ENGLISH, "November 23");
        doTheRealDateTimeSkeletonTesting(date, "{0,time,::yMMMMdjm}", ULocale.ENGLISH, "November 23, 2021, 4:42 PM");
        doTheRealDateTimeSkeletonTesting(date, "{0,time,   ::   yMMMMd   }", ULocale.ENGLISH, "November 23, 2021");
        doTheRealDateTimeSkeletonTesting(date, "{0,time,::yMMMMd}", ULocale.FRENCH, "23 novembre 2021");
        doTheRealDateTimeSkeletonTesting(date, "Expiration: {0,time,::yMMM}!", ULocale.ENGLISH, "Expiration: Nov 2021!");
        doTheRealDateTimeSkeletonTesting(date, "{0,time,'::'yMMMMd}", ULocale.ENGLISH, "::2021November23"); // pattern literal
    }
}
