// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 * Copyright (c) 2004-2014, International Business Machines
 * Corporation and others.  All Rights Reserved.
 *******************************************************************************
 *
 */
package com.ibm.icu.dev.test.serializable;

import java.text.AttributedCharacterIterator;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Objects;

import com.ibm.icu.impl.DateNumberFormat;
import com.ibm.icu.impl.TZDBTimeZoneNames;
import com.ibm.icu.impl.TimeZoneGenericNames;
import com.ibm.icu.impl.TimeZoneGenericNames.GenericNameType;
import com.ibm.icu.number.NumberRangeFormatter;
import com.ibm.icu.text.ChineseDateFormat;
import com.ibm.icu.text.ChineseDateFormatSymbols;
import com.ibm.icu.text.CompactDecimalFormat;
import com.ibm.icu.text.CurrencyPluralInfo;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.DateFormatSymbols;
import com.ibm.icu.text.DateIntervalFormat;
import com.ibm.icu.text.DateIntervalInfo;
import com.ibm.icu.text.DecimalFormat;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.DurationFormat;
import com.ibm.icu.text.ListFormatter;
import com.ibm.icu.text.MessageFormat;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralFormat;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.RelativeDateTimeFormatter;
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.text.SelectFormat;
import com.ibm.icu.text.SimpleDateFormat;
import com.ibm.icu.text.TimeUnitFormat;
import com.ibm.icu.text.TimeZoneFormat;
import com.ibm.icu.text.TimeZoneFormat.Style;
import com.ibm.icu.text.TimeZoneNames;
import com.ibm.icu.text.TimeZoneNames.NameType;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.DateInterval;
import com.ibm.icu.util.GregorianCalendar;
import com.ibm.icu.util.TimeUnit;
import com.ibm.icu.util.TimeUnitAmount;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;

/**
 * @author emader
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class FormatHandler
{
    /*
     * The serialized form of a normally created DateFormatSymbols object
     * will have locale-specific data in it that might change from one version
     * of ICU4J to another. To guard against this, we store the following canned
     * data into the test objects we create.
     */
    static HashMap<String, String[]> cannedMonthNames = new HashMap<>();
    static HashMap<String, String[]> cannedShortMonthNames = new HashMap<>();

    static String en_CA_MonthNames[] = {
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    };

    static String fr_CA_MonthNames[] = {
        "janvier",
        "f\u00E9vrier",
        "mars",
        "avril",
        "mai",
        "juin",
        "juillet",
        "ao\u00FBt",
        "septembre",
        "octobre",
        "novembre",
        "d\u00E9cembre",
    };

    static String zh_Hans_CN_MonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String zh_CN_MonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String zh_MonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String en_MonthNames[] = {
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    };

    static String fr_FR_MonthNames[] = {
        "janvier",
        "f\u00E9vrier",
        "mars",
        "avril",
        "mai",
        "juin",
        "juillet",
        "ao\u00FBt",
        "septembre",
        "octobre",
        "novembre",
        "d\u00E9cembre",
    };

    static String fr_MonthNames[] = {
        "janvier",
        "f\u00E9vrier",
        "mars",
        "avril",
        "mai",
        "juin",
        "juillet",
        "ao\u00FBt",
        "septembre",
        "octobre",
        "novembre",
        "d\u00E9cembre",
    };

    static String de_MonthNames[] = {
        "Januar",
        "Februar",
        "M\u00E4rz",
        "April",
        "Mai",
        "Juni",
        "Juli",
        "August",
        "September",
        "Oktober",
        "November",
        "Dezember",
    };

    static String de_DE_MonthNames[] = {
        "Januar",
        "Februar",
        "M\u00E4rz",
        "April",
        "Mai",
        "Juni",
        "Juli",
        "August",
        "September",
        "Oktober",
        "November",
        "Dezember",
    };

    static String it_MonthNames[] = {
        "gennaio",
        "febbraio",
        "marzo",
        "aprile",
        "maggio",
        "giugno",
        "luglio",
        "agosto",
        "settembre",
        "ottobre",
        "novembre",
        "dicembre",
    };

    static String it_IT_MonthNames[] = {
        "gennaio",
        "febbraio",
        "marzo",
        "aprile",
        "maggio",
        "giugno",
        "luglio",
        "agosto",
        "settembre",
        "ottobre",
        "novembre",
        "dicembre",
    };

    static String ja_JP_MonthNames[] = {
        "1\u6708",
        "2\u6708",
        "3\u6708",
        "4\u6708",
        "5\u6708",
        "6\u6708",
        "7\u6708",
        "8\u6708",
        "9\u6708",
        "10\u6708",
        "11\u6708",
        "12\u6708",
    };

    static String ja_MonthNames[] = {
        "1\u6708",
        "2\u6708",
        "3\u6708",
        "4\u6708",
        "5\u6708",
        "6\u6708",
        "7\u6708",
        "8\u6708",
        "9\u6708",
        "10\u6708",
        "11\u6708",
        "12\u6708",
    };

    static String ko_KR_MonthNames[] = {
        "1\uC6D4",
        "2\uC6D4",
        "3\uC6D4",
        "4\uC6D4",
        "5\uC6D4",
        "6\uC6D4",
        "7\uC6D4",
        "8\uC6D4",
        "9\uC6D4",
        "10\uC6D4",
        "11\uC6D4",
        "12\uC6D4",
    };

    static String ko_MonthNames[] = {
        "1\uC6D4",
        "2\uC6D4",
        "3\uC6D4",
        "4\uC6D4",
        "5\uC6D4",
        "6\uC6D4",
        "7\uC6D4",
        "8\uC6D4",
        "9\uC6D4",
        "10\uC6D4",
        "11\uC6D4",
        "12\uC6D4",
    };

    static String zh_Hant_TW_MonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String zh_TW_MonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
        };

    static String en_GB_MonthNames[] = {
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    };

    static String en_US_MonthNames[] = {
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December",
    };

    static String en_CA_ShortMonthNames[] = {
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    };

    static String fr_CA_ShortMonthNames[] = {
        "janv.",
        "f\u00E9vr.",
        "mars",
        "avr.",
        "mai",
        "juin",
        "juil.",
        "ao\u00FBt",
        "sept.",
        "oct.",
        "nov.",
        "d\u00E9c.",
    };

    static String zh_Hans_CN_ShortMonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String zh_CN_ShortMonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String zh_ShortMonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String en_ShortMonthNames[] = {
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    };

    static String fr_FR_ShortMonthNames[] = {
        "janv.",
        "f\u00E9vr.",
        "mars",
        "avr.",
        "mai",
        "juin",
        "juil.",
        "ao\u00FBt",
        "sept.",
        "oct.",
        "nov.",
        "d\u00E9c.",
    };

   static String fr_ShortMonthNames[] = {
        "janv.",
        "f\u00E9vr.",
        "mars",
        "avr.",
        "mai",
        "juin",
        "juil.",
        "ao\u00FBt",
        "sept.",
        "oct.",
        "nov.",
        "d\u00E9c.",
    };

    static String de_ShortMonthNames[] = {
        "Jan",
        "Feb",
        "Mrz",
        "Apr",
        "Mai",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Okt",
        "Nov",
        "Dez",
    };

    static String de_DE_ShortMonthNames[] = {
        "Jan",
        "Feb",
        "Mrz",
        "Apr",
        "Mai",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Okt",
        "Nov",
        "Dez",
    };

    static String it_ShortMonthNames[] = {
        "gen",
        "feb",
        "mar",
        "apr",
        "mag",
        "giu",
        "lug",
        "ago",
        "set",
        "ott",
        "nov",
        "dic",
    };

    static String it_IT_ShortMonthNames[] = {
        "gen",
        "feb",
        "mar",
        "apr",
        "mag",
        "giu",
        "lug",
        "ago",
        "set",
        "ott",
        "nov",
        "dic",
    };

    static String ja_JP_ShortMonthNames[] = {
        "1 \u6708",
        "2 \u6708",
        "3 \u6708",
        "4 \u6708",
        "5 \u6708",
        "6 \u6708",
        "7 \u6708",
        "8 \u6708",
        "9 \u6708",
        "10 \u6708",
        "11 \u6708",
        "12 \u6708",
    };

    static String ja_ShortMonthNames[] = {
        "1 \u6708",
        "2 \u6708",
        "3 \u6708",
        "4 \u6708",
        "5 \u6708",
        "6 \u6708",
        "7 \u6708",
        "8 \u6708",
        "9 \u6708",
        "10 \u6708",
        "11 \u6708",
        "12 \u6708",
    };

    static String ko_KR_ShortMonthNames[] = {
        "1\uC6D4",
        "2\uC6D4",
        "3\uC6D4",
        "4\uC6D4",
        "5\uC6D4",
        "6\uC6D4",
        "7\uC6D4",
        "8\uC6D4",
        "9\uC6D4",
        "10\uC6D4",
        "11\uC6D4",
        "12\uC6D4",
    };

    static String ko_ShortMonthNames[] = {
        "1\uC6D4",
        "2\uC6D4",
        "3\uC6D4",
        "4\uC6D4",
        "5\uC6D4",
        "6\uC6D4",
        "7\uC6D4",
        "8\uC6D4",
        "9\uC6D4",
        "10\uC6D4",
        "11\uC6D4",
        "12\uC6D4",
    };

    static String zh_Hant_TW_ShortMonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String zh_TW_ShortMonthNames[] = {
        "\u4E00\u6708",
        "\u4E8C\u6708",
        "\u4E09\u6708",
        "\u56DB\u6708",
        "\u4E94\u6708",
        "\u516D\u6708",
        "\u4E03\u6708",
        "\u516B\u6708",
        "\u4E5D\u6708",
        "\u5341\u6708",
        "\u5341\u4E00\u6708",
        "\u5341\u4E8C\u6708",
    };

    static String en_GB_ShortMonthNames[] = {
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    };

    static String en_US_ShortMonthNames[] = {
        "Jan",
        "Feb",
        "Mar",
        "Apr",
        "May",
        "Jun",
        "Jul",
        "Aug",
        "Sep",
        "Oct",
        "Nov",
        "Dec",
    };

    static {
        cannedMonthNames.put("en_CA",      en_CA_MonthNames);
        cannedMonthNames.put("fr_CA",      fr_CA_MonthNames);
        cannedMonthNames.put("zh_Hans_CN", zh_Hans_CN_MonthNames);
        cannedMonthNames.put("zh_CN",      zh_CN_MonthNames);
        cannedMonthNames.put("zh",         zh_MonthNames);
        cannedMonthNames.put("en",         en_MonthNames);
        cannedMonthNames.put("fr_FR",      fr_FR_MonthNames);
        cannedMonthNames.put("fr",         fr_MonthNames);
        cannedMonthNames.put("de",         de_MonthNames);
        cannedMonthNames.put("de_DE",      de_DE_MonthNames);
        cannedMonthNames.put("it",         it_MonthNames);
        cannedMonthNames.put("it_IT",      it_IT_MonthNames);
        cannedMonthNames.put("ja_JP",      ja_JP_MonthNames);
        cannedMonthNames.put("ja",         ja_MonthNames);
        cannedMonthNames.put("ko_KR",      ko_KR_MonthNames);
        cannedMonthNames.put("ko",         ko_MonthNames);
        cannedMonthNames.put("zh_Hant_TW", zh_Hant_TW_MonthNames);
        cannedMonthNames.put("zh_TW",      zh_TW_MonthNames);
        cannedMonthNames.put("en_GB",      en_GB_MonthNames);
        cannedMonthNames.put("en_US",      en_US_MonthNames);

        cannedShortMonthNames.put("en_CA",      en_CA_ShortMonthNames);
        cannedShortMonthNames.put("fr_CA",      fr_CA_ShortMonthNames);
        cannedShortMonthNames.put("zh_Hans_CN", zh_Hans_CN_ShortMonthNames);
        cannedShortMonthNames.put("zh_CN",      zh_CN_ShortMonthNames);
        cannedShortMonthNames.put("zh",         zh_ShortMonthNames);
        cannedShortMonthNames.put("en",         en_ShortMonthNames);
        cannedShortMonthNames.put("fr_FR",      fr_FR_ShortMonthNames);
        cannedShortMonthNames.put("fr",         fr_ShortMonthNames);
        cannedShortMonthNames.put("de",         de_ShortMonthNames);
        cannedShortMonthNames.put("de_DE",      de_DE_ShortMonthNames);
        cannedShortMonthNames.put("it",         it_ShortMonthNames);
        cannedShortMonthNames.put("it_IT",      it_IT_ShortMonthNames);
        cannedShortMonthNames.put("ja_JP",      ja_JP_ShortMonthNames);
        cannedShortMonthNames.put("ja",         ja_ShortMonthNames);
        cannedShortMonthNames.put("ko_KR",      ko_KR_ShortMonthNames);
        cannedShortMonthNames.put("ko",         ko_ShortMonthNames);
        cannedShortMonthNames.put("zh_Hant_TW", zh_Hant_TW_ShortMonthNames);
        cannedShortMonthNames.put("zh_TW",      zh_TW_ShortMonthNames);
        cannedShortMonthNames.put("en_GB",      en_GB_ShortMonthNames);
        cannedShortMonthNames.put("en_US",      en_US_ShortMonthNames);
    }

    private static DateFormatSymbols getCannedDateFormatSymbols(ULocale uloc)
    {
        DateFormatSymbols dfs =new DateFormatSymbols(GregorianCalendar.class, uloc);
        String key = uloc.toString();

        dfs.setMonths((String[]) cannedMonthNames.get(key));
        dfs.setShortMonths((String[]) cannedShortMonthNames.get(key));

        return dfs;
    }

    private static SimpleDateFormat getCannedSimpleDateFormat(String pattern, ULocale uloc)
    {
        DateFormatSymbols dfs = getCannedDateFormatSymbols(uloc);

        // Force PT
        TimeZone pt = TimeZone.getTimeZone("America/Los_Angeles");
        SimpleDateFormat cannedSDF = new SimpleDateFormat(pattern, dfs, uloc);
        cannedSDF.setTimeZone(pt);
        return cannedSDF;
    }

    /*
     * The serialized form of a normally created DecimalFormatSymbols object
     * will have locale-specific data in it that might change from one version
     * of ICU4J to another. To guard against this, we store the following canned
     * data into the test objects we create.
     */
    static HashMap<String, String[]> cannedDecimalFormatSymbols = new HashMap<>();

    static String en_CA_StringSymbols[] = {
        "$",
        "E",
        "\u221E",
        "CAD",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String fr_CA_StringSymbols[] = {
        "$",
        "E",
        "\u221E",
        "CAD",
        "\uFFFD",
        ",#\u00A0-,*;%\u2030+@0"
    };

    static String zh_CN_StringSymbols[] = {
        "\uFFE5",
        "E",
        "\u221E",
        "CNY",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String zh_StringSymbols[] = {
        "\u00A4",
        "E",
        "\u221E",
        "XXX",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String en_StringSymbols[] = {
        "\u00A4",
        "E",
        "\u221E",
        "XXX",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String fr_FR_StringSymbols[] = {
        "\u20AC",
        "E",
        "\u221E",
        "EUR",
        "\uFFFD",
        ",#\u00A0-,*;%\u2030+@0"
    };

    static String fr_StringSymbols[] = {
        "\u00A4",
        "E",
        "\u221E",
        "XXX",
        "\uFFFD",
        ",#\u00A0-,*;%\u2030+@0"
    };

    static String de_StringSymbols[] = {
        "\u00A4",
        "E",
        "\u221E",
        "XXX",
        "\uFFFD",
        ",#.-,*;%\u2030+@0"
    };

    static String de_DE_StringSymbols[] = {
        "\u20AC",
        "E",
        "\u221E",
        "EUR",
        "\uFFFD",
        ",#.-,*;%\u2030+@0"
    };

    static String it_StringSymbols[] = {
        "\u00A4",
        "E",
        "\u221E",
        "XXX",
        "\uFFFD",
        ",#.-,*;%\u2030+@0"
    };

    static String it_IT_StringSymbols[] = {
        "\u20AC",
        "E",
        "\u221E",
        "EUR",
        "\uFFFD",
        ",#.-,*;%\u2030+@0"
    };

    static String ja_JP_StringSymbols[] = {
        "\uFFE5",
        "E",
        "\u221E",
        "JPY",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String ja_StringSymbols[] = {
        "\u00A4",
        "E",
        "\u221E",
        "XXX",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String ko_KR_StringSymbols[] = {
        "\uFFE6",
        "E",
        "\u221E",
        "KRW",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String ko_StringSymbols[] = {
        "\u00A4",
        "E",
        "\u221E",
        "XXX",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String zh_Hans_CN_StringSymbols[] = {
        "\uFFE5",
        "E",
        "\u221E",
        "CNY",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String zh_Hant_TW_StringSymbols[] = {
        "NT$",
        "E",
        "\u221E",
        "TWD",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String zh_TW_StringSymbols[] = {
        "NT$",
        "E",
        "\u221E",
        "TWD",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String en_GB_StringSymbols[] = {
        "\u00A3",
        "E",
        "\u221E",
        "GBP",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static String en_US_StringSymbols[] = {
        "$",
        "E",
        "\u221E",
        "USD",
        "\uFFFD",
        ".#,-.*;%\u2030+@0"
    };

    static {
        cannedDecimalFormatSymbols.put("en_CA",      en_CA_StringSymbols);
        cannedDecimalFormatSymbols.put("fr_CA",      fr_CA_StringSymbols);
        cannedDecimalFormatSymbols.put("zh_CN",      zh_CN_StringSymbols);
        cannedDecimalFormatSymbols.put("zh",         zh_StringSymbols);
        cannedDecimalFormatSymbols.put("en",         en_StringSymbols);
        cannedDecimalFormatSymbols.put("fr_FR",      fr_FR_StringSymbols);
        cannedDecimalFormatSymbols.put("fr",         fr_StringSymbols);
        cannedDecimalFormatSymbols.put("de",         de_StringSymbols);
        cannedDecimalFormatSymbols.put("de_DE",      de_DE_StringSymbols);
        cannedDecimalFormatSymbols.put("it",         it_StringSymbols);
        cannedDecimalFormatSymbols.put("it_IT",      it_IT_StringSymbols);
        cannedDecimalFormatSymbols.put("ja_JP",      ja_JP_StringSymbols);
        cannedDecimalFormatSymbols.put("ja",         ja_StringSymbols);
        cannedDecimalFormatSymbols.put("ko_KR",      ko_KR_StringSymbols);
        cannedDecimalFormatSymbols.put("ko",         ko_StringSymbols);
        cannedDecimalFormatSymbols.put("zh_Hans_CN", zh_Hans_CN_StringSymbols);
        cannedDecimalFormatSymbols.put("zh_Hant_TW", zh_Hant_TW_StringSymbols);
        cannedDecimalFormatSymbols.put("zh_TW",      zh_TW_StringSymbols);
        cannedDecimalFormatSymbols.put("en_GB",      en_GB_StringSymbols);
        cannedDecimalFormatSymbols.put("en_US",      en_US_StringSymbols);
    }

    private static char[] getCharSymbols(DecimalFormatSymbols dfs)
    {
        char symbols[] = {
            dfs.getDecimalSeparator(),
            dfs.getDigit(),
            dfs.getGroupingSeparator(),
            dfs.getMinusSign(),
            dfs.getMonetaryDecimalSeparator(),
            dfs.getPadEscape(),
            dfs.getPatternSeparator(),
            dfs.getPercent(),
            dfs.getPerMill(),
            dfs.getPlusSign(),
            dfs.getSignificantDigit(),
            dfs.getZeroDigit()
        };

        return symbols;
    }

    private static void setCharSymbols(DecimalFormatSymbols dfs, char symbols[])
    {
        dfs.setDecimalSeparator(symbols[0]);
        dfs.setDigit(symbols[1]);
        dfs.setGroupingSeparator(symbols[2]);
        dfs.setMinusSign(symbols[3]);
        dfs.setMonetaryDecimalSeparator(symbols[4]);
        dfs.setPadEscape(symbols[5]);
        dfs.setPatternSeparator(symbols[6]);
        dfs.setPercent(symbols[7]);
        dfs.setPerMill(symbols[8]);
        dfs.setPlusSign(symbols[9]);
        dfs.setSignificantDigit(symbols[10]);
        dfs.setZeroDigit(symbols[11]);
    }

    private static String[] getStringSymbols(DecimalFormatSymbols dfs)
    {
        String symbols[] = {
            dfs.getCurrencySymbol(),
            dfs.getExponentSeparator(),
            dfs.getInfinity(),
            dfs.getInternationalCurrencySymbol(),
            dfs.getNaN()
        };

        return symbols;
    }

    private static DecimalFormatSymbols getCannedDecimalFormatSymbols(ULocale uloc)
    {
        DecimalFormatSymbols dfs = new DecimalFormatSymbols(uloc);

        setSymbols(dfs, (String[]) cannedDecimalFormatSymbols.get(uloc.toString()));

        return dfs;
    }

    private static DecimalFormat getCannedDecimalFormat(String pattern, ULocale uloc)
    {
        return new DecimalFormat(pattern, getCannedDecimalFormatSymbols(uloc));
    }

    private static void setSymbols(DecimalFormatSymbols dfs, String symbols[])
    {
        dfs.setCurrencySymbol(symbols[0]);
        dfs.setExponentSeparator(symbols[1]);
        dfs.setInfinity(symbols[2]);
        dfs.setInternationalCurrencySymbol(symbols[3]);
        dfs.setNaN(symbols[4]);

        setCharSymbols(dfs, symbols[5].toCharArray());
    }

    public static class RelativeDateFormatHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            DateFormat formats[] = {
                    DateFormat.getDateInstance(DateFormat.RELATIVE_LONG,new ULocale("en")),
                    DateFormat.getDateInstance(DateFormat.RELATIVE_SHORT,new ULocale("ru")),
            };

            return formats;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            DateFormat da = (DateFormat)a;
            DateFormat db = (DateFormat)b;

            Date d = new Date(System.currentTimeMillis());
            return da.format(d).equals(db.format(d));
        }
    }

    public static class BasicDurationFormatHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            DurationFormat formats[] = {
                    DurationFormat.getInstance(new ULocale("en"))

            };

            return formats;
        }

        //TODO: Revisit this after 3.8
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            //DurationFormat da = (DurationFormat)a;
            //DurationFormat db = (DurationFormat)b;

            //Date d = new Date(12345);
            //System.err.println("Warning: BasicDurationFormat test is being skipped for now.");
            return true;
            //return da.format(d).equals(db.format(d));
        }
    }

    public static class NumberFormatHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            ULocale uloc = ULocale.forLocale(Locale.US);
            NumberFormat formats[] = {
                /*
                 * The code below was used to generate the
                 * serialized NumberFormat objects in ICU 3.6:
                 *
                 *    NumberFormat.getInstance(Locale.US)
                 *    NumberFormat.getCurrencyInstance(Locale.US)
                 *    NumberFormat.getPercentInstance(Locale.US)
                 *    NumberFormat.getScientificInstance(Locale.US)
                 *
                 * Because the locale data might now be different that it was in
                 * ICU 3.6, the only way to guarantee that the object we generate
                 * will match the ICU 3.6 objects is to generate DecimalFormat objects
                 * that use the same patterns and DecimalFormatSymbols that
                 * were used in ICU 3.6.
                 */
                getCannedDecimalFormat("#,##0.###", uloc),
                getCannedDecimalFormat("\u00A4#,##0.00;(\u00A4#,##0.00)", uloc),
                getCannedDecimalFormat("#,##0%", uloc),
                getCannedDecimalFormat("#E0", uloc)

            };

            return formats;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            NumberFormat format_a = (NumberFormat) a;
            NumberFormat format_b = (NumberFormat) b;
            double number = 1234.56;

            // In CLDR 34, French changed grouping separator from \u00a0 to \u202f;
            // here we undo that in formatted result so serialization tests don't fail
            String result_a = format_a.format(number).replace('\u202f', '\u00a0');
            String result_b = format_b.format(number).replace('\u202f', '\u00a0');
            boolean equal = result_a.equals(result_b);
            if (!equal) {
                System.out.println(format_a+" "+format_b);
                System.out.println(result_a+" "+result_b);
            }
            return equal;
        }
    }

    public static class DecimalFormatHandler extends NumberFormatHandler
    {
        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            DecimalFormat formats[] = new DecimalFormat[locales.length];

            for (int i = 0; i < locales.length; i += 1) {
                ULocale uloc = ULocale.forLocale(locales[i]);

                formats[i] = getCannedDecimalFormat("#,##0.###", uloc);
            }
            if (formats[0] != null) {
                // Ticket#6449
                // Once formatToCharacterIterator is called, NumberFormat.Field
                // instances are created and stored in the private List field.
                // NumberForamt.Field is not a serializable, so serializing such
                // instances end up NotSerializableException.  This problem was
                // reproduced since formatToCharacterIterator was introduced,
                // up to ICU 4.0.

                AttributedCharacterIterator aci = formats[0].formatToCharacterIterator(12.345d);
                if (aci == null) {} // NOP - for resolving 'Unused local variable' warning.
            }
            return formats;
        }
    }

    public static class RuleBasedNumberFormatHandler extends NumberFormatHandler
    {
        // default rules, from root.txt
        String xx_SpelloutRules = "=#,##0.######=;\n";
        String xx_OrdinalRules = "=#,##0=;\n";
        String xx_DurationRules = "=#,##0=;\n";

        String ja_spelloutRules =
            "%financial:\n" +
                "\u96f6; \u58f1; \u5f10; \u53c2; \u56db; \u4f0d; \u516d; \u4e03; \u516b; \u4e5d;\n" +
                "\u62fe[>>];\n" +
                "20: <<\u62fe[>>];\n" +
                "100: <<\u767e[>>];\n" +
                "1000: <<\u5343[>>];\n" +
                "10,000: <<\u4e07[>>];\n" +
                "100,000,000: <<\u5104[>>];\n" +
                "1,000,000,000,000: <<\u5146[>>];\n" +
                "10,000,000,000,000,000: =#,##0=;\n" +

            "%traditional:\n" +
                "\u96f6; \u4e00; \u4e8c; \u4e09; \u56db; \u4e94; \u516d; \u4e03; \u516b; \u4e5d;\n" +
                "\u5341[>>];\n" +
                "20: <<\u5341[>>];\n" +
                "100: <<\u767e[>>];\n" +
                "1000: <<\u5343[>>];\n" +
                "10,000: <<\u4e07[>>];\n" +
                "100,000,000: <<\u5104[>>];\n" +
                "1,000,000,000,000: <<\u5146[>>];\n" +
                "10,000,000,000,000,000: =#,##0=;";

        String en_SpelloutRules =
            // This rule set shows the normal simple formatting rules for English
            "%simplified:\n" +
                   // negative number rule.  This rule is used to format negative
                   // numbers.  The result of formatting the number's absolute
                   // value is placed where the >> is.
                "-x: minus >>;\n" +
                   // faction rule.  This rule is used for formatting numbers
                   // with fractional parts.  The result of formatting the
                   // number's integral part is substituted for the <<, and
                   // the result of formatting the number's fractional part
                   // (one digit at a time, e.g., 0.123 is "zero point one two
                   // three") replaces the >>.
                "x.x: << point >>;\n" +
                   // the rules for the values from 0 to 19 are simply the
                   // words for those numbers
                "zero; one; two; three; four; five; six; seven; eight; nine;\n" +
                "ten; eleven; twelve; thirteen; fourteen; fifteen; sixteen;\n" +
                    "seventeen; eighteen; nineteen;\n" +
                   // beginning at 20, we use the >> to mark the position where
                   // the result of formatting the number's ones digit.  Thus,
                   // we only need a new rule at every multiple of 10.  Text in
                   // brackets is omitted if the value being formatted is an
                   // even multiple of 10.
                "20: twenty[->>];\n" +
                "30: thirty[->>];\n" +
                "40: forty[->>];\n" +
                "50: fifty[->>];\n" +
                "60: sixty[->>];\n" +
                "70: seventy[->>];\n" +
                "80: eighty[->>];\n" +
                "90: ninety[->>];\n" +
                   // beginning at 100, we can use << to mark the position where
                   // the result of formatting the multiple of 100 is to be
                   // inserted.  Notice also that the meaning of >> has shifted:
                   // here, it refers to both the ones place and the tens place.
                   // The meanings of the << and >> tokens depend on the base value
                   // of the rule.  A rule's divisor is (usually) the highest
                   // power of 10 that is less than or equal to the rule's base
                   // value.  The value being formatted is divided by the rule's
                   // divisor, and the integral quotient is used to get the text
                   // for <<, while the remainder is used to produce the text
                   // for >>.  Again, text in brackets is omitted if the value
                   // being formatted is an even multiple of the rule's divisor
                   // (in this case, an even multiple of 100)
                "100: << hundred[ >>];\n" +
                   // The rules for the higher numbers work the same way as the
                   // rule for 100: Again, the << and >> tokens depend on the
                   // rule's divisor, which for all these rules is also the rule's
                   // base value.  To group by thousand, we simply don't have any
                   // rules between 1,000 and 1,000,000.
                "1000: << thousand[ >>];\n" +
                "1,000,000: << million[ >>];\n" +
                "1,000,000,000: << billion[ >>];\n" +
                "1,000,000,000,000: << trillion[ >>];\n" +
                   // overflow rule.  This rule specifies that values of a
                   // quadrillion or more are shown in numerals rather than words.
                   // The == token means to format (with new rules) the value
                   // being formatted by this rule and place the result where
                   // the == is.  The #,##0 inside the == signs is a
                   // DecimalFormat pattern.  It specifies that the value should
                   // be formatted with a DecimalFormat object, and that it
                   // should be formatted with no decimal places, at least one
                   // digit, and a thousands separator.
                "1,000,000,000,000,000: =#,##0=;\n" +

            // %default is a more elaborate form of %simplified;  It is basically
            // the same, except that it introduces "and" before the ones digit
            // when appropriate (basically, between the tens and ones digits) and
            // separates the thousands groups with commas in values over 100,000.
            "%default:\n" +
                   // negative-number and fraction rules.  These are the same
                   // as those for %simplified, but have to be stated here too
                   // because this is an entry point
                "-x: minus >>;\n" +
                "x.x: << point >>;\n" +
                   // just use %simplified for values below 100
                "=%simplified=;\n" +
                   // for values from 100 to 9,999 use %%and to decide whether or
                   // not to interpose the "and"
                "100: << hundred[ >%%and>];\n" +
                "1000: << thousand[ >%%and>];\n" +
                   // for values of 100,000 and up, use %%commas to interpose the
                   // commas in the right places (and also to interpose the "and")
                "100,000>>: << thousand[>%%commas>];\n" +
                "1,000,000: << million[>%%commas>];\n" +
                "1,000,000,000: << billion[>%%commas>];\n" +
                "1,000,000,000,000: << trillion[>%%commas>];\n" +
                "1,000,000,000,000,000: =#,##0=;\n" +
            // if the value passed to this rule set is greater than 100, don't
            // add the "and"; if it's less than 100, add "and" before the last
            // digits
            "%%and:\n" +
                "and =%default=;\n" +
                "100: =%default=;\n" +
            // this rule set is used to place the commas
            "%%commas:\n" +
                   // for values below 100, add "and" (the apostrophe at the
                   // beginning is ignored, but causes the space that follows it
                   // to be significant: this is necessary because the rules
                   // calling %%commas don't put a space before it)
                "' and =%default=;\n" +
                   // put a comma after the thousands (or whatever preceded the
                   // hundreds)
                "100: , =%default=;\n" +
                   // put a comma after the millions (or whatever precedes the
                   // thousands)
                "1000: , <%default< thousand, >%default>;\n" +
                   // and so on...
                "1,000,000: , =%default=;" +
            // %%lenient-parse isn't really a set of number formatting rules;
            // it's a set of collation rules.  Lenient-parse mode uses a Collator
            // object to compare fragments of the text being parsed to the text
            // in the rules, allowing more leeway in the matching text.  This set
            // of rules tells the formatter to ignore commas when parsing (it
            // already ignores spaces, which is why we refer to the space; it also
            // ignores hyphens, making "twenty one" and "twenty-one" parse
            // identically)
            "%%lenient-parse:\n" +
            //                "& ' ' , ',' ;\n" +
            "   &\u0000 << ' ' << ',' << '-'; \n";

        String en_GB_SpelloutRules =
            "%simplified:\n" +
            "-x: minus >>;\n" +
            "x.x: << point >>;\n" +
            "zero; one; two; three; four; five; six; seven; eight; nine;\n" +
            "ten; eleven; twelve; thirteen; fourteen; fifteen; sixteen;\n" +
            "    seventeen; eighteen; nineteen;\n" +
            "20: twenty[->>];\n" +
            "30: thirty[->>];\n" +
            "40: forty[->>];\n" +
            "50: fifty[->>];\n" +
            "60: sixty[->>];\n" +
            "70: seventy[->>];\n" +
            "80: eighty[->>];\n" +
            "90: ninety[->>];\n" +
            "100: << hundred[ >>];\n" +
            "1000: << thousand[ >>];\n" +
            "1,000,000: << million[ >>];\n" +
            "1,000,000,000,000: << billion[ >>];\n" +
            "1,000,000,000,000,000: =#,##0=;\n" +
        "%default:\n" +
            "-x: minus >>;\n" +
            "x.x: << point >>;\n" +
            "=%simplified=;\n" +
            "100: << hundred[ >%%and>];\n" +
            "1000: << thousand[ >%%and>];\n" +
            "100,000>>: << thousand[>%%commas>];\n" +
            "1,000,000: << million[>%%commas>];\n" +
            "1,000,000,000,000: << billion[>%%commas>];\n" +
            "1,000,000,000,000,000: =#,##0=;\n" +
        "%%and:\n" +
            "and =%default=;\n" +
            "100: =%default=;\n" +
        "%%commas:\n" +
            "' and =%default=;\n" +
            "100: , =%default=;\n" +
            "1000: , <%default< thousand, >%default>;\n" +
            "1,000,000: , =%default=;" +
        "%%lenient-parse:\n" +
            "& ' ' , ',' ;\n";

        String fr_SpelloutRules =
            // the main rule set
            "%main:\n" +
                "-x: moins >>;\n" +
                "x.x: << virgule >>;\n" +
                   // words for numbers from 0 to 10
                "z\u00e9ro; un; deux; trois; quatre; cinq; six; sept; huit; neuf;\n" +
                "dix; onze; douze; treize; quatorze; quinze; seize;\n" +
                "    dix-sept; dix-huit; dix-neuf;\n" +
                   // ords for the multiples of 10: %%alt-ones inserts "et"
                   // when needed
                "20: vingt[->%%alt-ones>];\n" +
                "30: trente[->%%alt-ones>];\n" +
                "40: quarante[->%%alt-ones>];\n" +
                "50: cinquante[->%%alt-ones>];\n" +
                   // rule for 60.  The /20 causes this rule's multiplier to be
                   // 20 rather than 10, allowing us to recurse for all values
                   // from 60 to 79...
                "60/20: soixante[->%%alt-ones>];\n" +
                   // ...except for 71, which must be special-cased
                "71: soixante et onze;\n" +
                   // at 72, we have to repeat the rule for 60 to get us to 79
                "72/20: soixante->%%alt-ones>;\n" +
                   // at 80, we state a new rule with the phrase for 80.  Since
                   // it changes form when there's a ones digit, we need a second
                   // rule at 81.  This rule also includes "/20," allowing it to
                   // be used correctly for all values up to 99
                "80: quatre-vingts; 81/20: quatre-vingt->>;\n" +
                   // "cent" becomes plural when preceded by a multiplier, and
                   // the multiplier is omitted from the singular form
                "100: cent[ >>];\n" +
                "200: << cents[ >>];\n" +
                "1000: mille[ >>];\n" +
                   // values from 1,100 to 1,199 are rendered as "onze cents..."
                   // instead of "mille cent..."  The > after "1000" decreases
                   // the rule's exponent, causing its multiplier to be 100 instead
                   // of 1,000.  This prevents us from getting "onze cents cent
                   // vingt-deux" ("eleven hundred one hundred twenty-two").
                "1100>: onze cents[ >>];\n" +
                   // at 1,200, we go back to formatting in thousands, so we
                   // repeat the rule for 1,000
                "1200: mille >>;\n" +
                   // at 2,000, the multiplier is added
                "2000: << mille[ >>];\n" +
                "1,000,000: << million[ >>];\n" +
                "1,000,000,000: << milliard[ >>];\n" +
                "1,000,000,000,000: << billion[ >>];\n" +
                "1,000,000,000,000,000: =#,##0=;\n" +
            // %%alt-ones is used to insert "et" when the ones digit is 1
            "%%alt-ones:\n" +
                "; et-un; =%main=;\n" +
            "%%lenient-parse:\n" +
                "&\u0000 << ' ' << ',' << '-';\n";

        String de_SpelloutRules =
            // 1 is "eins" when by itself, but turns into "ein" in most
            // combinations
            "%alt-ones:\n" +
                "-x: minus >>;\n" +
                "x.x: << komma >>;\n" +
                "null; eins; =%%main=;\n" +
            "%%main:\n" +
                   // words for numbers from 0 to 12.  Notice that the values
                   // from 13 to 19 can derived algorithmically, unlike in most
                   // other languages
                "null; ein; zwei; drei; vier; f\u00fcnf; sechs; sieben; acht; neun;\n" +
                "zehn; elf; zw\u00f6lf; >>zehn;\n" +
                   // rules for the multiples of 10.  Notice that the ones digit
                   // goes on the front
                "20: [>>und]zwanzig;\n" +
                "30: [>>und]drei\u00dfig;\n" +
                "40: [>>und]vierzig;\n" +
                "50: [>>und]f\u00fcnfzig;\n" +
                "60: [>>und]sechzig;\n" +
                "70: [>>und]siebzig;\n" +
                "80: [>>und]achtzig;\n" +
                "90: [>>und]neunzig;\n" +
                "100: hundert[>%alt-ones>];\n" +
                "200: <<hundert[>%alt-ones>];\n" +
                "1000: tausend[>%alt-ones>];\n" +
                "2000: <<tausend[>%alt-ones>];\n" +
                "1,000,000: eine Million[ >%alt-ones>];\n" +
                "2,000,000: << Millionen[ >%alt-ones>];\n" +
                "1,000,000,000: eine Milliarde[ >%alt-ones>];\n" +
                "2,000,000,000: << Milliarden[ >%alt-ones>];\n" +
                "1,000,000,000,000: eine Billion[ >%alt-ones>];\n" +
                "2,000,000,000,000: << Billionen[ >%alt-ones>];\n" +
                "1,000,000,000,000,000: =#,##0=;" +
            "%%lenient-parse:\n" +
                "&\u0000 << ' ' << '-'\n" +
                "& ae , \u00e4 & ae , \u00c4\n" +
                "& oe , \u00f6 & oe , \u00d6\n" +
                "& ue , \u00fc & ue , \u00dc\n";

        String it_SpelloutRules =
            // main rule set.  Follows the patterns of the preceding rule sets,
            // except that the final vowel is omitted from words ending in
            // vowels when they are followed by another word; instead, we have
            // separate rule sets that are identical to this one, except that
            // all the words that don't begin with a vowel have a vowel tacked
            // onto them at the front.  A word ending in a vowel calls a
            // substitution that will supply that vowel, unless that vowel is to
            // be elided.
            "%main:\n" +
                "-x: meno >>;\n" +
                "x.x: << virgola >>;\n" +
                "zero; uno; due; tre; quattro; cinque; sei; sette; otto; nove;\n" +
                "dieci; undici; dodici; tredici; quattordici; quindici; sedici;\n" +
                "    diciasette; diciotto; diciannove;\n" +
                "20: venti; vent>%%with-i>;\n" +
                "30: trenta; trent>%%with-i>;\n" +
                "40: quaranta; quarant>%%with-a>;\n" +
                "50: cinquanta; cinquant>%%with-a>;\n" +
                "60: sessanta; sessant>%%with-a>;\n" +
                "70: settanta; settant>%%with-a>;\n" +
                "80: ottanta; ottant>%%with-a>;\n" +
                "90: novanta; novant>%%with-a>;\n" +
                "100: cento; cent[>%%with-o>];\n" +
                "200: <<cento; <<cent[>%%with-o>];\n" +
                "1000: mille; mill[>%%with-i>];\n" +
                "2000: <<mila; <<mil[>%%with-a>];\n" +
                "100,000>>: <<mila[ >>];\n" +
                "1,000,000: =#,##0= (incomplete data);\n" +
            "%%with-a:\n" +
                "azero; uno; adue; atre; aquattro; acinque; asei; asette; otto; anove;\n" +
                "adieci; undici; adodici; atredici; aquattordici; aquindici; asedici;\n" +
                "    adiciasette; adiciotto; adiciannove;\n" +
                "20: aventi; avent>%%with-i>;\n" +
                "30: atrenta; atrent>%%with-i>;\n" +
                "40: aquaranta; aquarant>%%with-a>;\n" +
                "50: acinquanta; acinquant>%%with-a>;\n" +
                "60: asessanta; asessant>%%with-a>;\n" +
                "70: asettanta; asettant>%%with-a>;\n" +
                "80: ottanta; ottant>%%with-a>;\n" +
                "90: anovanta; anovant>%%with-a>;\n" +
                "100: acento; acent[>%%with-o>];\n" +
                "200: <%%with-a<cento; <%%with-a<cent[>%%with-o>];\n" +
                "1000: amille; amill[>%%with-i>];\n" +
                "2000: <%%with-a<mila; <%%with-a<mil[>%%with-a>];\n" +
                "100,000: =%main=;\n" +
            "%%with-i:\n" +
                "izero; uno; idue; itre; iquattro; icinque; isei; isette; otto; inove;\n" +
                "idieci; undici; idodici; itredici; iquattordici; iquindici; isedici;\n" +
                "    idiciasette; idiciotto; idiciannove;\n" +
                "20: iventi; ivent>%%with-i>;\n" +
                "30: itrenta; itrent>%%with-i>;\n" +
                "40: iquaranta; iquarant>%%with-a>;\n" +
                "50: icinquanta; icinquant>%%with-a>;\n" +
                "60: isessanta; isessant>%%with-a>;\n" +
                "70: isettanta; isettant>%%with-a>;\n" +
                "80: ottanta; ottant>%%with-a>;\n" +
                "90: inovanta; inovant>%%with-a>;\n" +
                "100: icento; icent[>%%with-o>];\n" +
                "200: <%%with-i<cento; <%%with-i<cent[>%%with-o>];\n" +
                "1000: imille; imill[>%%with-i>];\n" +
                "2000: <%%with-i<mila; <%%with-i<mil[>%%with-a>];\n" +
                "100,000: =%main=;\n" +
            "%%with-o:\n" +
                "ozero; uno; odue; otre; oquattro; ocinque; osei; osette; otto; onove;\n" +
                "odieci; undici; ododici; otredici; oquattordici; oquindici; osedici;\n" +
                "    odiciasette; odiciotto; odiciannove;\n" +
                "20: oventi; ovent>%%with-i>;\n" +
                "30: otrenta; otrent>%%with-i>;\n" +
                "40: oquaranta; oquarant>%%with-a>;\n" +
                "50: ocinquanta; ocinquant>%%with-a>;\n" +
                "60: osessanta; osessant>%%with-a>;\n" +
                "70: osettanta; osettant>%%with-a>;\n" +
                "80: ottanta; ottant>%%with-a>;\n" +
                "90: onovanta; onovant>%%with-a>;\n" +
                "100: ocento; ocent[>%%with-o>];\n" +
                "200: <%%with-o<cento; <%%with-o<cent[>%%with-o>];\n" +
                "1000: omille; omill[>%%with-i>];\n" +
                "2000: <%%with-o<mila; <%%with-o<mil[>%%with-a>];\n" +
                "100,000: =%main=;\n" ;

        String en_OrdinalRules =
            // this rule set formats the numeral and calls %%abbrev to
            // supply the abbreviation
            "%main:\n" +
                "=#,##0==%%abbrev=;\n" +
            // this rule set supplies the abbreviation
            "%%abbrev:\n" +
                   // the abbreviations.  Everything from 4 to 19 ends in "th"
                "th; st; nd; rd; th;\n" +
                   // at 20, we begin repeating the cycle every 10 (13 is "13th",
                   // but 23 and 33 are "23rd" and "33rd")  We do this by
                   // ignoring all bug the ones digit in selecting the abbreviation
                "20: >>;\n" +
                   // at 100, we repeat the whole cycle by considering only the
                   // tens and ones digits in picking an abbreviation
                "100: >>;\n";

        String en_DurationRules =
            // main rule set for formatting with words
            "%with-words:\n" +
                   // take care of singular and plural forms of "second"
                "0 seconds; 1 second; =0= seconds;\n" +
                   // use %%min to format values greater than 60 seconds
                "60/60: <%%min<[, >>];\n" +
                   // use %%hr to format values greater than 3,600 seconds
                   // (the ">>>" below causes us to see the number of minutes
                   // when when there are zero minutes)
                "3600/60: <%%hr<[, >>>];\n" +
            // this rule set takes care of the singular and plural forms
            // of "minute"
            "%%min:\n" +
                "0 minutes; 1 minute; =0= minutes;\n" +
            // this rule set takes care of the singular and plural forms
            // of "hour"
            "%%hr:\n" +
                "0 hours; 1 hour; =0= hours;\n" +

            // main rule set for formatting in numerals
            "%in-numerals:\n" +
                   // values below 60 seconds are shown with "sec."
                "=0= sec.;\n" +
                   // higher values are shown with colons: %%min-sec is used for
                   // values below 3,600 seconds...
                "60: =%%min-sec=;\n" +
                   // ...and %%hr-min-sec is used for values of 3,600 seconds
                   // and above
                "3600: =%%hr-min-sec=;\n" +
            // this rule causes values of less than 10 minutes to show without
            // a leading zero
            "%%min-sec:\n" +
                "0: :=00=;\n" +
                "60/60: <0<>>;\n" +
            // this rule set is used for values of 3,600 or more.  Minutes are always
            // shown, and always shown with two digits
            "%%hr-min-sec:\n" +
                "0: :=00=;\n" +
                "60/60: <00<>>;\n" +
                "3600/60: <#,##0<:>>>;\n" +
            // the lenient-parse rules allow several different characters to be used
            // as delimiters between hours, minutes, and seconds
            "%%lenient-parse:\n" +
                "& ':' = '.' = ' ' = '-';\n";

            HashMap<String, String> cannedData = new HashMap<>();

        {
            cannedData.put("en_CA/SpelloutRules",      en_SpelloutRules);
            cannedData.put("en_CA/OrdinalRules",       en_OrdinalRules);
            cannedData.put("en_CA/DurationRules",      en_DurationRules);

            cannedData.put("fr_CA/SpelloutRules",      fr_SpelloutRules);
            cannedData.put("fr_CA/OrdinalRules",       xx_OrdinalRules);
            cannedData.put("fr_CA/DurationRules",      xx_DurationRules);

            cannedData.put("zh_CN/SpelloutRules",      en_SpelloutRules);
            cannedData.put("zh_CN/OrdinalRules",       en_OrdinalRules);
            cannedData.put("zh_CH/DurationRules",      xx_DurationRules);

            cannedData.put("zh/SpelloutRules",         en_SpelloutRules);
            cannedData.put("zh/OrdinalRules",          en_OrdinalRules);
            cannedData.put("zh_DurationRules",         xx_DurationRules);

            cannedData.put("en/SpelloutRules",         en_SpelloutRules);
            cannedData.put("en/OrdinalRules",          en_OrdinalRules);
            cannedData.put("en/DurationRules",         en_DurationRules);

            cannedData.put("fr_FR/SpelloutRules",      fr_SpelloutRules);
            cannedData.put("fr_FR/OrdinalRules",       xx_OrdinalRules);
            cannedData.put("fr_FR/DurationRules",      xx_DurationRules);

            cannedData.put("fr/SpelloutRules",         fr_SpelloutRules);
            cannedData.put("fr/OrdinalRules",          xx_OrdinalRules);
            cannedData.put("fr/DurationRules",         xx_DurationRules);

            cannedData.put("de/SpelloutRules",         de_SpelloutRules);
            cannedData.put("de/OrdinalRules",          xx_OrdinalRules);
            cannedData.put("de/DurationRules",         xx_DurationRules);

            cannedData.put("de_DE/SpelloutRules",      de_SpelloutRules);
            cannedData.put("de_DE/OrdinalRules",       xx_OrdinalRules);
            cannedData.put("de_DE/DurationRules",      xx_DurationRules);

            cannedData.put("it/SpelloutRules",         it_SpelloutRules);
            cannedData.put("it/OrdinalRules",          xx_OrdinalRules);
            cannedData.put("it/DurationRules",         xx_DurationRules);

            cannedData.put("it_IT/SpelloutRules",      it_SpelloutRules);
            cannedData.put("it_IT/OrdinalRules",       xx_OrdinalRules);
            cannedData.put("it_IT/DuratonRules",       xx_DurationRules);

            cannedData.put("ko_KR/SpelloutRules",      en_SpelloutRules);
            cannedData.put("ko_KR/OrdinalRules",       en_OrdinalRules);
            cannedData.put("ko_KR/DurationRules",      en_DurationRules);

            cannedData.put("ko/SpelloutRules",         en_SpelloutRules);
            cannedData.put("ko/OrdinalRules",          en_OrdinalRules);
            cannedData.put("ko/DurationRules",         en_DurationRules);

            cannedData.put("zh_Hans_CN/SpelloutRules", en_SpelloutRules);
            cannedData.put("zh_Hans_CN/OrdinalRules",  en_OrdinalRules);
            cannedData.put("zh_Hans_CH/DurationRules", xx_DurationRules);

            cannedData.put("zh_Hant_TW/SpelloutRules", en_SpelloutRules);
            cannedData.put("zh_Hant_TW/OrdinalRules",  en_OrdinalRules);
            cannedData.put("zh_Hant_TW/DurationRules", en_DurationRules);

            cannedData.put("zh_TW/SpelloutRules",      en_SpelloutRules);
            cannedData.put("zh_TW/OrdinalRules",       en_OrdinalRules);
            cannedData.put("zh_TW/DurationRules",      en_DurationRules);

            cannedData.put("en_GB/SpelloutRules",      en_GB_SpelloutRules);
            cannedData.put("en_GB/OrdinalRules",       en_OrdinalRules);
            cannedData.put("en_GB/DurationRules",      en_DurationRules);

            cannedData.put("en_US/SpelloutRules",      en_SpelloutRules);
            cannedData.put("en_US/OrdinalRules",       en_OrdinalRules);
            cannedData.put("en_US/DurationRules",      en_DurationRules);

            cannedData.put("ja/SpelloutRules",         ja_spelloutRules);
            cannedData.put("ja/OrdinalRules",          xx_OrdinalRules);
            cannedData.put("ja/DurationRules",         xx_DurationRules);

            cannedData.put("ja_JP/SpelloutRules",      ja_spelloutRules);
            cannedData.put("ja_JP/OrdinalRules",       xx_OrdinalRules);
            cannedData.put("ja_JP/DurationRules",      xx_DurationRules);
        }

        int types[]        = {RuleBasedNumberFormat.SPELLOUT, RuleBasedNumberFormat.ORDINAL, RuleBasedNumberFormat.DURATION};
        String typeNames[] = {"SpelloutRules", "OrdinalRules", "DurationRules"};

        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            RuleBasedNumberFormat formats[] = new RuleBasedNumberFormat[types.length * locales.length];
            int i = 0;

            for (int t = 0; t < types.length; t += 1) {
                for (int l = 0; l < locales.length; l += 1) {
                    String cannedRules = (String) cannedData.get(locales[l].toString() + "/" + typeNames[t]);

                    if (cannedRules != null) {
                        formats[i++] = new RuleBasedNumberFormat(cannedRules, locales[l]);
                    } else {
                        formats[i++] = new RuleBasedNumberFormat(locales[l], types[t]);
                    }
                }
            }

            return formats;
        }
    }

    public static class DecimalFormatSymbolsHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            DecimalFormatSymbols dfs[] = new DecimalFormatSymbols[locales.length];

            for (int i = 0; i < locales.length; i += 1) {
                ULocale uloc = ULocale.forLocale(locales[i]);

                dfs[i] = getCannedDecimalFormatSymbols(uloc);

//                System.out.println("\n    " + uloc.toString() + " = \"" +
//                        com.ibm.icu.impl.Utility.escape(String.valueOf(getCharSymbols(dfs[i]), 0, 12)) + "\"");
            }

            return dfs;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            DecimalFormatSymbols dfs_a = (DecimalFormatSymbols) a;
            DecimalFormatSymbols dfs_b = (DecimalFormatSymbols) b;
            String strings_a[] = getStringSymbols(dfs_a);
            String strings_b[] = getStringSymbols(dfs_b);
            char chars_a[] = getCharSymbols(dfs_a);
            char chars_b[] = getCharSymbols(dfs_b);

            // Spot-check char-to-string conversion (ICU 58)
            String percent_a1 = Character.toString(dfs_a.getPercent());
            String percent_a2 = dfs_a.getPercentString();
            String percent_b1 = Character.toString(dfs_b.getPercent());
            String percent_b2 = dfs_b.getPercentString();

            return SerializableTestUtility.compareStrings(strings_a, strings_b)
                    && SerializableTestUtility.compareChars(chars_a, chars_b)
                    && percent_a1.equals(percent_b1)
                    && percent_a2.equals(percent_b2)
                    && percent_a1.equals(percent_a2);
        }
    }

    public static class CurrencyPluralInfoHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            CurrencyPluralInfo currencyPluralInfo[] = {
                new CurrencyPluralInfo(), new CurrencyPluralInfo(new ULocale("en_US")),
                CurrencyPluralInfo.getInstance(), CurrencyPluralInfo.getInstance(new Locale("en", "US")),
                CurrencyPluralInfo.getInstance(new ULocale("en_US"))
            };
            for(int i=0; i<currencyPluralInfo.length; i++){
                currencyPluralInfo[i].setPluralRules("one: n is 1; few: n in 2..4");
                currencyPluralInfo[i].setCurrencyPluralPattern("few", "few currency");
            }
            return currencyPluralInfo;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return a.equals(b);
        }
    }

    public static class CompactDecimalFormatHandler extends NumberFormatHandler {
        @Override
        public Object[] getTestObjects() {
            return new CompactDecimalFormat[0];
        }
    }

    public static class MessageFormatHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            MessageFormat formats[] = {new MessageFormat("pattern{0}")};

            return formats;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            MessageFormat mfa = (MessageFormat) a;
            MessageFormat mfb = (MessageFormat) b;
            Object arguments[] = {123456};

            return mfa.format(arguments) != mfb.format(arguments);
        }
    }

    public static class MessageFormatFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            return new Object[] {MessageFormat.Field.ARGUMENT};
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class RelativeDateTimeFormatterFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            return new Object[] {RelativeDateTimeFormatter.Field.LITERAL};
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class DateIntervalSpanFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            return new Object[] {DateIntervalFormat.SpanField.DATE_INTERVAL_SPAN};
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class ListFormatterFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            return new Object[] {ListFormatter.Field.ELEMENT, ListFormatter.Field.LITERAL};
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class ListFormatterSpanFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            return new Object[] {ListFormatter.SpanField.LIST_SPAN};
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class NumberRangeFormatterSpanFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            return new Object[] {NumberRangeFormatter.SpanField.NUMBER_RANGE_SPAN};
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class DateFormatHandler implements SerializableTestUtility.Handler
    {
        static HashMap<String, String> cannedPatterns = new HashMap<>();
        static Date fixedDate;

        {
            cannedPatterns.put("en_CA",      "EEEE, MMMM d, yyyy h:mm:ss a z");
            cannedPatterns.put("fr_CA",      "EEEE d MMMM yyyy HH' h 'mm' min 'ss' s 'z");
            cannedPatterns.put("zh_Hans_CN", "yyyy'\u5E74'M'\u6708'd'\u65E5'EEEE ahh'\u65F6'mm'\u5206'ss'\u79D2' z");
            cannedPatterns.put("zh_CN",      "yyyy'\u5E74'M'\u6708'd'\u65E5'EEEE ahh'\u65F6'mm'\u5206'ss'\u79D2' z");
            cannedPatterns.put("zh",         "EEEE, yyyy MMMM dd HH:mm:ss z");
            cannedPatterns.put("en",         "EEEE, MMMM d, yyyy h:mm:ss a z");
            cannedPatterns.put("fr_FR",      "EEEE d MMMM yyyy HH' h 'mm z");
            cannedPatterns.put("fr",         "EEEE d MMMM yyyy HH' h 'mm z");
            cannedPatterns.put("de",         "EEEE, d. MMMM yyyy H:mm' Uhr 'z");
            cannedPatterns.put("de_DE",      "EEEE, d. MMMM yyyy H:mm' Uhr 'z");
            cannedPatterns.put("it",         "EEEE d MMMM yyyy HH:mm:ss z");
            cannedPatterns.put("it_IT",      "EEEE d MMMM yyyy HH:mm:ss z");
            cannedPatterns.put("ja_JP",      "yyyy'\u5E74'M'\u6708'd'\u65E5'EEEE H'\u6642'mm'\u5206'ss'\u79D2'z");
            cannedPatterns.put("ja",         "yyyy'\u5E74'M'\u6708'd'\u65E5'EEEE H'\u6642'mm'\u5206'ss'\u79D2'z");
            cannedPatterns.put("ko_KR",      "yyyy'\uB144' M'\uC6D4' d'\uC77C' EEEE a hh'\uC2DC' mm'\uBD84' ss'\uCD08' z");
            cannedPatterns.put("ko",         "yyyy'\uB144' M'\uC6D4' d'\uC77C' EEEE a hh'\uC2DC' mm'\uBD84' ss'\uCD08' z");
            cannedPatterns.put("zh_Hant_TW", "yyyy'\u5E74'M'\u6708'd'\u65E5'EEEE ahh'\u6642'mm'\u5206'ss'\u79D2' z");
            cannedPatterns.put("zh_TW",      "yyyy'\u5E74'M'\u6708'd'\u65E5'EEEE ahh'\u6642'mm'\u5206'ss'\u79D2' z");
            cannedPatterns.put("en_GB",      "EEEE, d MMMM yyyy HH:mm:ss z");
            cannedPatterns.put("en_US",      "EEEE, MMMM d, yyyy h:mm:ss a z");

            // Get a date that will likely not move in or out of Daylight savings time...
            Calendar cal = Calendar.getInstance(Locale.US);

            cal.clear();
            cal.set(2007, Calendar.JANUARY, 1, 12, 0, 0); // January 1, 2007 12:00:00 PM.
            fixedDate = cal.getTime();
        }

        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            DateFormat formats[] = new DateFormat[locales.length];

            for (int i = 0; i < locales.length; i += 1) {
                ULocale uloc = ULocale.forLocale(locales[i]);

              //formats[i] = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locales[i]);
                formats[i] = getCannedSimpleDateFormat((String)cannedPatterns.get(uloc.toString()), uloc);
            }

            return formats;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            DateFormat dfa = (DateFormat) a;
            DateFormat dfb = (DateFormat) b;

            // We previously did not use 'canned' time zone.
            TimeZone tza = dfa.getTimeZone();
            TimeZone tzb = dfb.getTimeZone();
            if (!tza.getID().equals(tzb.getID())) {
                // If IDs do not match, reset TimeZone in dfa.
                dfa.setTimeZone(tzb);
            }

            String sfa = dfa.format(fixedDate);
            String sfb = dfb.format(fixedDate);

            if (!sfa.equals(sfb)) {
                // TODO
                // In ICU3.8, localized GMT format pattern was added in
                // DateFormatSymbols, which has no public setter.
                // The difference of locale data for localized GMT format
                // will produce different format result.  This is a temporary
                // workaround for the issue.
                // Hmm, this test had tmp backwards, it needs to override a behavior
                // with certain symbols from b. Fixed in ICU 60.
                DateFormatSymbols dfsb = ((SimpleDateFormat)dfb).getDateFormatSymbols();
                DateFormatSymbols tmp = (DateFormatSymbols)((SimpleDateFormat)dfa).getDateFormatSymbols().clone();

                TimeZoneFormat tmptzf = (TimeZoneFormat)((SimpleDateFormat)dfb).getTimeZoneFormat().clone();

                tmp.setMonths(dfsb.getMonths());
                tmp.setShortMonths(dfsb.getShortMonths());
                tmp.setWeekdays(dfsb.getWeekdays());
                tmp.setShortWeekdays(dfsb.getShortWeekdays());
                tmp.setAmPmStrings(dfsb.getAmPmStrings());

                ((SimpleDateFormat)dfa).setDateFormatSymbols(tmp);
                ((SimpleDateFormat)dfa).setTimeZoneFormat(tmptzf);

                sfa = dfa.format(fixedDate);
            }

            //return sfa.equals(sfb);
            if (!sfa.equals(sfb)) {
                return false;
            }
            return true;
        }

    }

    public static class DateFormatFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects() {
            return new Object[] {
                    DateFormat.Field.AM_PM,
                    DateFormat.Field.DAY_OF_MONTH,
                    DateFormat.Field.DAY_OF_WEEK,
                    DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
                    DateFormat.Field.DAY_OF_YEAR,
                    DateFormat.Field.ERA,
                    DateFormat.Field.HOUR_OF_DAY0,
                    DateFormat.Field.HOUR_OF_DAY1,
                    DateFormat.Field.HOUR0,
                    DateFormat.Field.HOUR1,
                    DateFormat.Field.MILLISECOND,
                    DateFormat.Field.MINUTE,
                    DateFormat.Field.MONTH,
                    DateFormat.Field.SECOND,
                    DateFormat.Field.TIME_ZONE,
                    DateFormat.Field.WEEK_OF_MONTH,
                    DateFormat.Field.WEEK_OF_YEAR,
                    DateFormat.Field.YEAR,
                    DateFormat.Field.DOW_LOCAL,
                    DateFormat.Field.EXTENDED_YEAR,
                    DateFormat.Field.JULIAN_DAY,
                    DateFormat.Field.MILLISECONDS_IN_DAY,
                    DateFormat.Field.YEAR_WOY,
                    DateFormat.Field.QUARTER
            };
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class DateFormatSymbolsHandler implements SerializableTestUtility.Handler
    {

        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            DateFormatSymbols dfs[] = new DateFormatSymbols[locales.length];

            for (int i = 0; i < locales.length; i += 1) {
                ULocale uloc = ULocale.forLocale(locales[i]);

                dfs[i] = getCannedDateFormatSymbols(uloc);
            }

            return dfs;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            DateFormatSymbols dfs_a = (DateFormatSymbols) a;
            DateFormatSymbols dfs_b = (DateFormatSymbols) b;
            String months_a[] = dfs_a.getMonths();
            String months_b[] = dfs_b.getMonths();

            return SerializableTestUtility.compareStrings(months_a, months_b);
        }
    }

    public static class SimpleDateFormatHandler extends DateFormatHandler
    {
        String patterns[] = {
            "EEEE, yyyy MMMM dd",
            "yyyy MMMM d",
            "yyyy MMM d",
            "yy/MM/dd"
        };

        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            SimpleDateFormat dateFormats[] = new SimpleDateFormat[patterns.length * locales.length];
            int i = 0;

            for (int p = 0; p < patterns.length; p += 1) {
                for (int l = 0; l < locales.length; l += 1) {
                    dateFormats[i++] = getCannedSimpleDateFormat(patterns[p], ULocale.forLocale(locales[l]));
                }
            }

            return dateFormats;
        }
    }

    public static class DateIntervalFormatHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            DateIntervalFormat dateIntervalFormats[] = {
                DateIntervalFormat.getInstance("yMMMMEEEEd")
            };
            return dateIntervalFormats;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            DateIntervalFormat dfa = (DateIntervalFormat) a;
            DateIntervalFormat dfb = (DateIntervalFormat) b;
            DateInterval dateInterval = new DateInterval(1, System.currentTimeMillis());
            String sfa = dfa.format(dateInterval);
            String sfb = dfb.format(dateInterval);

            return sfa.equals(sfb);
        }
    }


    public static class DateIntervalInfoHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            DateIntervalInfo dateIntervalInfo[] = {
                new DateIntervalInfo()
            };
            dateIntervalInfo[0].setIntervalPattern("yMd", Calendar.YEAR, "yy/MM/dd - yy/MM/dd");
            dateIntervalInfo[0].setIntervalPattern("yMd", Calendar.MONTH, "yy/MM - MM/dd");
            return dateIntervalInfo;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return a.equals(b);
        }
    }


    public static class PatternInfoHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            DateIntervalInfo.PatternInfo patternInfo[] = {
                new DateIntervalInfo.PatternInfo("yyyy MMM dd - ",
                                                 "dd",
                                                 false)
            };
            return patternInfo;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return a.equals(b);
        }
    }

    public static class ChineseDateFormatHandler extends DateFormatHandler
    {
        String patterns[] = {
            "EEEE y'x'G-Ml-d",
            "y'x'G-Ml-d",
            "y'x'G-Ml-d",
            "y'x'G-Ml-d"
        };

        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            ChineseDateFormat dateFormats[] = new ChineseDateFormat[patterns.length * locales.length];
            int i = 0;

            for (int p = 0; p < patterns.length; p += 1) {
                for (int l = 0; l < locales.length; l += 1) {
                    ULocale locale = new ULocale(locales[l].toString() + "@calendar=chinese");

                    dateFormats[i++] = new ChineseDateFormat(patterns[p], locale);
                }
            }

            return dateFormats;
        }
    }

    public static class ChineseDateFormatFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects() {
            return new Object[] {
                    ChineseDateFormat.Field.IS_LEAP_MONTH
            };
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            return (a == b);
        }
    }

    public static class ChineseDateFormatSymbolsHandler extends DateFormatSymbolsHandler
    {
        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            ChineseDateFormatSymbols cdfs[] = new ChineseDateFormatSymbols[locales.length];

            for (int i = 0; i < locales.length; i += 1) {
                ULocale uloc = ULocale.forLocale(locales[i]);

                cdfs[i] = new ChineseDateFormatSymbols(uloc);
                cdfs[i].setMonths((String[]) cannedMonthNames.get(uloc.toString()));
            }

            return cdfs;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            if (! super.hasSameBehavior(a, b)) {
                return false;
            }

            ChineseDateFormatSymbols cdfs_a = (ChineseDateFormatSymbols) a;
            ChineseDateFormatSymbols cdfs_b = (ChineseDateFormatSymbols) b;

            // The old test did this, which tested that the leap month marker never
            // changed from one ICU version to the next; this is not a valid test.
            //return cdfs_a.getLeapMonth(0).equals(cdfs_b.getLeapMonth(0)) &&
            //     cdfs_a.getLeapMonth(1).equals(cdfs_b.getLeapMonth(1));
            //
            // A more valid test is that from one version to the next, the
            // marker for getLeapMonth(0) does not change and is empty, while
            // the marker for getLeapMonth(1) is non-empty in both versions:
            return cdfs_a.getLeapMonth(0).equals(cdfs_b.getLeapMonth(0)) &&
                   cdfs_a.getLeapMonth(0).length() == 0 &&
                   cdfs_a.getLeapMonth(1).length() > 0 &&
                   cdfs_b.getLeapMonth(1).length() > 0;
        }
    }

    public static class NumberFormatFieldHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            NumberFormat.Field fields[] = {
                NumberFormat.Field.CURRENCY, NumberFormat.Field.DECIMAL_SEPARATOR, NumberFormat.Field.EXPONENT,
                NumberFormat.Field.EXPONENT_SIGN, NumberFormat.Field.EXPONENT_SYMBOL, NumberFormat.Field.FRACTION,
                NumberFormat.Field.GROUPING_SEPARATOR, NumberFormat.Field.INTEGER, NumberFormat.Field.PERCENT,
                NumberFormat.Field.PERMILLE, NumberFormat.Field.SIGN
            };

            return fields;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b)
        {
            NumberFormat.Field field_a = (NumberFormat.Field) a;
            NumberFormat.Field field_b = (NumberFormat.Field) b;

            return field_a.toString().equals(field_b.toString());
        }
    }

    public static class DateNumberFormatHandler implements SerializableTestUtility.Handler
    {
        @Override
        public Object[] getTestObjects()
        {
            Locale locales[] = SerializableTestUtility.getLocales();
            DateNumberFormat[] dnfmts = new DateNumberFormat[locales.length];
            for (int i = 0; i < locales.length; i++) {
                ULocale uloc = ULocale.forLocale(locales[i]);
                dnfmts[i] = new DateNumberFormat(uloc,'0',"latn");
            }
            return dnfmts;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            return a.equals(b);
        }
    }

    public static class SelectFormatHandler implements SerializableTestUtility.Handler {

        @Override
        public Object[] getTestObjects() {
            SelectFormat[] selfmts = {new SelectFormat("keyword{phrase} other{otherPhrase}")};

            return selfmts;
        }

        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            SelectFormat sfa = (SelectFormat) a;
            SelectFormat sfb = (SelectFormat) b;
            String argument = "keyword";

            return sfa.format(argument) != sfb.format(argument);
        }
    }

    public static class PluralFormatHandler implements SerializableTestUtility.Handler {
        @Override
        public Object[] getTestObjects() {
            Locale[] locales = { Locale.US }; // main test is in plural rules handler
            PluralFormat[] plfmts = new PluralFormat[locales.length];
            for (int i = 0; i < locales.length; i++) {
                ULocale uloc = ULocale.forLocale(locales[i]);
                try {
                    plfmts[i] = new PluralFormat(uloc, "one{1 foo} other{# foo}");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return plfmts;
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            PluralFormat pfa = (PluralFormat)a;
            PluralFormat pfb = (PluralFormat)b;
            boolean isSame = true;
            for (int i = 0; i < 10; i++) {
                String texta = pfa.format(i);
                String textb = pfb.format(i);
                if (!texta.equals(textb)) {
                    isSame = false;
                    break;
                }
            }
            return isSame;
        }
    }

    public static class PluralRulesHandler implements SerializableTestUtility.Handler {
        // canned rules as of 4.2
        final String[] cannedRules = {
                "", // ja
                "one: n is 1", // da
                "one: n within 0..2 and n is not 2", // fr
                "one: n mod 10 is 1 and n mod 100 is not 11; zero: n is 0", // lv
                "one: n is 1; two: n is 2", // ga
                "few: n is 0 OR n is not 1 AND n mod 100 in 1..19; one: n is 1", // ro
                "few: n mod 10 in 2..9 and n mod 100 not in 11..19; one: n mod 10 is 1 and n mod 100 not in 11..19", // lt
                "few: n mod 10 in 2..4 and n mod 100 not in 12..14; many: n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14; one: n mod 10 is 1 and n mod 100 is not 11", // hr
                "few: n in 2..4; one: n is 1", // cs
                "few: n mod 10 in 2..4 and n mod 100 not in 12..14 and n mod 100 not in 22..24; one: n is 1", // pl
                "few: n mod 100 in 3..4; one: n mod 100 is 1; two: n mod 100 is 2", // sl
        };

        @Override
        public Object[] getTestObjects() {
            PluralRules[] plrulz = new PluralRules[cannedRules.length];
            for (int i = 0; i < cannedRules.length; i++) {
                try {
                    plrulz[i] = PluralRules.parseDescription(cannedRules[i]);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return plrulz;
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            return a.equals(b);
        }
    }

    public static class PluralRulesSerialProxyHandler implements SerializableTestUtility.Handler {
        // Tested through PluralRules, so just a stub here to keep CoverageTest happy
        final String[] cannedRules = {};

        @Override
        public Object[] getTestObjects() {
            return new PluralRules[cannedRules.length];
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            return a.equals(b);
        }
    }



    public static class TimeUnitFormatHandler implements SerializableTestUtility.Handler {
        // TODO - more test coverage!
        @Override
        public Object[] getTestObjects() {
            return new Object[] { new TimeUnitFormat().setLocale(ULocale.ENGLISH) };
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            TimeUnitFormat tufa = (TimeUnitFormat)a;
            TimeUnitFormat tufb = (TimeUnitFormat)b;

            TimeUnitAmount amount = new TimeUnitAmount(3, TimeUnit.HOUR);
            String resa = tufa.format(amount);
            String resb = tufb.format(amount);

            return resa.equals(resb);
        }
    }

    public static class TimeZoneNamesHandler implements SerializableTestUtility.Handler {
        @Override
        public Object[] getTestObjects() {
            return new Object[] {
                    TimeZoneNames.getInstance(ULocale.ENGLISH),
                    TimeZoneNames.getInstance(ULocale.JAPAN)
            };
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            TimeZoneNames tzna = (TimeZoneNames)a;
            TimeZoneNames tznb = (TimeZoneNames)b;

            final String tzid = "America/Los_Angeles";

            String eca = tzna.getExemplarLocationName(tzid);
            String ecb = tznb.getExemplarLocationName(tzid);

            if (!eca.equals(ecb)) {
                return false;
            }

            final String mzID = "America_Pacific";
            final String region = "US";

            String refza = tzna.getReferenceZoneID(mzID, region);
            String refzb = tznb.getReferenceZoneID(mzID, region);

            if (!refza.equals(refzb)) {
                return false;
            }

            return true;
        }
    }

    public static class TimeZoneGenericNamesHandler implements SerializableTestUtility.Handler {
        @Override
        public Object[] getTestObjects() {
            return new Object[] {
                    TimeZoneGenericNames.getInstance(ULocale.ENGLISH),
                    TimeZoneGenericNames.getInstance(ULocale.JAPAN)
            };
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            TimeZoneGenericNames tzgna = (TimeZoneGenericNames)a;
            TimeZoneGenericNames tzgnb = (TimeZoneGenericNames)b;

            final String[] TZIDS = {
                "America/Los_Angeles",
                "America/Argentina/Buenos_Aires",
                "Etc/GMT"
            };

            final long[] DATES = {
                1277942400000L, // 2010-07-01 00:00:00 GMT
                1293840000000L, // 2011-01-01 00:00:00 GMT
            };

            for (String tzid : TZIDS) {
                TimeZone tz = TimeZone.getTimeZone(tzid);
                for (GenericNameType nt : GenericNameType.values()) {
                    for (long date : DATES) {
                        String nameA = tzgna.getDisplayName(tz, nt, date);
                        String nameB = tzgnb.getDisplayName(tz, nt, date);
                        if (!Objects.equals(nameA, nameB)) {
                            return false;
                        }
                    }
                }
            }

            return true;
        }
    }

    public static class TZDBTimeZoneNamesHandler implements SerializableTestUtility.Handler {
        @Override
        public Object[] getTestObjects() {
            return new Object[] {
                    TimeZoneNames.getTZDBInstance(ULocale.ENGLISH),
                    TimeZoneNames.getTZDBInstance(ULocale.JAPAN)
            };
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            TZDBTimeZoneNames tzdbna = (TZDBTimeZoneNames)a;
            TZDBTimeZoneNames tzdbnb = (TZDBTimeZoneNames)b;

            final String[] TZIDS = {
                "America/Los_Angeles",
                "America/Argentina/Buenos_Aires",
                "Asia/Shanghai",
                "Etc/GMT"
            };

            final long[] DATES = {
                1277942400000L, // 2010-07-01 00:00:00 GMT
                1293840000000L, // 2011-01-01 00:00:00 GMT
            };

            final NameType[] nTypes = {
                    NameType.SHORT_STANDARD,
                    NameType.SHORT_DAYLIGHT
            };

            for (String tzid : TZIDS) {
                for (NameType nt : nTypes) {
                    for (long date : DATES) {
                        String nameA = tzdbna.getDisplayName(tzid, nt, date);
                        String nameB = tzdbnb.getDisplayName(tzid, nt, date);
                        if (!Objects.equals(nameA, nameB)) {
                            return false;
                        }
                    }
                }
            }

            return true;
        }
    }

    public static class TimeZoneFormatHandler implements SerializableTestUtility.Handler {
        static final String CUSTOM_GMT_PATTERN = "Offset {0} from UTC";

        @Override
        public Object[] getTestObjects() {
            TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(ULocale.ENGLISH).cloneAsThawed();
            tzfmt.setGMTPattern(CUSTOM_GMT_PATTERN);

            return new Object[] {tzfmt};
        }
        @Override
        public boolean hasSameBehavior(Object a, Object b) {
            TimeZoneFormat tzfa = (TimeZoneFormat)a;
            TimeZoneFormat tzfb = (TimeZoneFormat)b;

            if (!tzfa.getGMTPattern().equals(tzfb.getGMTPattern())) {
                return false;
            }

            final int offset = -5 * 60 * 60 * 1000;

            String gmta = tzfa.formatOffsetLocalizedGMT(offset);
            String gmtb = tzfb.formatOffsetLocalizedGMT(offset);

            if (!gmta.equals(gmtb)) {
                return false;
            }

            long now = System.currentTimeMillis();
            TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");

            String genloca = tzfa.format(Style.GENERIC_LOCATION, tz, now);
            String genlocb = tzfb.format(Style.GENERIC_LOCATION, tz, now);

            if (!genloca.equals(genlocb)) {
                return false;
            }

            return true;
        }
    }

    public static void main(String[] args)
    {
        // nothing needed...
    }

}
//eof
